sanic-security 1.12.7__py3-none-any.whl → 1.13.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 +81 -69
- sanic_security/authorization.py +9 -1
- sanic_security/configuration.py +2 -2
- sanic_security/exceptions.py +6 -0
- sanic_security/models.py +5 -13
- sanic_security/test/server.py +3 -5
- sanic_security/test/tests.py +1 -1
- sanic_security/verification.py +1 -3
- {sanic_security-1.12.7.dist-info → sanic_security-1.13.0.dist-info}/METADATA +11 -25
- sanic_security-1.13.0.dist-info/RECORD +16 -0
- sanic_security-1.12.7.dist-info/RECORD +0 -16
- {sanic_security-1.12.7.dist-info → sanic_security-1.13.0.dist-info}/LICENSE +0 -0
- {sanic_security-1.12.7.dist-info → sanic_security-1.13.0.dist-info}/WHEEL +0 -0
- {sanic_security-1.12.7.dist-info → sanic_security-1.13.0.dist-info}/top_level.txt +0 -0
sanic_security/authentication.py
CHANGED
@@ -15,6 +15,7 @@ from sanic_security.exceptions import (
|
|
15
15
|
DeactivatedError,
|
16
16
|
SecondFactorFulfilledError,
|
17
17
|
ExpiredError,
|
18
|
+
AuditWarning,
|
18
19
|
)
|
19
20
|
from sanic_security.models import Account, AuthenticationSession, Role, TwoStepSession
|
20
21
|
from sanic_security.utils import get_ip
|
@@ -85,7 +86,6 @@ async def register(
|
|
85
86
|
verified=verified,
|
86
87
|
disabled=disabled,
|
87
88
|
)
|
88
|
-
logger.info(f"Client {get_ip(request)} has registered account {account.id}.")
|
89
89
|
return account
|
90
90
|
|
91
91
|
|
@@ -128,10 +128,13 @@ async def login(
|
|
128
128
|
request, account, requires_second_factor=require_second_factor
|
129
129
|
)
|
130
130
|
logger.info(
|
131
|
-
f"Client has logged into account {account.id} with authentication session {authentication_session.id}."
|
131
|
+
f"Client {get_ip(request)} has logged into account {account.id} with authentication session {authentication_session.id}."
|
132
132
|
)
|
133
133
|
return authentication_session
|
134
134
|
except VerifyMismatchError:
|
135
|
+
logger.warning(
|
136
|
+
f"Client {get_ip(request)} has failed to log into account {account.id}."
|
137
|
+
)
|
135
138
|
raise CredentialsError("Incorrect password.", 401)
|
136
139
|
|
137
140
|
|
@@ -156,8 +159,8 @@ async def logout(request: Request) -> AuthenticationSession:
|
|
156
159
|
authentication_session.active = False
|
157
160
|
await authentication_session.save(update_fields=["active"])
|
158
161
|
logger.info(
|
159
|
-
f"Client has logged out{"
|
160
|
-
f"
|
162
|
+
f"Client {get_ip(request)} has logged out {"anonymously" if authentication_session.anonymous
|
163
|
+
else f"of account {authentication_session.bearer.id}"} with authentication session {authentication_session.id}."
|
161
164
|
)
|
162
165
|
return authentication_session
|
163
166
|
|
@@ -190,9 +193,6 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession:
|
|
190
193
|
await two_step_session.check_code(request.form.get("code"))
|
191
194
|
authentication_session.requires_second_factor = False
|
192
195
|
await authentication_session.save(update_fields=["requires_second_factor"])
|
193
|
-
logger.info(
|
194
|
-
f"Authentication session {authentication_session.id} second factor has been fulfilled."
|
195
|
-
)
|
196
196
|
return authentication_session
|
197
197
|
|
198
198
|
|
@@ -262,68 +262,6 @@ def requires_authentication(arg=None):
|
|
262
262
|
return decorator(arg) if callable(arg) else decorator
|
263
263
|
|
264
264
|
|
265
|
-
def attach_refresh_encoder(app: Sanic):
|
266
|
-
"""
|
267
|
-
Automatically encodes the new/refreshed session returned during authentication when client's current session expires.
|
268
|
-
|
269
|
-
Args:
|
270
|
-
app: (Sanic): The main Sanic application instance.
|
271
|
-
"""
|
272
|
-
|
273
|
-
@app.on_response
|
274
|
-
async def refresh_encoder_middleware(request, response):
|
275
|
-
if hasattr(request.ctx, "authentication_session"):
|
276
|
-
authentication_session = request.ctx.authentication_session
|
277
|
-
if authentication_session.is_refresh:
|
278
|
-
authentication_session.encode(response)
|
279
|
-
|
280
|
-
|
281
|
-
def create_initial_admin_account(app: Sanic) -> None:
|
282
|
-
"""
|
283
|
-
Creates the initial admin account that can be logged into and has complete authoritative access.
|
284
|
-
|
285
|
-
Args:
|
286
|
-
app (Sanic): The main Sanic application instance.
|
287
|
-
"""
|
288
|
-
|
289
|
-
@app.listener("before_server_start")
|
290
|
-
async def create(app, loop):
|
291
|
-
if security_config.SECRET == DEFAULT_CONFIG["SECRET"]:
|
292
|
-
warnings.warn("Secret should be changed from default.")
|
293
|
-
if security_config.INITIAL_ADMIN_EMAIL == DEFAULT_CONFIG["INITIAL_ADMIN_EMAIL"]:
|
294
|
-
warnings.warn("Initial admin email should be changed from default.")
|
295
|
-
if (
|
296
|
-
security_config.INITIAL_ADMIN_PASSWORD
|
297
|
-
== DEFAULT_CONFIG["INITIAL_ADMIN_PASSWORD"]
|
298
|
-
):
|
299
|
-
warnings.warn("Initial admin password should be changed from default.")
|
300
|
-
try:
|
301
|
-
role = await Role.filter(name="Admin").get()
|
302
|
-
except DoesNotExist:
|
303
|
-
role = await Role.create(
|
304
|
-
description="Has root abilities, assign sparingly.",
|
305
|
-
permissions="*:*",
|
306
|
-
name="Admin",
|
307
|
-
)
|
308
|
-
try:
|
309
|
-
account = await Account.filter(
|
310
|
-
email=security_config.INITIAL_ADMIN_EMAIL
|
311
|
-
).get()
|
312
|
-
await account.fetch_related("roles")
|
313
|
-
if role not in account.roles:
|
314
|
-
await account.roles.add(role)
|
315
|
-
logger.warning("Initial admin account role has been reinstated.")
|
316
|
-
except DoesNotExist:
|
317
|
-
account = await Account.create(
|
318
|
-
username="Admin",
|
319
|
-
email=security_config.INITIAL_ADMIN_EMAIL,
|
320
|
-
password=password_hasher.hash(security_config.INITIAL_ADMIN_PASSWORD),
|
321
|
-
verified=True,
|
322
|
-
)
|
323
|
-
await account.roles.add(role)
|
324
|
-
logger.info("Initial admin account created.")
|
325
|
-
|
326
|
-
|
327
265
|
def validate_email(email: str) -> str:
|
328
266
|
"""
|
329
267
|
Validates email format.
|
@@ -402,3 +340,77 @@ def validate_password(password: str) -> str:
|
|
402
340
|
400,
|
403
341
|
)
|
404
342
|
return password
|
343
|
+
|
344
|
+
|
345
|
+
def initialize_security(app: Sanic, create_root=True) -> None:
|
346
|
+
"""
|
347
|
+
Audits configuration, creates root administrator account, and attaches refresh encoder middleware.
|
348
|
+
|
349
|
+
Args:
|
350
|
+
app (Sanic): The main Sanic application instance.
|
351
|
+
create_root (bool): Determines root account creation on initialization.
|
352
|
+
"""
|
353
|
+
|
354
|
+
@app.on_response
|
355
|
+
async def refresh_encoder_middleware(request, response):
|
356
|
+
if hasattr(request.ctx, "authentication_session"):
|
357
|
+
authentication_session = request.ctx.authentication_session
|
358
|
+
if authentication_session.is_refresh:
|
359
|
+
authentication_session.encode(response)
|
360
|
+
|
361
|
+
@app.listener("before_server_start")
|
362
|
+
async def audit_configuration(app, loop):
|
363
|
+
if security_config.SECRET == DEFAULT_CONFIG["SECRET"]:
|
364
|
+
warnings.warn("Secret should be changed from default.", AuditWarning)
|
365
|
+
if not security_config.SESSION_HTTPONLY:
|
366
|
+
warnings.warn("HttpOnly should be enabled.", AuditWarning)
|
367
|
+
if not security_config.SESSION_SECURE:
|
368
|
+
warnings.warn("Secure should be enabled.", AuditWarning)
|
369
|
+
if security_config.SESSION_SAMESITE.lower() == "none":
|
370
|
+
warnings.warn("SameSite should not be set to none.", AuditWarning)
|
371
|
+
if (
|
372
|
+
create_root
|
373
|
+
and security_config.INITIAL_ADMIN_EMAIL
|
374
|
+
== DEFAULT_CONFIG["INITIAL_ADMIN_EMAIL"]
|
375
|
+
):
|
376
|
+
warnings.warn(
|
377
|
+
"Initial admin email should be changed from default.", AuditWarning
|
378
|
+
)
|
379
|
+
if (
|
380
|
+
create_root
|
381
|
+
and security_config.INITIAL_ADMIN_PASSWORD
|
382
|
+
== DEFAULT_CONFIG["INITIAL_ADMIN_PASSWORD"]
|
383
|
+
):
|
384
|
+
warnings.warn(
|
385
|
+
"Initial admin password should be changed from default.", AuditWarning
|
386
|
+
)
|
387
|
+
|
388
|
+
@app.listener("before_server_start")
|
389
|
+
async def create_root_account(app, loop):
|
390
|
+
if not create_root:
|
391
|
+
return
|
392
|
+
try:
|
393
|
+
role = await Role.filter(name="Root").get()
|
394
|
+
except DoesNotExist:
|
395
|
+
role = await Role.create(
|
396
|
+
description="Has administrator abilities, assign sparingly.",
|
397
|
+
permissions="*:*",
|
398
|
+
name="Root",
|
399
|
+
)
|
400
|
+
try:
|
401
|
+
account = await Account.filter(
|
402
|
+
email=security_config.INITIAL_ADMIN_EMAIL
|
403
|
+
).get()
|
404
|
+
await account.fetch_related("roles")
|
405
|
+
if role not in account.roles:
|
406
|
+
await account.roles.add(role)
|
407
|
+
logger.warning("Initial admin account role has been reinstated.")
|
408
|
+
except DoesNotExist:
|
409
|
+
account = await Account.create(
|
410
|
+
username="Root",
|
411
|
+
email=security_config.INITIAL_ADMIN_EMAIL,
|
412
|
+
password=password_hasher.hash(security_config.INITIAL_ADMIN_PASSWORD),
|
413
|
+
verified=True,
|
414
|
+
)
|
415
|
+
await account.roles.add(role)
|
416
|
+
logger.info("Initial admin account created.")
|
sanic_security/authorization.py
CHANGED
@@ -8,6 +8,7 @@ from tortoise.exceptions import DoesNotExist
|
|
8
8
|
from sanic_security.authentication import authenticate
|
9
9
|
from sanic_security.exceptions import AuthorizationError, AnonymousError
|
10
10
|
from sanic_security.models import Role, Account, AuthenticationSession
|
11
|
+
from sanic_security.utils import get_ip
|
11
12
|
|
12
13
|
"""
|
13
14
|
Copyright (c) 2020-present Nicholas Aidan Stewart
|
@@ -66,6 +67,10 @@ async def check_permissions(
|
|
66
67
|
):
|
67
68
|
if fnmatch(required_permission, role_permission):
|
68
69
|
return authentication_session
|
70
|
+
logger.warning(
|
71
|
+
f"Client {get_ip(request)} with account {authentication_session.bearer.id} has insufficient permissions for "
|
72
|
+
"attempted action."
|
73
|
+
)
|
69
74
|
raise AuthorizationError("Insufficient permissions required for this action.")
|
70
75
|
|
71
76
|
|
@@ -98,6 +103,10 @@ async def check_roles(request: Request, *required_roles: str) -> AuthenticationS
|
|
98
103
|
for role in roles:
|
99
104
|
if role.name in required_roles:
|
100
105
|
return authentication_session
|
106
|
+
logger.warning(
|
107
|
+
f"Client {get_ip(request)} with account {authentication_session.bearer.id} has insufficient roles for "
|
108
|
+
"attempted action."
|
109
|
+
)
|
101
110
|
raise AuthorizationError("Insufficient roles required for this action.")
|
102
111
|
|
103
112
|
|
@@ -120,7 +129,6 @@ async def assign_role(
|
|
120
129
|
description=description, permissions=permissions, name=name
|
121
130
|
)
|
122
131
|
await account.roles.add(role)
|
123
|
-
logger.info(f"Role {role.id} has been assigned to account {account.id}.")
|
124
132
|
return role
|
125
133
|
|
126
134
|
|
sanic_security/configuration.py
CHANGED
@@ -27,11 +27,11 @@ SOFTWARE.
|
|
27
27
|
DEFAULT_CONFIG = {
|
28
28
|
"SECRET": "This is a big secret. Shhhhh",
|
29
29
|
"PUBLIC_SECRET": None,
|
30
|
-
"SESSION_SAMESITE": "
|
30
|
+
"SESSION_SAMESITE": "Strict",
|
31
31
|
"SESSION_SECURE": True,
|
32
32
|
"SESSION_HTTPONLY": True,
|
33
33
|
"SESSION_DOMAIN": None,
|
34
|
-
"SESSION_PREFIX": "
|
34
|
+
"SESSION_PREFIX": "tkn",
|
35
35
|
"SESSION_ENCODING_ALGORITHM": "HS256",
|
36
36
|
"MAX_CHALLENGE_ATTEMPTS": 5,
|
37
37
|
"CAPTCHA_SESSION_EXPIRATION": 60,
|
sanic_security/exceptions.py
CHANGED
sanic_security/models.py
CHANGED
@@ -6,7 +6,6 @@ from typing import Union
|
|
6
6
|
import jwt
|
7
7
|
from captcha.image import ImageCaptcha
|
8
8
|
from jwt import DecodeError
|
9
|
-
from sanic.log import logger
|
10
9
|
from sanic.request import Request
|
11
10
|
from sanic.response import HTTPResponse, raw
|
12
11
|
from tortoise import fields, Model
|
@@ -142,7 +141,6 @@ class Account(BaseModel):
|
|
142
141
|
else:
|
143
142
|
self.disabled = True
|
144
143
|
await self.save(update_fields=["disabled"])
|
145
|
-
logger.info(f"Account {self.id} has been disabled.")
|
146
144
|
|
147
145
|
@property
|
148
146
|
def json(self) -> dict:
|
@@ -320,7 +318,6 @@ class Session(BaseModel):
|
|
320
318
|
if self.active:
|
321
319
|
self.active = False
|
322
320
|
await self.save(update_fields=["active"])
|
323
|
-
logger.info(f"Session {self.id} has been deactivated.")
|
324
321
|
else:
|
325
322
|
raise DeactivatedError("Session is already deactivated.", 403)
|
326
323
|
|
@@ -516,9 +513,6 @@ class VerificationSession(Session):
|
|
516
513
|
else:
|
517
514
|
raise MaxedOutChallengeError()
|
518
515
|
else:
|
519
|
-
logger.info(
|
520
|
-
f"Client has completed verification session {self.id} challenge."
|
521
|
-
)
|
522
516
|
await self.deactivate()
|
523
517
|
|
524
518
|
@classmethod
|
@@ -530,9 +524,7 @@ class VerificationSession(Session):
|
|
530
524
|
|
531
525
|
|
532
526
|
class TwoStepSession(VerificationSession):
|
533
|
-
"""
|
534
|
-
Validates a client using a code sent via email or text.
|
535
|
-
"""
|
527
|
+
"""Validates a client using a code sent via email or text."""
|
536
528
|
|
537
529
|
@classmethod
|
538
530
|
async def new(cls, request: Request, account: Account, **kwargs):
|
@@ -550,9 +542,7 @@ class TwoStepSession(VerificationSession):
|
|
550
542
|
|
551
543
|
|
552
544
|
class CaptchaSession(VerificationSession):
|
553
|
-
"""
|
554
|
-
Validates a client with a captcha challenge.
|
555
|
-
"""
|
545
|
+
"""Validates a client with a captcha challenge."""
|
556
546
|
|
557
547
|
@classmethod
|
558
548
|
async def new(cls, request: Request, **kwargs):
|
@@ -612,6 +602,9 @@ class AuthenticationSession(Session):
|
|
612
602
|
"""
|
613
603
|
Refreshes session if expired and within refresh date.
|
614
604
|
|
605
|
+
Args:
|
606
|
+
request (Request): Sanic request parameter.
|
607
|
+
|
615
608
|
Raises:
|
616
609
|
DeletedError
|
617
610
|
ExpiredError
|
@@ -633,7 +626,6 @@ class AuthenticationSession(Session):
|
|
633
626
|
):
|
634
627
|
self.active = False
|
635
628
|
await self.save(update_fields=["active"])
|
636
|
-
logger.info(f"Client has refreshed authentication session {self.id}.")
|
637
629
|
return await self.new(request, self.bearer, True)
|
638
630
|
else:
|
639
631
|
raise e
|
sanic_security/test/server.py
CHANGED
@@ -10,9 +10,8 @@ from sanic_security.authentication import (
|
|
10
10
|
register,
|
11
11
|
requires_authentication,
|
12
12
|
logout,
|
13
|
-
create_initial_admin_account,
|
14
13
|
fulfill_second_factor,
|
15
|
-
|
14
|
+
initialize_security,
|
16
15
|
)
|
17
16
|
from sanic_security.authorization import (
|
18
17
|
assign_role,
|
@@ -308,7 +307,6 @@ register_tortoise(
|
|
308
307
|
modules={"models": ["sanic_security.models"]},
|
309
308
|
generate_schemas=True,
|
310
309
|
)
|
311
|
-
|
312
|
-
create_initial_admin_account(app)
|
310
|
+
initialize_security(app, True)
|
313
311
|
if __name__ == "__main__":
|
314
|
-
app.run(host="127.0.0.1", port=8000, workers=1
|
312
|
+
app.run(host="127.0.0.1", port=8000, workers=1)
|
sanic_security/test/tests.py
CHANGED
@@ -231,7 +231,7 @@ class LoginTest(TestCase):
|
|
231
231
|
permitted_authorization_response = self.client.post(
|
232
232
|
"http://127.0.0.1:8000/api/test/auth/roles",
|
233
233
|
data={
|
234
|
-
"role": "
|
234
|
+
"role": "Root",
|
235
235
|
"permissions_required": "perm1:create,add, perm2:*",
|
236
236
|
},
|
237
237
|
)
|
sanic_security/verification.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import functools
|
2
2
|
from contextlib import suppress
|
3
3
|
|
4
|
-
from sanic.log import logger
|
5
4
|
from sanic.request import Request
|
6
5
|
|
7
6
|
from sanic_security.exceptions import (
|
@@ -85,7 +84,7 @@ async def two_step_verification(request: Request) -> TwoStepSession:
|
|
85
84
|
MaxedOutChallengeError
|
86
85
|
|
87
86
|
Returns:
|
88
|
-
|
87
|
+
two_step_session
|
89
88
|
"""
|
90
89
|
two_step_session = await TwoStepSession.decode(request)
|
91
90
|
two_step_session.validate()
|
@@ -157,7 +156,6 @@ async def verify_account(request: Request) -> TwoStepSession:
|
|
157
156
|
await two_step_session.check_code(request.form.get("code"))
|
158
157
|
two_step_session.bearer.verified = True
|
159
158
|
await two_step_session.bearer.save(update_fields=["verified"])
|
160
|
-
logger.info(f"Account {two_step_session.bearer.id} has been verified.")
|
161
159
|
return two_step_session
|
162
160
|
|
163
161
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sanic-security
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.13.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/
|
@@ -64,7 +64,7 @@ Requires-Dist: cryptography; extra == "dev"
|
|
64
64
|
* [Usage](#usage)
|
65
65
|
* [Authentication](#authentication)
|
66
66
|
* [Captcha](#captcha)
|
67
|
-
* [Two
|
67
|
+
* [Two-Step Verification](#two-step-verification)
|
68
68
|
* [Authorization](#authorization)
|
69
69
|
* [Testing](#testing)
|
70
70
|
* [Tortoise](#tortoise)
|
@@ -145,12 +145,12 @@ You can load environment variables with a different prefix via `config.load_envi
|
|
145
145
|
|---------------------------------------|------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
|
146
146
|
| **SECRET** | This is a big secret. Shhhhh | The secret used for generating and signing JWTs. This should be a string unique to your application. Keep it safe. |
|
147
147
|
| **PUBLIC_SECRET** | None | The secret used for verifying and decoding JWTs and can be publicly shared. This should be a string unique to your application. |
|
148
|
-
| **SESSION_SAMESITE** |
|
148
|
+
| **SESSION_SAMESITE** | Strict | The SameSite attribute of session cookies. |
|
149
149
|
| **SESSION_SECURE** | True | The Secure attribute of session cookies. |
|
150
150
|
| **SESSION_HTTPONLY** | True | The HttpOnly attribute of session cookies. HIGHLY recommended that you do not turn this off, unless you know what you are doing. |
|
151
151
|
| **SESSION_DOMAIN** | None | The Domain attribute of session cookies. |
|
152
152
|
| **SESSION_ENCODING_ALGORITHM** | HS256 | The algorithm used to encode and decode session JWT's. |
|
153
|
-
| **SESSION_PREFIX** |
|
153
|
+
| **SESSION_PREFIX** | tkn | Prefix attached to the beginning of session cookies. |
|
154
154
|
| **MAX_CHALLENGE_ATTEMPTS** | 5 | The maximum amount of session challenge attempts allowed. |
|
155
155
|
| **CAPTCHA_SESSION_EXPIRATION** | 60 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
|
156
156
|
| **CAPTCHA_FONT** | captcha-font.ttf | The file path to the font being used for captcha generation. |
|
@@ -166,19 +166,16 @@ You can load environment variables with a different prefix via `config.load_envi
|
|
166
166
|
Sanic Security's authentication and verification functionality is session based. A new session will be created for the user after the user logs in or requests some form of verification (two-step, captcha). The session data is then encoded into a JWT and stored on a cookie on the user’s browser. The session cookie is then sent
|
167
167
|
along with every subsequent request. The server can then compare the session stored on the cookie against the session information stored in the database to verify user’s identity and send a response with the corresponding state.
|
168
168
|
|
169
|
-
|
170
|
-
|
171
|
-
## Authentication
|
172
|
-
|
173
|
-
* Initial Administrator Account
|
174
|
-
|
175
|
-
Creates root account if it doesn't exist, you should modify its credentials in config!
|
176
|
-
|
169
|
+
* Initialize sanic-security as follows:
|
177
170
|
```python
|
178
|
-
|
171
|
+
initialize_security(app)
|
179
172
|
if __name__ == "__main__":
|
180
|
-
app.run(host="127.0.0.1", port=8000)
|
173
|
+
app.run(host="127.0.0.1", port=8000, workers=1, debug=True)
|
181
174
|
```
|
175
|
+
|
176
|
+
The tables in the below examples represent example [request form-data](https://sanicframework.org/en/guide/basics/request.html#form).
|
177
|
+
|
178
|
+
## Authentication
|
182
179
|
|
183
180
|
* Registration (With two-step account verification)
|
184
181
|
|
@@ -321,17 +318,6 @@ async def on_authenticate(request):
|
|
321
318
|
return response
|
322
319
|
```
|
323
320
|
|
324
|
-
* Refresh Encoder
|
325
|
-
|
326
|
-
A new/refreshed session is returned during authentication when the client's current session expires and it
|
327
|
-
requires encoding. This should be be done automatically via middleware.
|
328
|
-
|
329
|
-
```python
|
330
|
-
attach_refresh_encoder(app)
|
331
|
-
if __name__ == "__main__":
|
332
|
-
app.run(host="127.0.0.1", port=8000)
|
333
|
-
```
|
334
|
-
|
335
321
|
## Captcha
|
336
322
|
|
337
323
|
A pre-existing font for captcha challenges is included in the Sanic Security repository. You may set your own font by
|
@@ -0,0 +1,16 @@
|
|
1
|
+
sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
sanic_security/authentication.py,sha256=ZY4WJDUXbwDdaH_2Ovc9gkR1jCgw52yB9enYp_1LypM,14334
|
3
|
+
sanic_security/authorization.py,sha256=XbRrnsx-Yqpiemf3bn_djIYIe1khdnfToB7DsBheLtk,7338
|
4
|
+
sanic_security/configuration.py,sha256=br2hI3MHsTBh3yfPer5f3bkKSWfQdCeqfLqWmaDNVoM,5510
|
5
|
+
sanic_security/exceptions.py,sha256=JiCaBR2kjE1Cj0fc_08y-32fqJJXa_1UCw205T4_RTY,5493
|
6
|
+
sanic_security/models.py,sha256=v3tJyL420HEdZXqJCq9uPPSivuYXuQNtqf9QC9wF0TU,22274
|
7
|
+
sanic_security/utils.py,sha256=XAUNalcTi53qTz0D8xiDyDyRlq7Z7ffNBzUONJZqe90,2705
|
8
|
+
sanic_security/verification.py,sha256=ebT7QaytHAsw-IKA13W9wyCoqoBAYKgmFA1QJ80N2bE,7476
|
9
|
+
sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
sanic_security/test/server.py,sha256=DmwP5dBs2Sq2qk4UZgWbAqzm96YXvLGI9jWeqzluy_c,12082
|
11
|
+
sanic_security/test/tests.py,sha256=bW5fHJfsCrg8eBmcSqVMLm0R5XRL1ou-XJJRgz09GOE,21993
|
12
|
+
sanic_security-1.13.0.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
|
13
|
+
sanic_security-1.13.0.dist-info/METADATA,sha256=VeYHXoqKPrbNAfub2NW3RT78Rt1WSUBCKj2gVJrHplE,23000
|
14
|
+
sanic_security-1.13.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
15
|
+
sanic_security-1.13.0.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
|
16
|
+
sanic_security-1.13.0.dist-info/RECORD,,
|
@@ -1,16 +0,0 @@
|
|
1
|
-
sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
sanic_security/authentication.py,sha256=CzjbVh7uDjBs22oQCklcbmp5gTDW17AoYaa3lmyPnGk,13783
|
3
|
-
sanic_security/authorization.py,sha256=80vuM_2oNgfhOMuct89R6UBhDYmSWQzIgocKxhKzReE,7030
|
4
|
-
sanic_security/configuration.py,sha256=MKxYjq1q9RBRX2cMJkIe87ke0mLKa69RWoQ5MhVciho,5512
|
5
|
-
sanic_security/exceptions.py,sha256=MTPF4tm_68Nmf_z06RHH_6DTiC_CNiLER1jzEoW1dFk,5398
|
6
|
-
sanic_security/models.py,sha256=zWn9zXl-vwP8qJ-DzBuNNG7MCl1ggZX6yb6Zgh3Jfcs,22601
|
7
|
-
sanic_security/utils.py,sha256=XAUNalcTi53qTz0D8xiDyDyRlq7Z7ffNBzUONJZqe90,2705
|
8
|
-
sanic_security/verification.py,sha256=rNyZk_J53dOzk8qy5iG3yiMRuRcGaYeHwIwnFDH9TWw,7582
|
9
|
-
sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
sanic_security/test/server.py,sha256=-dQAB75C-l9kfN7PnaYNDOLiLXaqg0euZAWF5oXMxeE,12164
|
11
|
-
sanic_security/test/tests.py,sha256=jUZ5kgQF4rFx1bImKqHYR7UeoYAfjtNNXSQYTMfzlg4,21994
|
12
|
-
sanic_security-1.12.7.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
|
13
|
-
sanic_security-1.12.7.dist-info/METADATA,sha256=ZGVTjtv-OVMrs_3Ac__jKE1JgdhtIAyhMCW2n3VUW3Y,23393
|
14
|
-
sanic_security-1.12.7.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
15
|
-
sanic_security-1.12.7.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
|
16
|
-
sanic_security-1.12.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|