You've already forked hotpocket
BTHLABS-58: Share Extension in Apple Apps
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 5.2.3 on 2025-09-22 07:20
|
||||
|
||||
import uuid6
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0005_accesstoken'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AuthKey',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid6.uuid7, editable=False, primary_key=True, serialize=False)),
|
||||
('account_uuid', models.UUIDField(db_index=True, default=None)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('deleted_at', models.DateTimeField(blank=True, db_index=True, default=None, editable=False, null=True)),
|
||||
('key', models.CharField(db_index=True, default=None, editable=False, max_length=128, unique=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Auth Key',
|
||||
'verbose_name_plural': 'Auth Keys',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.3 on 2025-10-01 07:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0006_authkey'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='authkey',
|
||||
name='consumed_at',
|
||||
field=models.DateTimeField(blank=True, db_index=True, default=None, editable=False, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,2 +1,3 @@
|
||||
from .access_token import AccessToken # noqa: F401
|
||||
from .account import Account # noqa: F401
|
||||
from .auth_key import AuthKey # noqa: F401
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from hotpocket_backend.apps.core.models import Model
|
||||
|
||||
|
||||
class ActiveAuthKeysManager(models.Manager):
|
||||
def get_queryset(self) -> models.QuerySet[AuthKey]:
|
||||
return super().get_queryset().filter(
|
||||
deleted_at__isnull=True,
|
||||
)
|
||||
|
||||
|
||||
class AuthKey(Model):
|
||||
key = models.CharField(
|
||||
blank=False,
|
||||
default=None,
|
||||
null=False,
|
||||
max_length=128,
|
||||
db_index=True,
|
||||
unique=True,
|
||||
editable=False,
|
||||
)
|
||||
consumed_at = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
db_index=True,
|
||||
editable=False,
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
active_objects = ActiveAuthKeysManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Auth Key')
|
||||
verbose_name_plural = _('Auth Keys')
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'<AuthKey pk={self.pk} key={self.key}>'
|
||||
@@ -1 +1,3 @@
|
||||
from .access_tokens import AccessTokensService # noqa: F401
|
||||
from .accounts import AccountsService # noqa: F401
|
||||
from .auth_keys import AuthKeysService # noqa: F401
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import Account
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountsService:
|
||||
class AccountsServiceError(Exception):
|
||||
pass
|
||||
|
||||
class AccountNotFound(AccountsServiceError):
|
||||
pass
|
||||
|
||||
def get(self, *, pk: uuid.UUID) -> Account:
|
||||
try:
|
||||
query_set = Account.objects.filter(is_active=True)
|
||||
|
||||
return query_set.get(pk=pk)
|
||||
except Account.DoesNotExist as exception:
|
||||
raise self.AccountNotFound(
|
||||
f'Account not found: pk=`{pk}`',
|
||||
) from exception
|
||||
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from django.utils.timezone import now
|
||||
import uuid6
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import AuthKey
|
||||
from hotpocket_backend.apps.core.conf import settings
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthKeysService:
|
||||
class AuthKeysServiceError(Exception):
|
||||
pass
|
||||
|
||||
class AuthKeyNotFound(AuthKeysServiceError):
|
||||
pass
|
||||
|
||||
class AuthKeyExpired(AuthKeysServiceError):
|
||||
pass
|
||||
|
||||
class AuthKeyAccessDenied(AuthKeysServiceError):
|
||||
pass
|
||||
|
||||
def create(self, *, account_uuid: uuid.UUID) -> AuthKey:
|
||||
key = str(uuid6.uuid7())
|
||||
|
||||
return AuthKey.objects.create(
|
||||
account_uuid=account_uuid,
|
||||
key=key,
|
||||
)
|
||||
|
||||
def get(self, *, pk: uuid.UUID) -> AuthKey:
|
||||
try:
|
||||
query_set = AuthKey.active_objects
|
||||
|
||||
return query_set.get(pk=pk)
|
||||
except AuthKey.DoesNotExist as exception:
|
||||
raise self.AuthKeyNotFound(
|
||||
f'Auth Key not found: pk=`{pk}`',
|
||||
) from exception
|
||||
|
||||
def get_by_key(self, *, key: str, ttl: int | None = None) -> AuthKey:
|
||||
try:
|
||||
query_set = AuthKey.active_objects
|
||||
|
||||
result = query_set.get(key=key)
|
||||
|
||||
if ttl is None:
|
||||
ttl = settings.AUTH_KEY_TTL
|
||||
|
||||
if ttl > 0:
|
||||
if result.created_at < now() - datetime.timedelta(seconds=ttl):
|
||||
raise self.AuthKeyExpired(
|
||||
f'Auth Key expired: pk=`{key}`',
|
||||
)
|
||||
|
||||
if result.consumed_at is not None:
|
||||
raise self.AuthKeyExpired(
|
||||
f'Auth Key already consumed: pk=`{key}`',
|
||||
)
|
||||
|
||||
return result
|
||||
except AuthKey.DoesNotExist as exception:
|
||||
raise self.AuthKeyNotFound(
|
||||
f'Auth Key not found: key=`{key}`',
|
||||
) from exception
|
||||
Reference in New Issue
Block a user