Skip to content

Commit

Permalink
Anonymous login tests
Browse files Browse the repository at this point in the history
  • Loading branch information
na-stewart committed Jun 21, 2024
1 parent a17ffb0 commit 361a425
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 42 deletions.
78 changes: 39 additions & 39 deletions sanic_security/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -164,6 +164,37 @@ async def logout(request: Request) -> AuthenticationSession:
return authentication_session


async def fulfill_second_factor(request: Request) -> AuthenticationSession:
"""
Fulfills client authentication session's second factor requirement via two-step session code.
Args:
request (Request): Sanic request parameter. Request body should contain form-data with the following argument(s): code.
Raises:
NotFoundError
JWTDecodeError
DeletedError
ExpiredError
DeactivatedError
ChallengeError
MaxedOutChallengeError
SecondFactorFulfilledError
Returns:
authentication_Session
"""
authentication_session = await AuthenticationSession.decode(request)
if not authentication_session.requires_second_factor:
raise SecondFactorFulfilledError()
two_step_session = await TwoStepSession.decode(request)
two_step_session.validate()
await two_step_session.check_code(request, request.form.get("code"))
authentication_session.requires_second_factor = False
await authentication_session.save(update_fields=["requires_second_factor"])
return authentication_session


async def authenticate(request: Request) -> AuthenticationSession:
"""
Validates client's authentication session and account.
Expand Down Expand Up @@ -198,37 +229,6 @@ async def authenticate(request: Request) -> AuthenticationSession:
return authentication_session


async def fulfill_second_factor(request: Request) -> AuthenticationSession:
"""
Fulfills client authentication session's second factor requirement via two-step session code.
Args:
request (Request): Sanic request parameter. Request body should contain form-data with the following argument(s): code.
Raises:
NotFoundError
JWTDecodeError
DeletedError
ExpiredError
DeactivatedError
ChallengeError
MaxedOutChallengeError
SecondFactorFulfilledError
Returns:
authentication_Session
"""
authentication_session = await AuthenticationSession.decode(request)
if not authentication_session.requires_second_factor:
raise SecondFactorFulfilledError()
two_step_session = await TwoStepSession.decode(request)
two_step_session.validate()
await two_step_session.check_code(request, request.form.get("code"))
authentication_session.requires_second_factor = False
await authentication_session.save(update_fields=["requires_second_factor"])
return authentication_session


def requires_authentication(arg=None):
"""
Validates client's authentication session and account.
Expand Down Expand Up @@ -357,7 +357,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
Expand Down
2 changes: 1 addition & 1 deletion sanic_security/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ async def refresh(self, request: Request):
raise e

@classmethod
async def new(cls, request: Request, account: Account, **kwargs):
async def new(cls, request: Request, account: Account = None, **kwargs):
return await AuthenticationSession.create(
**kwargs,
bearer=account,
Expand Down
28 changes: 26 additions & 2 deletions sanic_security/test/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ async def on_login(request):
return response


@app.post("api/test/auth/login/anon")
async def on_login_anonymous(request):
"""
Login as anonymous user.
"""
authentication_session = await AuthenticationSession.new(request)
response = json(
"Anonymous user now associated with session!", authentication_session.json
)
authentication_session.encode(response)
return response


@app.post("api/test/auth/validate-2fa")
async def on_two_factor_authentication(request):
"""
Expand Down Expand Up @@ -142,9 +155,20 @@ async def on_logout(request):
@requires_authentication()
async def on_authenticate(request):
"""
Authenticate client session and account.
Authenticate client session and account, encode refreshed session if necessary.
"""
response = json("Authenticated!", request.ctx.authentication_session.bearer.json)
authentication_session = request.ctx.authentication_session
response = json(
"Authenticated!",
{
"bearer": (
authentication_session.bearer.json
if not authentication_session.anonymous
else None
),
"auto-refreshed": authentication_session.refreshed,
},
)
request.ctx.authentication_session.encode(response)
return response

Expand Down
27 changes: 27 additions & 0 deletions sanic_security/test/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ def test_two_factor_login(self):
)
assert authenticate_response.status_code == 200, authenticate_response.text

def test_anonymous_login(self):
anon_login_response = self.client.post(
"http://127.0.0.1:8000/api/test/auth/login/anon"
)
assert anon_login_response.status_code == 200, anon_login_response.text
authenticate_response = self.client.post(
"http://127.0.0.1:8000/api/test/auth",
)
assert authenticate_response.status_code == 200, authenticate_response.text


class VerificationTest(TestCase):
"""
Expand Down Expand Up @@ -472,6 +482,23 @@ def test_roles_authorization(self):
prohibited_authorization_response.status_code == 403
), prohibited_authorization_response.text

def test_anonymous_authorization(self):
anon_login_response = self.client.post(
"http://127.0.0.1:8000/api/test/auth/login/anon"
)
assert anon_login_response.status_code == 200, anon_login_response.text
authenticate_response = self.client.post(
"http://127.0.0.1:8000/api/test/auth",
)
assert authenticate_response.status_code == 200, authenticate_response.text
prohibited_authorization_response = self.client.post(
"http://127.0.0.1:8000/api/test/auth/roles",
data={"role": "AuthTestPerms"},
)
assert (
prohibited_authorization_response.status_code == 403
), prohibited_authorization_response.text


class MiscTest(TestCase):
"""
Expand Down

0 comments on commit 361a425

Please sign in to comment.