sanic-security 1.14.0__py3-none-any.whl → 1.15.0__py3-none-any.whl
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/authentication.py +10 -10
- sanic_security/authorization.py +2 -2
- sanic_security/configuration.py +2 -2
- sanic_security/exceptions.py +0 -9
- sanic_security/models.py +27 -36
- sanic_security/test/server.py +0 -1
- sanic_security/utils.py +35 -13
- sanic_security/verification.py +1 -1
- {sanic_security-1.14.0.dist-info → sanic_security-1.15.0.dist-info}/METADATA +14 -15
- sanic_security-1.15.0.dist-info/RECORD +16 -0
- {sanic_security-1.14.0.dist-info → sanic_security-1.15.0.dist-info}/WHEEL +1 -1
- sanic_security-1.14.0.dist-info/RECORD +0 -16
- {sanic_security-1.14.0.dist-info → sanic_security-1.15.0.dist-info}/LICENSE +0 -0
- {sanic_security-1.14.0.dist-info → sanic_security-1.15.0.dist-info}/top_level.txt +0 -0
sanic_security/authentication.py
CHANGED
@@ -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,20 +284,13 @@ 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.
|
291
291
|
create_root (bool): Determines root account creation on initialization.
|
292
292
|
"""
|
293
293
|
|
294
|
-
@app.on_response
|
295
|
-
async def response_handler_middleware(request, response):
|
296
|
-
if hasattr(request.ctx, "session"):
|
297
|
-
secure_headers.set_headers(response)
|
298
|
-
if request.ctx.session.is_refresh:
|
299
|
-
request.ctx.session.encode(response)
|
300
|
-
|
301
294
|
@app.listener("before_server_start")
|
302
295
|
async def audit_configuration(app, loop):
|
303
296
|
if security_config.SECRET == DEFAULT_CONFIG["SECRET"]:
|
@@ -361,3 +354,10 @@ def initialize_security(app: Sanic, create_root=True) -> None:
|
|
361
354
|
)
|
362
355
|
await account.roles.add(role)
|
363
356
|
logger.info("Initial admin account created.")
|
357
|
+
|
358
|
+
@app.on_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)
|
sanic_security/authorization.py
CHANGED
@@ -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:
|
sanic_security/configuration.py
CHANGED
@@ -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,
|
sanic_security/exceptions.py
CHANGED
@@ -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.
|
sanic_security/models.py
CHANGED
@@ -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:
|
sanic_security/test/server.py
CHANGED
sanic_security/utils.py
CHANGED
@@ -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
|
+
)
|
sanic_security/verification.py
CHANGED
@@ -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,15 +20,14 @@ 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
|
-
Provides-Extra: crypto
|
25
|
-
Requires-Dist: cryptography>=3.3.1; extra == "crypto"
|
26
23
|
Provides-Extra: dev
|
27
24
|
Requires-Dist: httpx; extra == "dev"
|
28
25
|
Requires-Dist: black; extra == "dev"
|
29
26
|
Requires-Dist: blacken-docs; extra == "dev"
|
30
27
|
Requires-Dist: pdoc3; extra == "dev"
|
31
28
|
Requires-Dist: cryptography; extra == "dev"
|
29
|
+
Provides-Extra: crypto
|
30
|
+
Requires-Dist: cryptography>=3.3.1; extra == "crypto"
|
32
31
|
|
33
32
|
<!-- PROJECT SHIELDS -->
|
34
33
|
<!--
|
@@ -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
|
@@ -212,10 +210,12 @@ Verifies the client's account via two-step session code.
|
|
212
210
|
| **code** | 24KF19 |
|
213
211
|
|
214
212
|
```python
|
215
|
-
@app.
|
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)
|
@@ -255,14 +255,13 @@ Fulfills client authentication session's second factor requirement via two-step
|
|
255
255
|
| **code** | XGED2U |
|
256
256
|
|
257
257
|
```python
|
258
|
-
@app.
|
258
|
+
@app.put("api/security/fulfill-2fa")
|
259
259
|
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
|
-
authentication_session.encode(response)
|
266
265
|
return response
|
267
266
|
```
|
268
267
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
sanic_security/authentication.py,sha256=3Pit1B4PN_MRvwAlhYPHl_6DGD9JVpdPjgjiyKQJanM,13145
|
3
|
+
sanic_security/authorization.py,sha256=jAxfDT9cHN_zpMKcA3oYFZ5Eu2KItnMJZ7oPcqmMwrw,7537
|
4
|
+
sanic_security/configuration.py,sha256=_E66ts5g9t_XHW9ZAnr48rWVcZmGNu_DWGDxm_AVVWE,5681
|
5
|
+
sanic_security/exceptions.py,sha256=9zISLyAvP6qN8sNR8e5qxKP__FA4NLIXCun_fEKndOw,5297
|
6
|
+
sanic_security/models.py,sha256=v4ZnvZonwrgxnXKPHLQYNtEDJAE2Ri0TGEAbLX0VWTo,22429
|
7
|
+
sanic_security/utils.py,sha256=Y_qs3UVuOXHnDWelAxNC6VLeB5lMeSv3xRty9xYlHOw,3538
|
8
|
+
sanic_security/verification.py,sha256=9bi8-NZ8GE3rcuELZ63yh18zDg8RxvxGPkhAu5SzLn0,8692
|
9
|
+
sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
sanic_security/test/server.py,sha256=RjL9Kfvkfqpm5TXWwFQKKa0J4hfTKgwI6U0s_TAKO8w,11984
|
11
|
+
sanic_security/test/tests.py,sha256=bW5fHJfsCrg8eBmcSqVMLm0R5XRL1ou-XJJRgz09GOE,21993
|
12
|
+
sanic_security-1.15.0.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
|
13
|
+
sanic_security-1.15.0.dist-info/METADATA,sha256=7bxyaqAFp0hm6ldrbuux1Dwth8OtXVcffKelbV8zkl8,23247
|
14
|
+
sanic_security-1.15.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
15
|
+
sanic_security-1.15.0.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
|
16
|
+
sanic_security-1.15.0.dist-info/RECORD,,
|
@@ -1,16 +0,0 @@
|
|
1
|
-
sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
sanic_security/authentication.py,sha256=dq7Yt_1xm9_LSZgMZkyzgcvG46NUQsysEb3s5pgO7BE,13165
|
3
|
-
sanic_security/authorization.py,sha256=QvLsaOWu3te0Y75tqChrEXQP5CR92nsRU7LXiUWy5cw,7541
|
4
|
-
sanic_security/configuration.py,sha256=h-Kh4PalJpjbDcZvVHCzxX5l-GnldP3Fr8OlgGCZNHY,5680
|
5
|
-
sanic_security/exceptions.py,sha256=JiCaBR2kjE1Cj0fc_08y-32fqJJXa_1UCw205T4_RTY,5493
|
6
|
-
sanic_security/models.py,sha256=bK5daR6Iq7V7aqNSzksH6DGrCXMj2e4feNRhlxlFQMg,22722
|
7
|
-
sanic_security/utils.py,sha256=tgewsCAkNl_NkobHaDlZNIgVopQPiD8SWb6UC6tBYNs,3151
|
8
|
-
sanic_security/verification.py,sha256=olmpP2AwXKILRVRnDf7AMRJuK5Fs_i5ESHXSH94A-Yk,8694
|
9
|
-
sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
sanic_security/test/server.py,sha256=hLvT2mG1VXeV7nE6r1avoSOTa1qMSS6jL0qTizyCdOY,12029
|
11
|
-
sanic_security/test/tests.py,sha256=bW5fHJfsCrg8eBmcSqVMLm0R5XRL1ou-XJJRgz09GOE,21993
|
12
|
-
sanic_security-1.14.0.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
|
13
|
-
sanic_security-1.14.0.dist-info/METADATA,sha256=ynu6jYXbs9s7ZQi8l2Z2Ilm93Yu8JxQukx-swbOcQIc,23306
|
14
|
-
sanic_security-1.14.0.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
15
|
-
sanic_security-1.14.0.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
|
16
|
-
sanic_security-1.14.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|