From f405f7261ddb6b3a57bc7efab4620e17bc70d698 Mon Sep 17 00:00:00 2001 From: na-stewart Date: Sat, 22 Jun 2024 20:55:04 -0400 Subject: [PATCH] Syntax and refresh revision --- sanic_security/authentication.py | 30 ++++++++++++------------------ sanic_security/configuration.py | 4 ---- sanic_security/models.py | 14 +++++++++----- sanic_security/test/server.py | 2 +- sanic_security/test/tests.py | 13 ++++++++++--- sanic_security/utils.py | 4 +++- sanic_security/verification.py | 5 +---- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/sanic_security/authentication.py b/sanic_security/authentication.py index 660700d..f4d23ea 100644 --- a/sanic_security/authentication.py +++ b/sanic_security/authentication.py @@ -45,7 +45,7 @@ async def register( - request: Request, verified: bool = False, disabled: bool = False + request: Request, verified: bool = False, disabled: bool = False ) -> Account: """ Registers a new account that can be logged into. @@ -65,14 +65,14 @@ async def register( if await Account.filter(email=email_lower).exists(): raise CredentialsError("An account with this email may already exist.", 409) elif await Account.filter( - username=validate_username(request.form.get("username")) + username=validate_username(request.form.get("username")) ).exists(): raise CredentialsError("An account with this username may already exist.", 409) elif ( - request.form.get("phone") - and await Account.filter( - phone=validate_phone(request.form.get("phone")) - ).exists() + request.form.get("phone") + and await Account.filter( + phone=validate_phone(request.form.get("phone")) + ).exists() ): raise CredentialsError( "An account with this phone number may already exist.", 409 @@ -90,7 +90,7 @@ async def register( async def login( - request: Request, account: Account = None, require_second_factor: bool = False + request: Request, account: Account = None, require_second_factor: bool = False ) -> AuthenticationSession: """ Login with email or username (if enabled) and password. @@ -195,7 +195,7 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession: return authentication_session -async def authenticate(request: Request) -> AuthenticationSession: +async def authenticate(request: Request) -> tuple[bool, AuthenticationSession]: """ Validates client's authentication session and account. @@ -220,11 +220,8 @@ async def authenticate(request: Request) -> AuthenticationSession: authentication_session.validate() if not authentication_session.anonymous: authentication_session.bearer.validate() - except ExpiredError as e: - if security_config.AUTHENTICATION_REFRESH_AUTO: - authentication_session = await authentication_session.refresh(request) - else: - raise e + except ExpiredError: + authentication_session = await authentication_session.refresh(request) return authentication_session @@ -258,10 +255,7 @@ async def wrapper(request, *args, **kwargs): return wrapper - if callable(arg): - return decorator(arg) - else: - return decorator + return decorator(arg) if callable(arg) else decorator def create_initial_admin_account(app: Sanic) -> None: @@ -356,7 +350,7 @@ def validate_phone(phone: str) -> str: CredentialsError """ if phone and not re.search( - r"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$", phone + r"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$", phone ): raise CredentialsError("Please use a valid phone number.", 400) return phone diff --git a/sanic_security/configuration.py b/sanic_security/configuration.py index 826d597..af4f48f 100644 --- a/sanic_security/configuration.py +++ b/sanic_security/configuration.py @@ -1,7 +1,6 @@ from os import environ from sanic.utils import str_to_bool -from sanic.log import logger """ Copyright (c) 2020-present Nicholas Aidan Stewart @@ -41,7 +40,6 @@ "TWO_STEP_SESSION_EXPIRATION": 300, "AUTHENTICATION_SESSION_EXPIRATION": 86400, "AUTHENTICATION_REFRESH_EXPIRATION": 2592000, - "AUTHENTICATION_REFRESH_AUTO": True, "ALLOW_LOGIN_WITH_USERNAME": False, "INITIAL_ADMIN_EMAIL": "admin@example.com", "INITIAL_ADMIN_PASSWORD": "admin123", @@ -68,7 +66,6 @@ class Config(dict): TWO_STEP_SESSION_EXPIRATION (int): The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration. AUTHENTICATION_SESSION_EXPIRATION (int): The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration. AUTHENTICATION_REFRESH_EXPIRATION (int): The amount of seconds till authentication session refresh expiration. - AUTHENTICATION_REFRESH_AUTO (bool): Expired sessions will be automatically refreshed and passed into endpoint on authentication attempt, requires session to be re-encoded. ALLOW_LOGIN_WITH_USERNAME (bool): Allows login via username and email. INITIAL_ADMIN_EMAIL (str): Email used when creating the initial admin account. INITIAL_ADMIN_PASSWORD (str): Password used when creating the initial admin account. @@ -89,7 +86,6 @@ class Config(dict): TWO_STEP_SESSION_EXPIRATION: int AUTHENTICATION_SESSION_EXPIRATION: int AUTHENTICATION_REFRESH_EXPIRATION: int - AUTHENTICATION_REFRESH_AUTO: bool ALLOW_LOGIN_WITH_USERNAME: bool INITIAL_ADMIN_EMAIL: str INITIAL_ADMIN_PASSWORD: str diff --git a/sanic_security/models.py b/sanic_security/models.py index f6d1673..604a6d6 100644 --- a/sanic_security/models.py +++ b/sanic_security/models.py @@ -526,12 +526,12 @@ class AuthenticationSession(Session): Attributes: requires_second_factor (bool): Determines if session requires a second factor. refresh_expiration_date (bool): Date and time the session can no longer be refreshed. - is_refresh (bool): Will only be true for the first time session is created during refresh. + is_refresh (bool): Will only be true in the instance the session is created during refresh. """ requires_second_factor: bool = fields.BooleanField(default=False) refresh_expiration_date: datetime.datetime = fields.DatetimeField(null=True) - is_refresh: bool + is_refresh: bool = False def validate(self) -> None: """ @@ -571,13 +571,15 @@ async def refresh(self, request: Request): ): self.active = False await self.save(update_fields=["active"]) - return await self.new(request, self.bearer, is_refresh=True) + return await self.new(request, self.bearer, True) else: raise e @classmethod - async def new(cls, request: Request, account: Account = None, **kwargs): - return await AuthenticationSession.create( + async def new( + cls, request: Request, account: Account = None, is_refresh=False, **kwargs + ): + authentication_session = await AuthenticationSession.create( **kwargs, bearer=account, ip=get_ip(request), @@ -588,6 +590,8 @@ async def new(cls, request: Request, account: Account = None, **kwargs): security_config.AUTHENTICATION_REFRESH_EXPIRATION ), ) + authentication_session.is_refresh = is_refresh + return authentication_session class Meta: table = "authentication_session" diff --git a/sanic_security/test/server.py b/sanic_security/test/server.py index 32053eb..cc3a006 100644 --- a/sanic_security/test/server.py +++ b/sanic_security/test/server.py @@ -167,7 +167,7 @@ async def on_authenticate(request): if not authentication_session.anonymous else None ), - "refresh": authentication_session.is_refresh + "refresh": authentication_session.is_refresh, }, ) if authentication_session.is_refresh: diff --git a/sanic_security/test/tests.py b/sanic_security/test/tests.py index 66eb5cd..d0474b3 100644 --- a/sanic_security/test/tests.py +++ b/sanic_security/test/tests.py @@ -558,8 +558,15 @@ def test_authentication_refresh(self): assert login_response.status_code == 200, login_response.text expire_response = self.client.post("http://127.0.0.1:8000/api/test/auth/expire") assert expire_response.status_code == 200, expire_response.text - authenticate_response = self.client.post( + authenticate_refresh_response = self.client.post( "http://127.0.0.1:8000/api/test/auth", ) - assert authenticate_response.status_code == 200, authenticate_response.text - + assert ( + json.loads(authenticate_refresh_response.text)["data"]["refresh"] is True + ), authenticate_refresh_response.text + authenticate_response = self.client.post( + "http://127.0.0.1:8000/api/test/auth", + ) # Since session refresh handling is complete, it will be returned as a regular session now. + assert ( + json.loads(authenticate_response.text)["data"]["refresh"] is False + ), authenticate_response.text diff --git a/sanic_security/utils.py b/sanic_security/utils.py index c8e08e4..037201f 100644 --- a/sanic_security/utils.py +++ b/sanic_security/utils.py @@ -52,7 +52,9 @@ def get_code() -> str: return "".join(random.choices(string.digits + string.ascii_uppercase, k=6)) -def json(message: str, data, status_code: int = 200) -> HTTPResponse: # May be causing fixture error bc of json property +def json( + message: str, data, status_code: int = 200 +) -> HTTPResponse: # May be causing fixture error bc of json property """ A preformatted Sanic json response. diff --git a/sanic_security/verification.py b/sanic_security/verification.py index ecc42e2..f74a21a 100644 --- a/sanic_security/verification.py +++ b/sanic_security/verification.py @@ -126,10 +126,7 @@ async def wrapper(request, *args, **kwargs): return wrapper - if callable(arg): - return decorator(arg) - else: - return decorator + return decorator(arg) if callable(arg) else decorator async def verify_account(request: Request) -> TwoStepSession: