sanic-security 1.14.1__py3-none-any.whl → 1.15.1__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 +8 -11
- sanic_security/authorization.py +2 -2
- sanic_security/configuration.py +2 -2
- sanic_security/exceptions.py +0 -9
- sanic_security/models.py +36 -39
- sanic_security/utils.py +31 -16
- sanic_security/verification.py +1 -1
- {sanic_security-1.14.1.dist-info → sanic_security-1.15.1.dist-info}/METADATA +10 -10
- sanic_security-1.15.1.dist-info/RECORD +16 -0
- sanic_security-1.14.1.dist-info/RECORD +0 -16
- {sanic_security-1.14.1.dist-info → sanic_security-1.15.1.dist-info}/LICENSE +0 -0
- {sanic_security-1.14.1.dist-info → sanic_security-1.15.1.dist-info}/WHEEL +0 -0
- {sanic_security-1.14.1.dist-info → sanic_security-1.15.1.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,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)
|
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,6 +1,8 @@
|
|
1
1
|
import base64
|
2
2
|
import datetime
|
3
|
+
import logging
|
3
4
|
import re
|
5
|
+
import uuid
|
4
6
|
from typing import Union
|
5
7
|
|
6
8
|
import jwt
|
@@ -19,6 +21,7 @@ from sanic_security.utils import (
|
|
19
21
|
get_expiration_date,
|
20
22
|
image_generator,
|
21
23
|
audio_generator,
|
24
|
+
is_expired,
|
22
25
|
)
|
23
26
|
|
24
27
|
"""
|
@@ -55,7 +58,9 @@ 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(
|
62
|
+
pk=True, max_length=36, default=lambda: str(uuid.uuid4())
|
63
|
+
)
|
59
64
|
date_created: datetime.datetime = fields.DatetimeField(auto_now_add=True)
|
60
65
|
date_updated: datetime.datetime = fields.DatetimeField(auto_now=True)
|
61
66
|
deleted: bool = fields.BooleanField(default=False)
|
@@ -67,7 +72,7 @@ class BaseModel(Model):
|
|
67
72
|
Raises:
|
68
73
|
SecurityError
|
69
74
|
"""
|
70
|
-
raise NotImplementedError
|
75
|
+
raise NotImplementedError
|
71
76
|
|
72
77
|
@property
|
73
78
|
def json(self) -> dict:
|
@@ -89,7 +94,7 @@ class BaseModel(Model):
|
|
89
94
|
}
|
90
95
|
|
91
96
|
"""
|
92
|
-
raise NotImplementedError
|
97
|
+
raise NotImplementedError
|
93
98
|
|
94
99
|
class Meta:
|
95
100
|
abstract = True
|
@@ -148,9 +153,9 @@ class Account(BaseModel):
|
|
148
153
|
if self.deleted:
|
149
154
|
raise DeletedError("Account has been deleted.")
|
150
155
|
elif not self.verified:
|
151
|
-
raise UnverifiedError
|
156
|
+
raise UnverifiedError
|
152
157
|
elif self.disabled:
|
153
|
-
raise DisabledError
|
158
|
+
raise DisabledError
|
154
159
|
|
155
160
|
async def disable(self):
|
156
161
|
"""
|
@@ -321,12 +326,9 @@ class Session(BaseModel):
|
|
321
326
|
if self.deleted:
|
322
327
|
raise DeletedError("Session has been deleted.")
|
323
328
|
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()
|
329
|
+
raise DeactivatedError
|
330
|
+
elif is_expired(self.expiration_date):
|
331
|
+
raise ExpiredError
|
330
332
|
|
331
333
|
async def deactivate(self):
|
332
334
|
"""
|
@@ -416,7 +418,7 @@ class Session(BaseModel):
|
|
416
418
|
Returns:
|
417
419
|
session
|
418
420
|
"""
|
419
|
-
raise NotImplementedError
|
421
|
+
raise NotImplementedError
|
420
422
|
|
421
423
|
@classmethod
|
422
424
|
async def get_associated(cls, account: Account):
|
@@ -517,15 +519,15 @@ class VerificationSession(Session):
|
|
517
519
|
ChallengeError
|
518
520
|
MaxedOutChallengeError
|
519
521
|
"""
|
520
|
-
if self.code != code.upper():
|
522
|
+
if not code or self.code != code.upper():
|
523
|
+
self.attempts += 1
|
521
524
|
if self.attempts < security_config.MAX_CHALLENGE_ATTEMPTS:
|
522
|
-
self.attempts += 1
|
523
525
|
await self.save(update_fields=["attempts"])
|
524
526
|
raise ChallengeError(
|
525
527
|
"Your code does not match verification session code."
|
526
528
|
)
|
527
529
|
else:
|
528
|
-
raise MaxedOutChallengeError
|
530
|
+
raise MaxedOutChallengeError
|
529
531
|
else:
|
530
532
|
await self.deactivate()
|
531
533
|
|
@@ -540,9 +542,13 @@ class VerificationSession(Session):
|
|
540
542
|
class TwoStepSession(VerificationSession):
|
541
543
|
"""Validates a client using a code sent via email or text."""
|
542
544
|
|
545
|
+
code: str = fields.CharField(
|
546
|
+
max_length=6, default=lambda: get_code(True), null=True
|
547
|
+
)
|
548
|
+
|
543
549
|
@classmethod
|
544
550
|
async def new(cls, request: Request, account: Account, **kwargs):
|
545
|
-
return await
|
551
|
+
return await cls.create(
|
546
552
|
**kwargs,
|
547
553
|
ip=get_ip(request),
|
548
554
|
bearer=account,
|
@@ -560,7 +566,7 @@ class CaptchaSession(VerificationSession):
|
|
560
566
|
|
561
567
|
@classmethod
|
562
568
|
async def new(cls, request: Request, **kwargs):
|
563
|
-
return await
|
569
|
+
return await cls.create(
|
564
570
|
**kwargs,
|
565
571
|
ip=get_ip(request),
|
566
572
|
expiration_date=get_expiration_date(
|
@@ -621,45 +627,36 @@ class AuthenticationSession(Session):
|
|
621
627
|
"""
|
622
628
|
super().validate()
|
623
629
|
if self.requires_second_factor:
|
624
|
-
raise SecondFactorRequiredError
|
630
|
+
raise SecondFactorRequiredError
|
625
631
|
|
626
632
|
async def refresh(self, request: Request):
|
627
633
|
"""
|
628
|
-
Refreshes session if
|
634
|
+
Refreshes session if within refresh date.
|
629
635
|
|
630
636
|
Args:
|
631
637
|
request (Request): Sanic request parameter.
|
632
638
|
|
633
639
|
Raises:
|
634
|
-
DeletedError
|
635
640
|
ExpiredError
|
636
|
-
DeactivatedError
|
637
|
-
SecondFactorRequiredError
|
638
|
-
NotExpiredError
|
639
641
|
|
640
642
|
Returns:
|
641
643
|
session
|
642
644
|
"""
|
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
|
645
|
+
if not is_expired(self.refresh_expiration_date):
|
646
|
+
self.active = False
|
647
|
+
await self.save(update_fields=["active"])
|
648
|
+
logging.info(
|
649
|
+
f"Client {get_ip(request)} has refreshed authentication session {self.id}."
|
650
|
+
)
|
651
|
+
return await self.new(request, self.bearer, True)
|
652
|
+
else:
|
653
|
+
raise ExpiredError
|
657
654
|
|
658
655
|
@classmethod
|
659
656
|
async def new(
|
660
657
|
cls, request: Request, account: Account = None, is_refresh=False, **kwargs
|
661
658
|
):
|
662
|
-
authentication_session = await
|
659
|
+
authentication_session = await cls.create(
|
663
660
|
**kwargs,
|
664
661
|
bearer=account,
|
665
662
|
ip=get_ip(request),
|
@@ -692,7 +689,7 @@ class Role(BaseModel):
|
|
692
689
|
permissions: str = fields.CharField(max_length=255, null=True)
|
693
690
|
|
694
691
|
def validate(self) -> None:
|
695
|
-
raise NotImplementedError
|
692
|
+
raise NotImplementedError
|
696
693
|
|
697
694
|
@property
|
698
695
|
def json(self) -> dict:
|
sanic_security/utils.py
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
import datetime
|
2
2
|
import random
|
3
|
-
import
|
3
|
+
from string import ascii_uppercase, digits
|
4
4
|
|
5
5
|
from argon2 import PasswordHasher
|
6
6
|
from captcha.audio import AudioCaptcha
|
7
7
|
from captcha.image import ImageCaptcha
|
8
8
|
from sanic.request import Request
|
9
9
|
from sanic.response import json as sanic_json, HTTPResponse
|
10
|
-
from secure import Secure
|
11
10
|
|
12
11
|
from sanic_security.configuration import config
|
13
12
|
|
@@ -38,7 +37,6 @@ image_generator = ImageCaptcha(
|
|
38
37
|
)
|
39
38
|
audio_generator = AudioCaptcha(voicedir=config.CAPTCHA_VOICE)
|
40
39
|
password_hasher = PasswordHasher()
|
41
|
-
secure_headers = Secure.with_default_headers()
|
42
40
|
|
43
41
|
|
44
42
|
def get_ip(request: Request) -> str:
|
@@ -54,35 +52,33 @@ def get_ip(request: Request) -> str:
|
|
54
52
|
return request.remote_addr or request.ip
|
55
53
|
|
56
54
|
|
57
|
-
def get_code() -> str:
|
55
|
+
def get_code(digits_only: bool = False) -> str:
|
58
56
|
"""
|
59
57
|
Generates random code to be used for verification.
|
60
58
|
|
59
|
+
Args:
|
60
|
+
digits_only: Determines if code should only contain digits.
|
61
|
+
|
61
62
|
Returns:
|
62
63
|
code
|
63
64
|
"""
|
64
65
|
return "".join(
|
65
|
-
random.choice(
|
66
|
+
random.choice(("" if digits_only else ascii_uppercase) + digits)
|
67
|
+
for _ in range(6)
|
66
68
|
)
|
67
69
|
|
68
70
|
|
69
|
-
def
|
70
|
-
message: str, data, status_code: int = 200
|
71
|
-
) -> HTTPResponse: # May be causing fixture error bc of json property
|
71
|
+
def is_expired(date):
|
72
72
|
"""
|
73
|
-
|
73
|
+
Checks if current date has surpassed the date passed into the function.
|
74
74
|
|
75
75
|
Args:
|
76
|
-
|
77
|
-
data (Any): Raw information to be used by client.
|
78
|
-
status_code (int): HTTP response code.
|
76
|
+
date: The date being checked for expiration.
|
79
77
|
|
80
78
|
Returns:
|
81
|
-
|
79
|
+
is_expired
|
82
80
|
"""
|
83
|
-
return
|
84
|
-
{"message": message, "code": status_code, "data": data}, status=status_code
|
85
|
-
)
|
81
|
+
return date and datetime.datetime.now(datetime.timezone.utc) >= date
|
86
82
|
|
87
83
|
|
88
84
|
def get_expiration_date(seconds: int) -> datetime.datetime:
|
@@ -100,3 +96,22 @@ def get_expiration_date(seconds: int) -> datetime.datetime:
|
|
100
96
|
if seconds > 0
|
101
97
|
else None
|
102
98
|
)
|
99
|
+
|
100
|
+
|
101
|
+
def json(
|
102
|
+
message: str, data, status_code: int = 200
|
103
|
+
) -> HTTPResponse: # May be causing fixture error bc of json property
|
104
|
+
"""
|
105
|
+
A preformatted Sanic json response.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
message (int): Message describing data or relaying human-readable information.
|
109
|
+
data (Any): Raw information to be used by client.
|
110
|
+
status_code (int): HTTP response code.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
json
|
114
|
+
"""
|
115
|
+
return sanic_json(
|
116
|
+
{"message": message, "code": status_code, "data": data}, status=status_code
|
117
|
+
)
|
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.1
|
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
|
```
|
@@ -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=TytuQTjfKslPr893-mCYSzsRK7gfJtXmDz656iCCM0k,22530
|
7
|
+
sanic_security/utils.py,sha256=Il5MjFzVe975yx_CV2HV_LVQYl2W3XYDRGtCG5CQA8Q,3531
|
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.1.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
|
13
|
+
sanic_security-1.15.1.dist-info/METADATA,sha256=EINxdSgG_LLkPu3QqXo1CMe_-GTQk5QDhO3b3909quI,23247
|
14
|
+
sanic_security-1.15.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
15
|
+
sanic_security-1.15.1.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
|
16
|
+
sanic_security-1.15.1.dist-info/RECORD,,
|
@@ -1,16 +0,0 @@
|
|
1
|
-
sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
sanic_security/authentication.py,sha256=ksHa4E82kNXNRKGSqeO28xfQWdF2pJDxUaaQy9snKwU,13299
|
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=RjL9Kfvkfqpm5TXWwFQKKa0J4hfTKgwI6U0s_TAKO8w,11984
|
11
|
-
sanic_security/test/tests.py,sha256=bW5fHJfsCrg8eBmcSqVMLm0R5XRL1ou-XJJRgz09GOE,21993
|
12
|
-
sanic_security-1.14.1.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
|
13
|
-
sanic_security-1.14.1.dist-info/METADATA,sha256=It9-aEffADsbk5GJGO0SEG2DUd1wTq7FRl64l5oMcdA,23259
|
14
|
-
sanic_security-1.14.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
15
|
-
sanic_security-1.14.1.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
|
16
|
-
sanic_security-1.14.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|