sanic-security 1.14.1__tar.gz → 1.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sanic_security-1.14.1/sanic_security.egg-info → sanic_security-1.15.0}/PKG-INFO +10 -10
- {sanic_security-1.14.1 → sanic_security-1.15.0}/README.md +9 -8
- {sanic_security-1.14.1 → sanic_security-1.15.0}/pyproject.toml +1 -2
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/authentication.py +8 -11
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/authorization.py +2 -2
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/configuration.py +2 -2
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/exceptions.py +0 -9
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/models.py +27 -36
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/utils.py +35 -13
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/verification.py +1 -1
- {sanic_security-1.14.1 → sanic_security-1.15.0/sanic_security.egg-info}/PKG-INFO +10 -10
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security.egg-info/requires.txt +0 -1
- {sanic_security-1.14.1 → sanic_security-1.15.0}/LICENSE +0 -0
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/__init__.py +0 -0
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/test/__init__.py +0 -0
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/test/server.py +0 -0
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security/test/tests.py +0 -0
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security.egg-info/SOURCES.txt +0 -0
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security.egg-info/dependency_links.txt +0 -0
- {sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security.egg-info/top_level.txt +0 -0
- {sanic_security-1.14.1 → sanic_security-1.15.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sanic-security
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.15.0
|
4
4
|
Summary: An async security library for the Sanic framework.
|
5
5
|
Author-email: Aidan Stewart <me@na-stewart.com>
|
6
6
|
Project-URL: Documentation, https://security.na-stewart.com/
|
@@ -20,7 +20,6 @@ Requires-Dist: captcha>=0.4
|
|
20
20
|
Requires-Dist: pillow>=9.5.0
|
21
21
|
Requires-Dist: argon2-cffi>=20.1.0
|
22
22
|
Requires-Dist: sanic>=21.3.0
|
23
|
-
Requires-Dist: secure>=1.0.1
|
24
23
|
Provides-Extra: dev
|
25
24
|
Requires-Dist: httpx; extra == "dev"
|
26
25
|
Requires-Dist: black; extra == "dev"
|
@@ -149,11 +148,11 @@ You can load environment variables with a different prefix via `config.load_envi
|
|
149
148
|
| **SESSION_DOMAIN** | None | The Domain attribute of session cookies. |
|
150
149
|
| **SESSION_ENCODING_ALGORITHM** | HS256 | The algorithm used to encode and decode session JWT's. |
|
151
150
|
| **SESSION_PREFIX** | tkn | Prefix attached to the beginning of session cookies. |
|
152
|
-
| **MAX_CHALLENGE_ATTEMPTS** |
|
153
|
-
| **CAPTCHA_SESSION_EXPIRATION** |
|
151
|
+
| **MAX_CHALLENGE_ATTEMPTS** | 3 | The maximum amount of session challenge attempts allowed. |
|
152
|
+
| **CAPTCHA_SESSION_EXPIRATION** | 180 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
|
154
153
|
| **CAPTCHA_FONT** | captcha-font.ttf | The file path to the font being used for captcha generation. Several fonts can be used by separating them via comma. |
|
155
154
|
| **CAPTCHA_VOICE** | captcha-voice/ | The directory of the voice library being used for audio captcha generation. |
|
156
|
-
| **TWO_STEP_SESSION_EXPIRATION** |
|
155
|
+
| **TWO_STEP_SESSION_EXPIRATION** | 300 | The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration. |
|
157
156
|
| **AUTHENTICATION_SESSION_EXPIRATION** | 86400 | The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration. |
|
158
157
|
| **AUTHENTICATION_REFRESH_EXPIRATION** | 604800 | The amount of seconds till authentication refresh expiration. Setting to 0 will disable refresh mechanism. |
|
159
158
|
| **ALLOW_LOGIN_WITH_USERNAME** | False | Allows login via username; unique constraint is disabled when set to false. |
|
@@ -196,8 +195,7 @@ async def on_register(request):
|
|
196
195
|
account.email, two_step_session.code # Code = 24KF19
|
197
196
|
) # Custom method for emailing verification code.
|
198
197
|
response = json(
|
199
|
-
"Registration successful! Email verification required.",
|
200
|
-
two_step_session.json,
|
198
|
+
"Registration successful! Email verification required.", account.json
|
201
199
|
)
|
202
200
|
two_step_session.encode(response)
|
203
201
|
return response
|
@@ -215,7 +213,9 @@ Verifies the client's account via two-step session code.
|
|
215
213
|
@app.put("api/security/verify")
|
216
214
|
async def on_verify(request):
|
217
215
|
two_step_session = await verify_account(request)
|
218
|
-
return json(
|
216
|
+
return json(
|
217
|
+
"You have verified your account and may login!", two_step_session.bearer.json
|
218
|
+
)
|
219
219
|
```
|
220
220
|
|
221
221
|
* Login (With two-factor authentication)
|
@@ -237,7 +237,7 @@ async def on_login(request):
|
|
237
237
|
) # Custom method for emailing verification code.
|
238
238
|
response = json(
|
239
239
|
"Login successful! Two-factor authentication required.",
|
240
|
-
authentication_session.json,
|
240
|
+
authentication_session.bearer.json,
|
241
241
|
)
|
242
242
|
authentication_session.encode(response)
|
243
243
|
two_step_session.encode(response)
|
@@ -260,7 +260,7 @@ async def on_two_factor_authentication(request):
|
|
260
260
|
authentication_session = await fulfill_second_factor(request)
|
261
261
|
response = json(
|
262
262
|
"Authentication session second-factor fulfilled! You are now authenticated.",
|
263
|
-
authentication_session.json,
|
263
|
+
authentication_session.bearer.json,
|
264
264
|
)
|
265
265
|
return response
|
266
266
|
```
|
@@ -117,11 +117,11 @@ You can load environment variables with a different prefix via `config.load_envi
|
|
117
117
|
| **SESSION_DOMAIN** | None | The Domain attribute of session cookies. |
|
118
118
|
| **SESSION_ENCODING_ALGORITHM** | HS256 | The algorithm used to encode and decode session JWT's. |
|
119
119
|
| **SESSION_PREFIX** | tkn | Prefix attached to the beginning of session cookies. |
|
120
|
-
| **MAX_CHALLENGE_ATTEMPTS** |
|
121
|
-
| **CAPTCHA_SESSION_EXPIRATION** |
|
120
|
+
| **MAX_CHALLENGE_ATTEMPTS** | 3 | The maximum amount of session challenge attempts allowed. |
|
121
|
+
| **CAPTCHA_SESSION_EXPIRATION** | 180 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
|
122
122
|
| **CAPTCHA_FONT** | captcha-font.ttf | The file path to the font being used for captcha generation. Several fonts can be used by separating them via comma. |
|
123
123
|
| **CAPTCHA_VOICE** | captcha-voice/ | The directory of the voice library being used for audio captcha generation. |
|
124
|
-
| **TWO_STEP_SESSION_EXPIRATION** |
|
124
|
+
| **TWO_STEP_SESSION_EXPIRATION** | 300 | The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration. |
|
125
125
|
| **AUTHENTICATION_SESSION_EXPIRATION** | 86400 | The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration. |
|
126
126
|
| **AUTHENTICATION_REFRESH_EXPIRATION** | 604800 | The amount of seconds till authentication refresh expiration. Setting to 0 will disable refresh mechanism. |
|
127
127
|
| **ALLOW_LOGIN_WITH_USERNAME** | False | Allows login via username; unique constraint is disabled when set to false. |
|
@@ -164,8 +164,7 @@ async def on_register(request):
|
|
164
164
|
account.email, two_step_session.code # Code = 24KF19
|
165
165
|
) # Custom method for emailing verification code.
|
166
166
|
response = json(
|
167
|
-
"Registration successful! Email verification required.",
|
168
|
-
two_step_session.json,
|
167
|
+
"Registration successful! Email verification required.", account.json
|
169
168
|
)
|
170
169
|
two_step_session.encode(response)
|
171
170
|
return response
|
@@ -183,7 +182,9 @@ Verifies the client's account via two-step session code.
|
|
183
182
|
@app.put("api/security/verify")
|
184
183
|
async def on_verify(request):
|
185
184
|
two_step_session = await verify_account(request)
|
186
|
-
return json(
|
185
|
+
return json(
|
186
|
+
"You have verified your account and may login!", two_step_session.bearer.json
|
187
|
+
)
|
187
188
|
```
|
188
189
|
|
189
190
|
* Login (With two-factor authentication)
|
@@ -205,7 +206,7 @@ async def on_login(request):
|
|
205
206
|
) # Custom method for emailing verification code.
|
206
207
|
response = json(
|
207
208
|
"Login successful! Two-factor authentication required.",
|
208
|
-
authentication_session.json,
|
209
|
+
authentication_session.bearer.json,
|
209
210
|
)
|
210
211
|
authentication_session.encode(response)
|
211
212
|
two_step_session.encode(response)
|
@@ -228,7 +229,7 @@ async def on_two_factor_authentication(request):
|
|
228
229
|
authentication_session = await fulfill_second_factor(request)
|
229
230
|
response = json(
|
230
231
|
"Authentication session second-factor fulfilled! You are now authenticated.",
|
231
|
-
authentication_session.json,
|
232
|
+
authentication_session.bearer.json,
|
232
233
|
)
|
233
234
|
return response
|
234
235
|
```
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "sanic-security"
|
7
|
-
version = "1.
|
7
|
+
version = "1.15.0"
|
8
8
|
requires-python = ">=3.8"
|
9
9
|
dependencies = [
|
10
10
|
"tortoise-orm>=0.17.0",
|
@@ -13,7 +13,6 @@ dependencies = [
|
|
13
13
|
"pillow>=9.5.0",
|
14
14
|
"argon2-cffi>=20.1.0",
|
15
15
|
"sanic>=21.3.0",
|
16
|
-
"secure>=1.0.1"
|
17
16
|
]
|
18
17
|
description = "An async security library for the Sanic framework."
|
19
18
|
authors = [
|
@@ -17,7 +17,7 @@ from sanic_security.exceptions import (
|
|
17
17
|
AuditWarning,
|
18
18
|
)
|
19
19
|
from sanic_security.models import Account, AuthenticationSession, Role, TwoStepSession
|
20
|
-
from sanic_security.utils import get_ip, password_hasher
|
20
|
+
from sanic_security.utils import get_ip, password_hasher
|
21
21
|
|
22
22
|
"""
|
23
23
|
Copyright (c) 2020-present Nicholas Aidan Stewart
|
@@ -183,7 +183,7 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession:
|
|
183
183
|
"""
|
184
184
|
authentication_session = await AuthenticationSession.decode(request)
|
185
185
|
if not authentication_session.requires_second_factor:
|
186
|
-
raise SecondFactorFulfilledError
|
186
|
+
raise SecondFactorFulfilledError
|
187
187
|
two_step_session = await TwoStepSession.decode(request)
|
188
188
|
two_step_session.validate()
|
189
189
|
await two_step_session.check_code(request.form.get("code"))
|
@@ -284,7 +284,7 @@ def validate_password(password: str) -> str:
|
|
284
284
|
|
285
285
|
def initialize_security(app: Sanic, create_root=True) -> None:
|
286
286
|
"""
|
287
|
-
Audits configuration, creates root administrator account, and attaches
|
287
|
+
Audits configuration, creates root administrator account, and attaches refresh encoder middleware.
|
288
288
|
|
289
289
|
Args:
|
290
290
|
app (Sanic): The main Sanic application instance.
|
@@ -356,11 +356,8 @@ def initialize_security(app: Sanic, create_root=True) -> None:
|
|
356
356
|
logger.info("Initial admin account created.")
|
357
357
|
|
358
358
|
@app.on_response
|
359
|
-
async def
|
360
|
-
if hasattr(request.ctx, "session")
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
and request.ctx.session.is_refresh
|
365
|
-
):
|
366
|
-
request.ctx.session.encode(response)
|
359
|
+
async def refresh_encoder_middleware(request, response):
|
360
|
+
if hasattr(request.ctx, "session") and getattr(
|
361
|
+
request.ctx.session, "is_refresh", False
|
362
|
+
):
|
363
|
+
request.ctx.session.encode(response)
|
@@ -62,7 +62,7 @@ async def check_permissions(
|
|
62
62
|
logger.warning(
|
63
63
|
f"Client {get_ip(request)} attempted an unauthorized action anonymously."
|
64
64
|
)
|
65
|
-
raise AnonymousError
|
65
|
+
raise AnonymousError
|
66
66
|
roles = await authentication_session.bearer.roles.filter(deleted=False).all()
|
67
67
|
for role in roles:
|
68
68
|
for required_permission, role_permission in zip(
|
@@ -104,7 +104,7 @@ async def check_roles(request: Request, *required_roles: str) -> AuthenticationS
|
|
104
104
|
logger.warning(
|
105
105
|
f"Client {get_ip(request)} attempted an unauthorized action anonymously."
|
106
106
|
)
|
107
|
-
raise AnonymousError
|
107
|
+
raise AnonymousError
|
108
108
|
roles = await authentication_session.bearer.roles.filter(deleted=False).all()
|
109
109
|
for role in roles:
|
110
110
|
if role.name in required_roles:
|
@@ -33,8 +33,8 @@ DEFAULT_CONFIG = {
|
|
33
33
|
"SESSION_DOMAIN": None,
|
34
34
|
"SESSION_PREFIX": "tkn",
|
35
35
|
"SESSION_ENCODING_ALGORITHM": "HS256",
|
36
|
-
"MAX_CHALLENGE_ATTEMPTS":
|
37
|
-
"CAPTCHA_SESSION_EXPIRATION":
|
36
|
+
"MAX_CHALLENGE_ATTEMPTS": 3,
|
37
|
+
"CAPTCHA_SESSION_EXPIRATION": 180,
|
38
38
|
"CAPTCHA_FONT": "captcha-font.ttf",
|
39
39
|
"CAPTCHA_VOICE": "captcha-voice/",
|
40
40
|
"TWO_STEP_SESSION_EXPIRATION": 300,
|
@@ -145,15 +145,6 @@ class ExpiredError(SessionError):
|
|
145
145
|
super().__init__("Session has expired.")
|
146
146
|
|
147
147
|
|
148
|
-
class NotExpiredError(SessionError):
|
149
|
-
"""
|
150
|
-
Raised when session needs to be expired.
|
151
|
-
"""
|
152
|
-
|
153
|
-
def __init__(self):
|
154
|
-
super().__init__("Session has not expired yet.", 403)
|
155
|
-
|
156
|
-
|
157
148
|
class SecondFactorRequiredError(SessionError):
|
158
149
|
"""
|
159
150
|
Raised when authentication session two-factor requirement isn't met.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import base64
|
2
2
|
import datetime
|
3
|
+
import logging
|
3
4
|
import re
|
4
5
|
from typing import Union
|
5
6
|
|
@@ -19,6 +20,8 @@ from sanic_security.utils import (
|
|
19
20
|
get_expiration_date,
|
20
21
|
image_generator,
|
21
22
|
audio_generator,
|
23
|
+
get_id,
|
24
|
+
is_expired,
|
22
25
|
)
|
23
26
|
|
24
27
|
"""
|
@@ -55,7 +58,7 @@ class BaseModel(Model):
|
|
55
58
|
deleted (bool): Renders the model filterable without removing from the database.
|
56
59
|
"""
|
57
60
|
|
58
|
-
id:
|
61
|
+
id: str = fields.CharField(pk=True, max_length=36, default=get_id)
|
59
62
|
date_created: datetime.datetime = fields.DatetimeField(auto_now_add=True)
|
60
63
|
date_updated: datetime.datetime = fields.DatetimeField(auto_now=True)
|
61
64
|
deleted: bool = fields.BooleanField(default=False)
|
@@ -67,7 +70,7 @@ class BaseModel(Model):
|
|
67
70
|
Raises:
|
68
71
|
SecurityError
|
69
72
|
"""
|
70
|
-
raise NotImplementedError
|
73
|
+
raise NotImplementedError
|
71
74
|
|
72
75
|
@property
|
73
76
|
def json(self) -> dict:
|
@@ -89,7 +92,7 @@ class BaseModel(Model):
|
|
89
92
|
}
|
90
93
|
|
91
94
|
"""
|
92
|
-
raise NotImplementedError
|
95
|
+
raise NotImplementedError
|
93
96
|
|
94
97
|
class Meta:
|
95
98
|
abstract = True
|
@@ -148,9 +151,9 @@ class Account(BaseModel):
|
|
148
151
|
if self.deleted:
|
149
152
|
raise DeletedError("Account has been deleted.")
|
150
153
|
elif not self.verified:
|
151
|
-
raise UnverifiedError
|
154
|
+
raise UnverifiedError
|
152
155
|
elif self.disabled:
|
153
|
-
raise DisabledError
|
156
|
+
raise DisabledError
|
154
157
|
|
155
158
|
async def disable(self):
|
156
159
|
"""
|
@@ -321,12 +324,9 @@ class Session(BaseModel):
|
|
321
324
|
if self.deleted:
|
322
325
|
raise DeletedError("Session has been deleted.")
|
323
326
|
elif not self.active:
|
324
|
-
raise DeactivatedError
|
325
|
-
elif (
|
326
|
-
|
327
|
-
and datetime.datetime.now(datetime.timezone.utc) >= self.expiration_date
|
328
|
-
):
|
329
|
-
raise ExpiredError()
|
327
|
+
raise DeactivatedError
|
328
|
+
elif is_expired(self.expiration_date):
|
329
|
+
raise ExpiredError
|
330
330
|
|
331
331
|
async def deactivate(self):
|
332
332
|
"""
|
@@ -416,7 +416,7 @@ class Session(BaseModel):
|
|
416
416
|
Returns:
|
417
417
|
session
|
418
418
|
"""
|
419
|
-
raise NotImplementedError
|
419
|
+
raise NotImplementedError
|
420
420
|
|
421
421
|
@classmethod
|
422
422
|
async def get_associated(cls, account: Account):
|
@@ -517,15 +517,15 @@ class VerificationSession(Session):
|
|
517
517
|
ChallengeError
|
518
518
|
MaxedOutChallengeError
|
519
519
|
"""
|
520
|
-
if self.code != code.upper():
|
520
|
+
if not code or self.code != code.upper():
|
521
|
+
self.attempts += 1
|
521
522
|
if self.attempts < security_config.MAX_CHALLENGE_ATTEMPTS:
|
522
|
-
self.attempts += 1
|
523
523
|
await self.save(update_fields=["attempts"])
|
524
524
|
raise ChallengeError(
|
525
525
|
"Your code does not match verification session code."
|
526
526
|
)
|
527
527
|
else:
|
528
|
-
raise MaxedOutChallengeError
|
528
|
+
raise MaxedOutChallengeError
|
529
529
|
else:
|
530
530
|
await self.deactivate()
|
531
531
|
|
@@ -621,39 +621,30 @@ class AuthenticationSession(Session):
|
|
621
621
|
"""
|
622
622
|
super().validate()
|
623
623
|
if self.requires_second_factor:
|
624
|
-
raise SecondFactorRequiredError
|
624
|
+
raise SecondFactorRequiredError
|
625
625
|
|
626
626
|
async def refresh(self, request: Request):
|
627
627
|
"""
|
628
|
-
Refreshes session if
|
628
|
+
Refreshes session if within refresh date.
|
629
629
|
|
630
630
|
Args:
|
631
631
|
request (Request): Sanic request parameter.
|
632
632
|
|
633
633
|
Raises:
|
634
|
-
DeletedError
|
635
634
|
ExpiredError
|
636
|
-
DeactivatedError
|
637
|
-
SecondFactorRequiredError
|
638
|
-
NotExpiredError
|
639
635
|
|
640
636
|
Returns:
|
641
637
|
session
|
642
638
|
"""
|
643
|
-
|
644
|
-
self.
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
self.active = False
|
653
|
-
await self.save(update_fields=["active"])
|
654
|
-
return await self.new(request, self.bearer, True)
|
655
|
-
else:
|
656
|
-
raise e
|
639
|
+
if not is_expired(self.refresh_expiration_date):
|
640
|
+
self.active = False
|
641
|
+
await self.save(update_fields=["active"])
|
642
|
+
logging.warning(
|
643
|
+
f"Client {get_ip(request)} has refreshed authentication session {self.id}."
|
644
|
+
)
|
645
|
+
return await self.new(request, self.bearer, True)
|
646
|
+
else:
|
647
|
+
raise ExpiredError
|
657
648
|
|
658
649
|
@classmethod
|
659
650
|
async def new(
|
@@ -692,7 +683,7 @@ class Role(BaseModel):
|
|
692
683
|
permissions: str = fields.CharField(max_length=255, null=True)
|
693
684
|
|
694
685
|
def validate(self) -> None:
|
695
|
-
raise NotImplementedError
|
686
|
+
raise NotImplementedError
|
696
687
|
|
697
688
|
@property
|
698
689
|
def json(self) -> dict:
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import datetime
|
2
2
|
import random
|
3
3
|
import string
|
4
|
+
import uuid
|
4
5
|
|
5
6
|
from argon2 import PasswordHasher
|
6
7
|
from captcha.audio import AudioCaptcha
|
7
8
|
from captcha.image import ImageCaptcha
|
8
9
|
from sanic.request import Request
|
9
10
|
from sanic.response import json as sanic_json, HTTPResponse
|
10
|
-
from secure import Secure
|
11
11
|
|
12
12
|
from sanic_security.configuration import config
|
13
13
|
|
@@ -38,7 +38,6 @@ image_generator = ImageCaptcha(
|
|
38
38
|
)
|
39
39
|
audio_generator = AudioCaptcha(voicedir=config.CAPTCHA_VOICE)
|
40
40
|
password_hasher = PasswordHasher()
|
41
|
-
secure_headers = Secure.with_default_headers()
|
42
41
|
|
43
42
|
|
44
43
|
def get_ip(request: Request) -> str:
|
@@ -66,23 +65,27 @@ def get_code() -> str:
|
|
66
65
|
)
|
67
66
|
|
68
67
|
|
69
|
-
def
|
70
|
-
message: str, data, status_code: int = 200
|
71
|
-
) -> HTTPResponse: # May be causing fixture error bc of json property
|
68
|
+
def get_id() -> str:
|
72
69
|
"""
|
73
|
-
|
70
|
+
Generates uuid to be used for primary key.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
id
|
74
|
+
"""
|
75
|
+
return str(uuid.uuid4())
|
76
|
+
|
77
|
+
|
78
|
+
def is_expired(date):
|
79
|
+
"""
|
80
|
+
Checks if current date has surpassed the date passed into the function.
|
74
81
|
|
75
82
|
Args:
|
76
|
-
|
77
|
-
data (Any): Raw information to be used by client.
|
78
|
-
status_code (int): HTTP response code.
|
83
|
+
date: The date being checked for expiration.
|
79
84
|
|
80
85
|
Returns:
|
81
|
-
|
86
|
+
is_expired
|
82
87
|
"""
|
83
|
-
return
|
84
|
-
{"message": message, "code": status_code, "data": data}, status=status_code
|
85
|
-
)
|
88
|
+
return date and datetime.datetime.now(datetime.timezone.utc) >= date
|
86
89
|
|
87
90
|
|
88
91
|
def get_expiration_date(seconds: int) -> datetime.datetime:
|
@@ -100,3 +103,22 @@ def get_expiration_date(seconds: int) -> datetime.datetime:
|
|
100
103
|
if seconds > 0
|
101
104
|
else None
|
102
105
|
)
|
106
|
+
|
107
|
+
|
108
|
+
def json(
|
109
|
+
message: str, data, status_code: int = 200
|
110
|
+
) -> HTTPResponse: # May be causing fixture error bc of json property
|
111
|
+
"""
|
112
|
+
A preformatted Sanic json response.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
message (int): Message describing data or relaying human-readable information.
|
116
|
+
data (Any): Raw information to be used by client.
|
117
|
+
status_code (int): HTTP response code.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
json
|
121
|
+
"""
|
122
|
+
return sanic_json(
|
123
|
+
{"message": message, "code": status_code, "data": data}, status=status_code
|
124
|
+
)
|
@@ -164,7 +164,7 @@ async def verify_account(request: Request) -> TwoStepSession:
|
|
164
164
|
"""
|
165
165
|
two_step_session = await TwoStepSession.decode(request)
|
166
166
|
if two_step_session.bearer.verified:
|
167
|
-
raise VerifiedError
|
167
|
+
raise VerifiedError
|
168
168
|
two_step_session.validate()
|
169
169
|
try:
|
170
170
|
await two_step_session.check_code(request.form.get("code"))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sanic-security
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.15.0
|
4
4
|
Summary: An async security library for the Sanic framework.
|
5
5
|
Author-email: Aidan Stewart <me@na-stewart.com>
|
6
6
|
Project-URL: Documentation, https://security.na-stewart.com/
|
@@ -20,7 +20,6 @@ Requires-Dist: captcha>=0.4
|
|
20
20
|
Requires-Dist: pillow>=9.5.0
|
21
21
|
Requires-Dist: argon2-cffi>=20.1.0
|
22
22
|
Requires-Dist: sanic>=21.3.0
|
23
|
-
Requires-Dist: secure>=1.0.1
|
24
23
|
Provides-Extra: dev
|
25
24
|
Requires-Dist: httpx; extra == "dev"
|
26
25
|
Requires-Dist: black; extra == "dev"
|
@@ -149,11 +148,11 @@ You can load environment variables with a different prefix via `config.load_envi
|
|
149
148
|
| **SESSION_DOMAIN** | None | The Domain attribute of session cookies. |
|
150
149
|
| **SESSION_ENCODING_ALGORITHM** | HS256 | The algorithm used to encode and decode session JWT's. |
|
151
150
|
| **SESSION_PREFIX** | tkn | Prefix attached to the beginning of session cookies. |
|
152
|
-
| **MAX_CHALLENGE_ATTEMPTS** |
|
153
|
-
| **CAPTCHA_SESSION_EXPIRATION** |
|
151
|
+
| **MAX_CHALLENGE_ATTEMPTS** | 3 | The maximum amount of session challenge attempts allowed. |
|
152
|
+
| **CAPTCHA_SESSION_EXPIRATION** | 180 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
|
154
153
|
| **CAPTCHA_FONT** | captcha-font.ttf | The file path to the font being used for captcha generation. Several fonts can be used by separating them via comma. |
|
155
154
|
| **CAPTCHA_VOICE** | captcha-voice/ | The directory of the voice library being used for audio captcha generation. |
|
156
|
-
| **TWO_STEP_SESSION_EXPIRATION** |
|
155
|
+
| **TWO_STEP_SESSION_EXPIRATION** | 300 | The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration. |
|
157
156
|
| **AUTHENTICATION_SESSION_EXPIRATION** | 86400 | The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration. |
|
158
157
|
| **AUTHENTICATION_REFRESH_EXPIRATION** | 604800 | The amount of seconds till authentication refresh expiration. Setting to 0 will disable refresh mechanism. |
|
159
158
|
| **ALLOW_LOGIN_WITH_USERNAME** | False | Allows login via username; unique constraint is disabled when set to false. |
|
@@ -196,8 +195,7 @@ async def on_register(request):
|
|
196
195
|
account.email, two_step_session.code # Code = 24KF19
|
197
196
|
) # Custom method for emailing verification code.
|
198
197
|
response = json(
|
199
|
-
"Registration successful! Email verification required.",
|
200
|
-
two_step_session.json,
|
198
|
+
"Registration successful! Email verification required.", account.json
|
201
199
|
)
|
202
200
|
two_step_session.encode(response)
|
203
201
|
return response
|
@@ -215,7 +213,9 @@ Verifies the client's account via two-step session code.
|
|
215
213
|
@app.put("api/security/verify")
|
216
214
|
async def on_verify(request):
|
217
215
|
two_step_session = await verify_account(request)
|
218
|
-
return json(
|
216
|
+
return json(
|
217
|
+
"You have verified your account and may login!", two_step_session.bearer.json
|
218
|
+
)
|
219
219
|
```
|
220
220
|
|
221
221
|
* Login (With two-factor authentication)
|
@@ -237,7 +237,7 @@ async def on_login(request):
|
|
237
237
|
) # Custom method for emailing verification code.
|
238
238
|
response = json(
|
239
239
|
"Login successful! Two-factor authentication required.",
|
240
|
-
authentication_session.json,
|
240
|
+
authentication_session.bearer.json,
|
241
241
|
)
|
242
242
|
authentication_session.encode(response)
|
243
243
|
two_step_session.encode(response)
|
@@ -260,7 +260,7 @@ async def on_two_factor_authentication(request):
|
|
260
260
|
authentication_session = await fulfill_second_factor(request)
|
261
261
|
response = json(
|
262
262
|
"Authentication session second-factor fulfilled! You are now authenticated.",
|
263
|
-
authentication_session.json,
|
263
|
+
authentication_session.bearer.json,
|
264
264
|
)
|
265
265
|
return response
|
266
266
|
```
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{sanic_security-1.14.1 → sanic_security-1.15.0}/sanic_security.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|