sanic-security 1.12.7__py3-none-any.whl → 1.13.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.
@@ -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
@@ -128,10 +129,13 @@ async def login(
128
129
  request, account, requires_second_factor=require_second_factor
129
130
  )
130
131
  logger.info(
131
- f"Client has logged into account {account.id} with authentication session {authentication_session.id}."
132
+ f"Client {get_ip(request)} has logged in with authentication session {authentication_session.id}."
132
133
  )
133
134
  return authentication_session
134
135
  except VerifyMismatchError:
136
+ logger.warning(
137
+ f"Client {get_ip(request)} has failed to log into account {account.id}."
138
+ )
135
139
  raise CredentialsError("Incorrect password.", 401)
136
140
 
137
141
 
@@ -156,8 +160,7 @@ async def logout(request: Request) -> AuthenticationSession:
156
160
  authentication_session.active = False
157
161
  await authentication_session.save(update_fields=["active"])
158
162
  logger.info(
159
- f"Client has logged out{" anonymously" if authentication_session.anonymous else
160
- f" of account {authentication_session.bearer.id}"} with authentication session {authentication_session.id}."
163
+ f"Client {get_ip(request)} has logged out with authentication session {authentication_session.id}."
161
164
  )
162
165
  return authentication_session
163
166
 
@@ -191,7 +194,8 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession:
191
194
  authentication_session.requires_second_factor = False
192
195
  await authentication_session.save(update_fields=["requires_second_factor"])
193
196
  logger.info(
194
- f"Authentication session {authentication_session.id} second factor has been fulfilled."
197
+ f"Client {get_ip(request)} has fulfilled authentication session {authentication_session.id} "
198
+ "second factor."
195
199
  )
196
200
  return authentication_session
197
201
 
@@ -262,68 +266,6 @@ def requires_authentication(arg=None):
262
266
  return decorator(arg) if callable(arg) else decorator
263
267
 
264
268
 
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
269
  def validate_email(email: str) -> str:
328
270
  """
329
271
  Validates email format.
@@ -402,3 +344,77 @@ def validate_password(password: str) -> str:
402
344
  400,
403
345
  )
404
346
  return password
347
+
348
+
349
+ def initialize_security(app: Sanic, create_root=True) -> None:
350
+ """
351
+ Audits configuration, creates root administrator account, and attaches refresh encoder middleware.
352
+
353
+ Args:
354
+ app (Sanic): The main Sanic application instance.
355
+ create_root (bool): Determines root account creation on initialization.
356
+ """
357
+
358
+ @app.on_response
359
+ async def refresh_encoder_middleware(request, response):
360
+ if hasattr(request.ctx, "authentication_session"):
361
+ authentication_session = request.ctx.authentication_session
362
+ if authentication_session.is_refresh:
363
+ authentication_session.encode(response)
364
+
365
+ @app.listener("before_server_start")
366
+ async def audit_configuration(app, loop):
367
+ if security_config.SECRET == DEFAULT_CONFIG["SECRET"]:
368
+ warnings.warn("Secret should be changed from default.", AuditWarning)
369
+ if not security_config.SESSION_HTTPONLY:
370
+ warnings.warn("HttpOnly should be enabled.", AuditWarning)
371
+ if not security_config.SESSION_SECURE:
372
+ warnings.warn("Secure should be enabled.", AuditWarning)
373
+ if security_config.SESSION_SAMESITE.lower() == "none":
374
+ warnings.warn("SameSite should not be set to none.", AuditWarning)
375
+ if (
376
+ create_root
377
+ and security_config.INITIAL_ADMIN_EMAIL
378
+ == DEFAULT_CONFIG["INITIAL_ADMIN_EMAIL"]
379
+ ):
380
+ warnings.warn(
381
+ "Initial admin email should be changed from default.", AuditWarning
382
+ )
383
+ if (
384
+ create_root
385
+ and security_config.INITIAL_ADMIN_PASSWORD
386
+ == DEFAULT_CONFIG["INITIAL_ADMIN_PASSWORD"]
387
+ ):
388
+ warnings.warn(
389
+ "Initial admin password should be changed from default.", AuditWarning
390
+ )
391
+
392
+ @app.listener("before_server_start")
393
+ async def create_root_account(app, loop):
394
+ if not create_root:
395
+ return
396
+ try:
397
+ role = await Role.filter(name="Root").get()
398
+ except DoesNotExist:
399
+ role = await Role.create(
400
+ description="Has administrator abilities, assign sparingly.",
401
+ permissions="*:*",
402
+ name="Root",
403
+ )
404
+ try:
405
+ account = await Account.filter(
406
+ email=security_config.INITIAL_ADMIN_EMAIL
407
+ ).get()
408
+ await account.fetch_related("roles")
409
+ if role not in account.roles:
410
+ await account.roles.add(role)
411
+ logger.warning("Initial admin account role has been reinstated.")
412
+ except DoesNotExist:
413
+ account = await Account.create(
414
+ username="Root",
415
+ email=security_config.INITIAL_ADMIN_EMAIL,
416
+ password=password_hasher.hash(security_config.INITIAL_ADMIN_PASSWORD),
417
+ verified=True,
418
+ )
419
+ await account.roles.add(role)
420
+ logger.info("Initial admin account created.")
@@ -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
@@ -58,6 +59,9 @@ async def check_permissions(
58
59
  """
59
60
  authentication_session = await authenticate(request)
60
61
  if authentication_session.anonymous:
62
+ logger.warning(
63
+ f"Client {get_ip(request)} attempted an unauthorized action anonymously."
64
+ )
61
65
  raise AnonymousError()
62
66
  roles = await authentication_session.bearer.roles.filter(deleted=False).all()
63
67
  for role in roles:
@@ -66,6 +70,9 @@ async def check_permissions(
66
70
  ):
67
71
  if fnmatch(required_permission, role_permission):
68
72
  return authentication_session
73
+ logger.warning(
74
+ f"Client {get_ip(request)} with account {authentication_session.bearer.id} attempted an unauthorized action. "
75
+ )
69
76
  raise AuthorizationError("Insufficient permissions required for this action.")
70
77
 
71
78
 
@@ -93,11 +100,17 @@ async def check_roles(request: Request, *required_roles: str) -> AuthenticationS
93
100
  """
94
101
  authentication_session = await authenticate(request)
95
102
  if authentication_session.anonymous:
103
+ logger.warning(
104
+ f"Client {get_ip(request)} attempted an unauthorized action anonymously."
105
+ )
96
106
  raise AnonymousError()
97
107
  roles = await authentication_session.bearer.roles.filter(deleted=False).all()
98
108
  for role in roles:
99
109
  if role.name in required_roles:
100
110
  return authentication_session
111
+ logger.warning(
112
+ f"Client {get_ip(request)} with account {authentication_session.bearer.id} attempted an unauthorized action. "
113
+ )
101
114
  raise AuthorizationError("Insufficient roles required for this action.")
102
115
 
103
116
 
@@ -120,7 +133,6 @@ async def assign_role(
120
133
  description=description, permissions=permissions, name=name
121
134
  )
122
135
  await account.roles.add(role)
123
- logger.info(f"Role {role.id} has been assigned to account {account.id}.")
124
136
  return role
125
137
 
126
138
 
@@ -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": "strict",
30
+ "SESSION_SAMESITE": "Strict",
31
31
  "SESSION_SECURE": True,
32
32
  "SESSION_HTTPONLY": True,
33
33
  "SESSION_DOMAIN": None,
34
- "SESSION_PREFIX": "token",
34
+ "SESSION_PREFIX": "tkn",
35
35
  "SESSION_ENCODING_ALGORITHM": "HS256",
36
36
  "MAX_CHALLENGE_ATTEMPTS": 5,
37
37
  "CAPTCHA_SESSION_EXPIRATION": 60,
@@ -206,3 +206,9 @@ class AnonymousError(AuthorizationError):
206
206
 
207
207
  def __init__(self):
208
208
  super().__init__("Session is anonymous.")
209
+
210
+
211
+ class AuditWarning(Warning):
212
+ """
213
+ Raised when configuration is invalid.
214
+ """
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
@@ -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
- attach_refresh_encoder,
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
- attach_refresh_encoder(app)
312
- create_initial_admin_account(app)
310
+ initialize_security(app, True)
313
311
  if __name__ == "__main__":
314
312
  app.run(host="127.0.0.1", port=8000, workers=1, debug=True)
@@ -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": "Admin",
234
+ "role": "Root",
235
235
  "permissions_required": "perm1:create,add, perm2:*",
236
236
  },
237
237
  )
@@ -8,12 +8,14 @@ from sanic_security.exceptions import (
8
8
  JWTDecodeError,
9
9
  NotFoundError,
10
10
  VerifiedError,
11
+ MaxedOutChallengeError,
11
12
  )
12
13
  from sanic_security.models import (
13
14
  Account,
14
15
  TwoStepSession,
15
16
  CaptchaSession,
16
17
  )
18
+ from sanic_security.utils import get_ip
17
19
 
18
20
  """
19
21
  Copyright (c) 2020-present Nicholas Aidan Stewart
@@ -85,12 +87,21 @@ async def two_step_verification(request: Request) -> TwoStepSession:
85
87
  MaxedOutChallengeError
86
88
 
87
89
  Returns:
88
- two_step_session
90
+ two_step_session
89
91
  """
90
92
  two_step_session = await TwoStepSession.decode(request)
91
93
  two_step_session.validate()
92
94
  two_step_session.bearer.validate()
93
- await two_step_session.check_code(request.form.get("code"))
95
+ try:
96
+ await two_step_session.check_code(request.form.get("code"))
97
+ except MaxedOutChallengeError as e:
98
+ logger.warning(
99
+ f"Client {get_ip(request)} has exceeded maximum two-step session {two_step_session.id} challenge attempts."
100
+ )
101
+ raise e
102
+ logger.info(
103
+ f"Client {get_ip(request)} has completed two-step session {two_step_session.id} challenge."
104
+ )
94
105
  return two_step_session
95
106
 
96
107
 
@@ -154,10 +165,19 @@ async def verify_account(request: Request) -> TwoStepSession:
154
165
  if two_step_session.bearer.verified:
155
166
  raise VerifiedError()
156
167
  two_step_session.validate()
157
- await two_step_session.check_code(request.form.get("code"))
168
+ try:
169
+ await two_step_session.check_code(request.form.get("code"))
170
+ except MaxedOutChallengeError as e:
171
+ logger.warning(
172
+ f"Client {get_ip(request)} has exceeded maximum two-step session {two_step_session.id} challenge attempts "
173
+ "during account verification."
174
+ )
175
+ raise e
158
176
  two_step_session.bearer.verified = True
159
177
  await two_step_session.bearer.save(update_fields=["verified"])
160
- logger.info(f"Account {two_step_session.bearer.id} has been verified.")
178
+ logger.info(
179
+ f"Client {get_ip(request)} has verified account {two_step_session.bearer.id}."
180
+ )
161
181
  return two_step_session
162
182
 
163
183
 
@@ -199,7 +219,16 @@ async def captcha(request: Request) -> CaptchaSession:
199
219
  """
200
220
  captcha_session = await CaptchaSession.decode(request)
201
221
  captcha_session.validate()
202
- await captcha_session.check_code(request.form.get("captcha"))
222
+ try:
223
+ await captcha_session.check_code(request.form.get("captcha"))
224
+ except MaxedOutChallengeError as e:
225
+ logger.warning(
226
+ f"Client {get_ip(request)} has exceeded maximum captcha session {captcha_session.id} challenge attempts."
227
+ )
228
+ raise e
229
+ logger.info(
230
+ f"Client {get_ip(request)} has completed captcha session {captcha_session.id} challenge."
231
+ )
203
232
  return captcha_session
204
233
 
205
234
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sanic-security
3
- Version: 1.12.7
3
+ Version: 1.13.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/
@@ -64,7 +64,7 @@ Requires-Dist: cryptography; extra == "dev"
64
64
  * [Usage](#usage)
65
65
  * [Authentication](#authentication)
66
66
  * [Captcha](#captcha)
67
- * [Two Step Verification](#two-step-verification)
67
+ * [Two-Step Verification](#two-step-verification)
68
68
  * [Authorization](#authorization)
69
69
  * [Testing](#testing)
70
70
  * [Tortoise](#tortoise)
@@ -79,10 +79,11 @@ Requires-Dist: cryptography; extra == "dev"
79
79
  Sanic Security is an authentication, authorization, and verification library designed for use with [Sanic](https://github.com/huge-success/sanic).
80
80
 
81
81
  * Login, registration, and authentication with refresh mechanisms
82
+ * Role based authorization with wildcard permissions
82
83
  * Two-factor authentication
83
- * Captcha
84
84
  * Two-step verification
85
- * Role based authorization with wildcard permissions
85
+ * Captcha
86
+ * Logging
86
87
 
87
88
  Visit [security.na-stewart.com](https://security.na-stewart.com) for documentation.
88
89
 
@@ -145,12 +146,12 @@ You can load environment variables with a different prefix via `config.load_envi
145
146
  |---------------------------------------|------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
146
147
  | **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
148
  | **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** | strict | The SameSite attribute of session cookies. |
149
+ | **SESSION_SAMESITE** | Strict | The SameSite attribute of session cookies. |
149
150
  | **SESSION_SECURE** | True | The Secure attribute of session cookies. |
150
151
  | **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
152
  | **SESSION_DOMAIN** | None | The Domain attribute of session cookies. |
152
153
  | **SESSION_ENCODING_ALGORITHM** | HS256 | The algorithm used to encode and decode session JWT's. |
153
- | **SESSION_PREFIX** | token | Prefix attached to the beginning of session cookies. |
154
+ | **SESSION_PREFIX** | tkn | Prefix attached to the beginning of session cookies. |
154
155
  | **MAX_CHALLENGE_ATTEMPTS** | 5 | The maximum amount of session challenge attempts allowed. |
155
156
  | **CAPTCHA_SESSION_EXPIRATION** | 60 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
156
157
  | **CAPTCHA_FONT** | captcha-font.ttf | The file path to the font being used for captcha generation. |
@@ -166,19 +167,16 @@ You can load environment variables with a different prefix via `config.load_envi
166
167
  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
168
  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
169
 
169
- The tables in the below examples represent example [request form-data](https://sanicframework.org/en/guide/basics/request.html#form).
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
-
170
+ * Initialize sanic-security as follows:
177
171
  ```python
178
- create_initial_admin_account(app)
172
+ initialize_security(app)
179
173
  if __name__ == "__main__":
180
- app.run(host="127.0.0.1", port=8000)
174
+ app.run(host="127.0.0.1", port=8000, workers=1, debug=True)
181
175
  ```
176
+
177
+ The tables in the below examples represent example [request form-data](https://sanicframework.org/en/guide/basics/request.html#form).
178
+
179
+ ## Authentication
182
180
 
183
181
  * Registration (With two-step account verification)
184
182
 
@@ -321,17 +319,6 @@ async def on_authenticate(request):
321
319
  return response
322
320
  ```
323
321
 
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
322
  ## Captcha
336
323
 
337
324
  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=h0Yq2hWFv1GZoqhPQBmoJnqywGQd6fOYu7zyaqfv6wQ,14432
3
+ sanic_security/authorization.py,sha256=1SHx4cU_ibC0o_nEDDYURH_l_K6Q66M0SLzpRQrsIXc,7534
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=js2PkqJU6o46atslJ76n-_cYoY5iz5fbyiV0OFwoySo,8668
9
+ sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ sanic_security/test/server.py,sha256=79HaNIH1skWTrh2gIbh8WWVNxvYqPA5GlQ8AqRaCsXQ,12094
11
+ sanic_security/test/tests.py,sha256=bW5fHJfsCrg8eBmcSqVMLm0R5XRL1ou-XJJRgz09GOE,21993
12
+ sanic_security-1.13.1.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
13
+ sanic_security-1.13.1.dist-info/METADATA,sha256=7oqMMHZfK9wPEeR1gQ04FvUPqCgVdETi7LES-w16uEM,23011
14
+ sanic_security-1.13.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
15
+ sanic_security-1.13.1.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
16
+ sanic_security-1.13.1.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,,