sanic-security 1.13.3__py3-none-any.whl → 1.13.5__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.
@@ -2,7 +2,6 @@ import functools
2
2
  import re
3
3
  import warnings
4
4
 
5
- from argon2 import PasswordHasher
6
5
  from argon2.exceptions import VerifyMismatchError
7
6
  from sanic import Sanic
8
7
  from sanic.log import logger
@@ -18,7 +17,7 @@ from sanic_security.exceptions import (
18
17
  AuditWarning,
19
18
  )
20
19
  from sanic_security.models import Account, AuthenticationSession, Role, TwoStepSession
21
- from sanic_security.utils import get_ip
20
+ from sanic_security.utils import get_ip, password_hasher, secure_headers
22
21
 
23
22
  """
24
23
  Copyright (c) 2020-present Nicholas Aidan Stewart
@@ -42,8 +41,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
42
41
  SOFTWARE.
43
42
  """
44
43
 
45
- password_hasher = PasswordHasher()
46
-
47
44
 
48
45
  async def register(
49
46
  request: Request, verified: bool = False, disabled: bool = False
@@ -193,8 +190,7 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession:
193
190
  authentication_session.requires_second_factor = False
194
191
  await authentication_session.save(update_fields=["requires_second_factor"])
195
192
  logger.info(
196
- f"Client {get_ip(request)} has fulfilled authentication session {authentication_session.id} "
197
- "second factor."
193
+ f"Client {get_ip(request)} has fulfilled authentication session {authentication_session.id} second factor."
198
194
  )
199
195
  return authentication_session
200
196
 
@@ -288,7 +284,7 @@ def validate_password(password: str) -> str:
288
284
 
289
285
  def initialize_security(app: Sanic, create_root=True) -> None:
290
286
  """
291
- Audits configuration, creates root administrator account, and attaches refresh encoder middleware.
287
+ Audits configuration, creates root administrator account, and attaches response handler middleware.
292
288
 
293
289
  Args:
294
290
  app (Sanic): The main Sanic application instance.
@@ -296,34 +292,34 @@ def initialize_security(app: Sanic, create_root=True) -> None:
296
292
  """
297
293
 
298
294
  @app.on_response
299
- async def refresh_encoder_middleware(request, response):
295
+ async def response_handler_middleware(request, response):
300
296
  if hasattr(request.ctx, "authentication_session"):
301
- authentication_session = request.ctx.authentication_session
302
- if authentication_session.is_refresh:
303
- authentication_session.encode(response)
297
+ secure_headers.set_headers(response)
298
+ if request.ctx.authentication_session.is_refresh:
299
+ request.ctx.authentication_session.encode(response)
304
300
 
305
301
  @app.listener("before_server_start")
306
302
  async def audit_configuration(app, loop):
307
303
  if security_config.SECRET == DEFAULT_CONFIG["SECRET"]:
308
- warnings.warn("Secret should be changed from default.", AuditWarning)
304
+ warnings.warn("Secret should be changed from default.", AuditWarning, 2)
309
305
  if not security_config.SESSION_HTTPONLY:
310
- warnings.warn("HttpOnly should be enabled.", AuditWarning)
306
+ warnings.warn("HttpOnly should be enabled.", AuditWarning, 2)
311
307
  if not security_config.SESSION_SECURE:
312
- warnings.warn("Secure should be enabled.", AuditWarning)
308
+ warnings.warn("Secure should be enabled.", AuditWarning, 2)
313
309
  if (
314
310
  not security_config.SESSION_SAMESITE
315
311
  or security_config.SESSION_SAMESITE.lower() == "none"
316
312
  ):
317
- warnings.warn("SameSite should not be none.", AuditWarning)
313
+ warnings.warn("SameSite should not be none.", AuditWarning, 2)
318
314
  if not security_config.SESSION_DOMAIN:
319
- warnings.warn("Domain should not be none.", AuditWarning)
315
+ warnings.warn("Domain should not be none.", AuditWarning, 2)
320
316
  if (
321
317
  create_root
322
318
  and security_config.INITIAL_ADMIN_EMAIL
323
319
  == DEFAULT_CONFIG["INITIAL_ADMIN_EMAIL"]
324
320
  ):
325
321
  warnings.warn(
326
- "Initial admin email should be changed from default.", AuditWarning
322
+ "Initial admin email should be changed from default.", AuditWarning, 2
327
323
  )
328
324
  if (
329
325
  create_root
@@ -331,7 +327,9 @@ def initialize_security(app: Sanic, create_root=True) -> None:
331
327
  == DEFAULT_CONFIG["INITIAL_ADMIN_PASSWORD"]
332
328
  ):
333
329
  warnings.warn(
334
- "Initial admin password should be changed from default.", AuditWarning
330
+ "Initial admin password should be changed from default.",
331
+ AuditWarning,
332
+ 2,
335
333
  )
336
334
 
337
335
  @app.listener("before_server_start")
@@ -71,7 +71,7 @@ async def check_permissions(
71
71
  if fnmatch(required_permission, role_permission):
72
72
  return authentication_session
73
73
  logger.warning(
74
- f"Client {get_ip(request)} with account {authentication_session.bearer.id} attempted an unauthorized action. "
74
+ f"Client {get_ip(request)} with account {authentication_session.bearer.id} attempted an unauthorized action."
75
75
  )
76
76
  raise AuthorizationError("Insufficient permissions required for this action.")
77
77
 
@@ -123,14 +123,16 @@ async def assign_role(
123
123
  Args:
124
124
  name (str): The name of the role associated with the account.
125
125
  account (Account): The account associated with the created role.
126
- permissions (str): The permissions of the role associated with the account. Permissions must be separated via comma and in wildcard format.
126
+ permissions (str): The permissions of the role associated with the account. Permissions must be separated via ", " and in wildcard format.
127
127
  description (str): The description of the role associated with the account.
128
128
  """
129
129
  try:
130
130
  role = await Role.filter(name=name).get()
131
131
  except DoesNotExist:
132
132
  role = await Role.create(
133
- description=description, permissions=permissions, name=name
133
+ name=name,
134
+ description=description,
135
+ permissions=permissions,
134
136
  )
135
137
  await account.roles.add(role)
136
138
  return role
@@ -36,6 +36,7 @@ DEFAULT_CONFIG = {
36
36
  "MAX_CHALLENGE_ATTEMPTS": 5,
37
37
  "CAPTCHA_SESSION_EXPIRATION": 60,
38
38
  "CAPTCHA_FONT": "captcha-font.ttf",
39
+ "CAPTCHA_VOICE": "captcha-voice/",
39
40
  "TWO_STEP_SESSION_EXPIRATION": 300,
40
41
  "AUTHENTICATION_SESSION_EXPIRATION": 86400,
41
42
  "AUTHENTICATION_REFRESH_EXPIRATION": 604800,
@@ -62,6 +63,7 @@ class Config(dict):
62
63
  MAX_CHALLENGE_ATTEMPTS (str): The maximum amount of session challenge attempts allowed.
63
64
  CAPTCHA_SESSION_EXPIRATION (int): The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration.
64
65
  CAPTCHA_FONT (str): The file path to the font being used for captcha generation.
66
+ CAPTCHA_VOICE (str): The directory of the voice library being used for audio captcha generation.
65
67
  TWO_STEP_SESSION_EXPIRATION (int): The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration.
66
68
  AUTHENTICATION_SESSION_EXPIRATION (int): The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration.
67
69
  AUTHENTICATION_REFRESH_EXPIRATION (int): The amount of seconds till authentication session refresh expiration. Setting to 0 will disable refresh mechanism.
@@ -82,6 +84,7 @@ class Config(dict):
82
84
  MAX_CHALLENGE_ATTEMPTS: int
83
85
  CAPTCHA_SESSION_EXPIRATION: int
84
86
  CAPTCHA_FONT: str
87
+ CAPTCHA_VOICE: str
85
88
  TWO_STEP_SESSION_EXPIRATION: int
86
89
  AUTHENTICATION_SESSION_EXPIRATION: int
87
90
  AUTHENTICATION_REFRESH_EXPIRATION: int
sanic_security/models.py CHANGED
@@ -1,11 +1,9 @@
1
1
  import base64
2
2
  import datetime
3
3
  import re
4
- from io import BytesIO
5
4
  from typing import Union
6
5
 
7
6
  import jwt
8
- from captcha.image import ImageCaptcha
9
7
  from jwt import DecodeError
10
8
  from sanic.request import Request
11
9
  from sanic.response import HTTPResponse, raw
@@ -15,7 +13,13 @@ from tortoise.validators import RegexValidator
15
13
 
16
14
  from sanic_security.configuration import config as security_config
17
15
  from sanic_security.exceptions import *
18
- from sanic_security.utils import get_ip, get_code, get_expiration_date
16
+ from sanic_security.utils import (
17
+ get_ip,
18
+ get_code,
19
+ get_expiration_date,
20
+ image_generator,
21
+ audio_generator,
22
+ )
19
23
 
20
24
  """
21
25
  Copyright (c) 2020-present Nicholas Aidan Stewart
@@ -68,7 +72,7 @@ class BaseModel(Model):
68
72
  @property
69
73
  def json(self) -> dict:
70
74
  """
71
- A JSON serializable dict to be used in a HTTP request or response.
75
+ A JSON serializable dict to be used in an HTTP request or response.
72
76
 
73
77
  Example:
74
78
  Below is an example of this method returning a dict to be used for JSON serialization.
@@ -119,7 +123,7 @@ class Account(BaseModel):
119
123
  )
120
124
  phone: str = fields.CharField(
121
125
  unique=True,
122
- max_length=14,
126
+ max_length=15,
123
127
  null=True,
124
128
  validators=[
125
129
  RegexValidator(r"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$", re.I)
@@ -189,9 +193,8 @@ class Account(BaseModel):
189
193
  NotFoundError
190
194
  """
191
195
  try:
192
- account = await Account.filter(email=email, deleted=False).get()
193
- return account
194
- except DoesNotExist:
196
+ return await Account.filter(email=email, deleted=False).get()
197
+ except (DoesNotExist, ValidationError):
195
198
  raise NotFoundError("Account with this email does not exist.")
196
199
 
197
200
  @staticmethod
@@ -209,8 +212,7 @@ class Account(BaseModel):
209
212
  NotFoundError
210
213
  """
211
214
  try:
212
- account = await Account.filter(username=username, deleted=False).get()
213
- return account
215
+ return await Account.filter(username=username, deleted=False).get()
214
216
  except (DoesNotExist, ValidationError):
215
217
  raise NotFoundError("Account with this username does not exist.")
216
218
 
@@ -230,7 +232,7 @@ class Account(BaseModel):
230
232
  """
231
233
  try:
232
234
  account = await Account.get_via_email(credential)
233
- except (NotFoundError, ValidationError) as e:
235
+ except NotFoundError as e:
234
236
  if security_config.ALLOW_LOGIN_WITH_USERNAME:
235
237
  account = await Account.get_via_username(credential)
236
238
  else:
@@ -281,9 +283,8 @@ class Account(BaseModel):
281
283
  NotFoundError
282
284
  """
283
285
  try:
284
- account = await Account.filter(phone=phone, deleted=False).get()
285
- return account
286
- except DoesNotExist:
286
+ return await Account.filter(phone=phone, deleted=False).get()
287
+ except (DoesNotExist, ValidationError):
287
288
  raise NotFoundError("Account with this phone number does not exist.")
288
289
 
289
290
 
@@ -574,10 +575,21 @@ class CaptchaSession(VerificationSession):
574
575
  Returns:
575
576
  captcha_image
576
577
  """
577
- image = ImageCaptcha(190, 90, fonts=[security_config.CAPTCHA_FONT])
578
- with BytesIO() as output:
579
- image.generate_image(self.code).save(output, format="JPEG")
580
- return raw(output.getvalue(), content_type="image/jpeg")
578
+ return raw(
579
+ image_generator.generate(self.code, "jpeg").getvalue(),
580
+ content_type="image/jpeg",
581
+ )
582
+
583
+ def get_audio(self) -> HTTPResponse:
584
+ """
585
+ Retrieves captcha audio file.
586
+
587
+ Returns:
588
+ captcha_audio
589
+ """
590
+ return raw(
591
+ bytes(audio_generator.generate(self.code)), content_type="audio/mpeg"
592
+ )
581
593
 
582
594
  class Meta:
583
595
  table = "captcha_session"
@@ -195,9 +195,14 @@ async def on_captcha_request(request):
195
195
  async def on_captcha_image(request):
196
196
  """Request captcha image."""
197
197
  captcha_session = await CaptchaSession.decode(request)
198
- response = captcha_session.get_image()
199
- captcha_session.encode(response)
200
- return response
198
+ return captcha_session.get_image()
199
+
200
+
201
+ @app.get("api/test/capt/audio")
202
+ async def on_captcha_audio(request):
203
+ """Request captcha audio."""
204
+ captcha_session = await CaptchaSession.decode(request)
205
+ return captcha_session.get_audio()
201
206
 
202
207
 
203
208
  @app.post("api/test/capt")
sanic_security/utils.py CHANGED
@@ -2,8 +2,14 @@ import datetime
2
2
  import random
3
3
  import string
4
4
 
5
+ from argon2 import PasswordHasher
6
+ from captcha.audio import AudioCaptcha
7
+ from captcha.image import ImageCaptcha
5
8
  from sanic.request import Request
6
9
  from sanic.response import json as sanic_json, HTTPResponse
10
+ from secure import Secure
11
+
12
+ from sanic_security.configuration import config
7
13
 
8
14
  """
9
15
  Copyright (c) 2020-Present Nicholas Aidan Stewart
@@ -27,6 +33,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
33
  SOFTWARE.
28
34
  """
29
35
 
36
+ image_generator = ImageCaptcha(
37
+ 190, 90, fonts=config.CAPTCHA_FONT.replace(" ", "").split(",")
38
+ )
39
+ audio_generator = AudioCaptcha(voicedir=config.CAPTCHA_VOICE)
40
+ password_hasher = PasswordHasher()
41
+ secure_headers = Secure.with_default_headers()
42
+
30
43
 
31
44
  def get_ip(request: Request) -> str:
32
45
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sanic-security
3
- Version: 1.13.3
3
+ Version: 1.13.5
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,6 +20,7 @@ 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
23
24
  Provides-Extra: crypto
24
25
  Requires-Dist: cryptography>=3.3.1; extra == "crypto"
25
26
  Provides-Extra: dev
@@ -63,7 +64,7 @@ Requires-Dist: cryptography; extra == "dev"
63
64
  * [Configuration](#configuration)
64
65
  * [Usage](#usage)
65
66
  * [Authentication](#authentication)
66
- * [Captcha](#captcha)
67
+ * [CAPTCHA](#captcha)
67
68
  * [Two-step Verification](#two-step-verification)
68
69
  * [Authorization](#authorization)
69
70
  * [Testing](#testing)
@@ -76,21 +77,22 @@ Requires-Dist: cryptography; extra == "dev"
76
77
  <!-- ABOUT THE PROJECT -->
77
78
  ## About The Project
78
79
 
79
- Sanic Security is an authentication, authorization, and verification library designed for use with [Sanic](https://github.com/huge-success/sanic).
80
+ Sanic Security is an authentication, authorization, and verification library designed for use with the
81
+ [Sanic](https://github.com/huge-success/sanic) framework.
80
82
 
81
83
  * Login, registration, and authentication with refresh mechanisms
82
84
  * Role based authorization with wildcard permissions
85
+ * Image & audio CAPTCHA
83
86
  * Two-factor authentication
84
87
  * Two-step verification
85
- * Logging & Auditing
86
- * Captcha
88
+ * Logging & auditing
87
89
 
88
90
  Visit [security.na-stewart.com](https://security.na-stewart.com) for documentation.
89
91
 
90
92
  <!-- GETTING STARTED -->
91
93
  ## Getting Started
92
94
 
93
- In order to get started, please install [Pip](https://pypi.org/).
95
+ In order to get started, please install [PyPI](https://pypi.org/).
94
96
 
95
97
  ### Installation
96
98
 
@@ -109,14 +111,9 @@ as an extra requirement.
109
111
  pip3 install sanic-security[crypto]
110
112
  ````
111
113
 
112
- * For developers, fork Sanic Security and install development dependencies.
113
- ```shell
114
- pip3 install -e ".[dev]"
115
- ````
116
-
117
114
  * Update sanic-security if already installed.
118
115
  ```shell
119
- pip3 install --upgrade sanic-security
116
+ pip3 install sanic-security --upgrade
120
117
  ```
121
118
 
122
119
  ### Configuration
@@ -154,7 +151,8 @@ You can load environment variables with a different prefix via `config.load_envi
154
151
  | **SESSION_PREFIX** | tkn | Prefix attached to the beginning of session cookies. |
155
152
  | **MAX_CHALLENGE_ATTEMPTS** | 5 | The maximum amount of session challenge attempts allowed. |
156
153
  | **CAPTCHA_SESSION_EXPIRATION** | 60 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
157
- | **CAPTCHA_FONT** | captcha-font.ttf | The file path to the font being used for captcha generation. |
154
+ | **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
+ | **CAPTCHA_VOICE** | captcha-voice/ | The directory of the voice library being used for audio captcha generation. |
158
156
  | **TWO_STEP_SESSION_EXPIRATION** | 200 | The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration. |
159
157
  | **AUTHENTICATION_SESSION_EXPIRATION** | 86400 | The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration. |
160
158
  | **AUTHENTICATION_REFRESH_EXPIRATION** | 604800 | The amount of seconds till authentication refresh expiration. Setting to 0 will disable refresh mechanism. |
@@ -319,16 +317,40 @@ async def on_authenticate(request):
319
317
  return response
320
318
  ```
321
319
 
322
- ## Captcha
320
+ ## CAPTCHA
321
+
322
+ Protects against spam and malicious activities by ensuring that only real humans can complete certain actions, like
323
+ submitting a form or creating an account.
323
324
 
324
- A pre-existing font for captcha challenges is included in the Sanic Security repository. You may set your own font by
325
- downloading a .ttf font and defining the file's path in the configuration.
325
+ * Fonts
326
+
327
+ A font for captcha challenges is included in the repository. You can set a custom font by downloading a .ttf file and
328
+ specifying its path in the configuration.
326
329
 
327
330
  [1001 Free Fonts](https://www.1001fonts.com/)
328
331
 
329
- [Recommended Font](https://www.1001fonts.com/source-sans-pro-font.html)
332
+ * Voice Library
333
+
334
+ A voice library for captcha challenges is included in the repository. You can generate your own using `espeak` and
335
+ `ffmpeg`, then specify the library's directory in the configuration.
330
336
 
331
- * Request Captcha
337
+ ```bash
338
+ # Set the language code
339
+ export ESLANG=en
340
+
341
+ # Create a directory for the specified language code
342
+ mkdir "$ESLANG"
343
+
344
+ # Loop through each character (A-Z, 0-9) and create a directory for each
345
+ for i in {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,0,1,2,3,4,5,6,7,8,9}; do
346
+ mkdir "$ESLANG/$i"
347
+ espeak -a 150 -s 100 -p 15 -v "$ESLANG" "$i" -w "$ESLANG/$i/orig_default.wav"
348
+ ffmpeg -i "$ESLANG/$i/orig_default.wav" -ar 8000 -ac 1 -acodec pcm_u8 "$ESLANG/$i/default.wav"
349
+ rm "$ESLANG/$i/orig_default.wav"
350
+ done
351
+ ```
352
+
353
+ * Request CAPTCHA
332
354
 
333
355
  ```python
334
356
  @app.get("api/security/captcha")
@@ -339,7 +361,16 @@ async def on_captcha_img_request(request):
339
361
  return response
340
362
  ```
341
363
 
342
- * Captcha
364
+ * Request CAPTCHA Audio
365
+
366
+ ```python
367
+ @app.get("api/security/captcha/audio")
368
+ async def on_captcha_audio_request(request):
369
+ captcha_session = await CaptchaSession.decode(request)
370
+ return captcha_session.get_audio() # Captcha: LJ0F3U
371
+ ```
372
+
373
+ * Attempt CAPTCHA
343
374
 
344
375
  | Key | Value |
345
376
  |-------------|--------|
@@ -352,7 +383,7 @@ async def on_captcha(request):
352
383
  return json("Captcha attempt successful!", captcha_session.json)
353
384
  ```
354
385
 
355
- * Requires Captcha (This method is not called directly and instead used as a decorator)
386
+ * Requires CAPTCHA (This method is not called directly and instead used as a decorator)
356
387
 
357
388
  | Key | Value |
358
389
  |-------------|--------|
@@ -367,7 +398,7 @@ async def on_captcha(request):
367
398
 
368
399
  ## Two-step Verification
369
400
 
370
- Two-step verification should be integrated with other custom functionality. For example, account verification during registration.
401
+ Two-step verification should be integrated with other custom functionalities, such as account verification during registration.
371
402
 
372
403
  * Request Two-step Verification
373
404
 
@@ -399,7 +430,7 @@ async def on_two_step_resend(request):
399
430
  return json("Verification code resend successful!", two_step_session.json)
400
431
  ```
401
432
 
402
- * Two-step Verification
433
+ * Attempt Two-step Verification
403
434
 
404
435
  | Key | Value |
405
436
  |----------|--------|
@@ -442,13 +473,14 @@ user's account; this simplifies common operations, such as adding a user, or cha
442
473
 
443
474
  Wildcard permissions support the concept of multiple levels or parts. For example, you could grant a user the permission
444
475
  `printer:query`, `printer:query,delete`, or `printer:*`.
476
+
445
477
  * Assign Role
446
478
 
447
479
  ```python
448
480
  await assign_role(
449
481
  "Chat Room Moderator",
450
482
  account,
451
- "channels:view,delete, account:suspend,mute, voice:*",
483
+ "channels:view,delete, voice:*, account:suspend,mute",
452
484
  "Can read and delete messages in all chat rooms, suspend and mute accounts, and control voice chat.",
453
485
  )
454
486
  ```
@@ -497,7 +529,7 @@ async def on_check_roles(request):
497
529
 
498
530
  * Make sure the test Sanic instance (`test/server.py`) is running on your machine.
499
531
 
500
- * Run the unit test client (`test/tests.py`) for results.
532
+ * Run the test client (`test/tests.py`) for results.
501
533
 
502
534
  ## Tortoise
503
535
 
@@ -0,0 +1,16 @@
1
+ sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ sanic_security/authentication.py,sha256=SbPFze7s86xDsKOwoy37nGB8xffK3pSHGnmGUdlnexA,13225
3
+ sanic_security/authorization.py,sha256=ddJWqGJbFIqII5pUW5SxI7h4EyVB-EhrbGM7jsQutOI,7559
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=js2PkqJU6o46atslJ76n-_cYoY5iz5fbyiV0OFwoySo,8668
9
+ sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ sanic_security/test/server.py,sha256=Rh_L12HPCfagvAyqkHziBD1C4WHAKZ9ht4mTCpX2Yik,12240
11
+ sanic_security/test/tests.py,sha256=bW5fHJfsCrg8eBmcSqVMLm0R5XRL1ou-XJJRgz09GOE,21993
12
+ sanic_security-1.13.5.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
13
+ sanic_security-1.13.5.dist-info/METADATA,sha256=WBdJdWbBOUphyj-RaEioPjEl8ksycafckhJTF2COCQU,24248
14
+ sanic_security-1.13.5.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
15
+ sanic_security-1.13.5.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
16
+ sanic_security-1.13.5.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- sanic_security/authentication.py,sha256=Rc2QqAjY17EialuML-wAThIrOxK8FKIFq927bst70IU,13218
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=XF9u3RIU76M19QzNiM4C7LoLHe4uv6tfcELGH4W7L5k,22637
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.3.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
13
- sanic_security-1.13.3.dist-info/METADATA,sha256=PUPuAXTXIRtjpdBzYWl9Pz8S3m-VdJB2skIEnHqQdGM,23022
14
- sanic_security-1.13.3.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
15
- sanic_security-1.13.3.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
16
- sanic_security-1.13.3.dist-info/RECORD,,