sanic-security 1.11.7__tar.gz → 1.12.1__tar.gz

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.
Files changed (27) hide show
  1. sanic_security-1.12.1/LICENSE +21 -0
  2. sanic_security-1.11.7/README.md → sanic_security-1.12.1/PKG-INFO +118 -57
  3. sanic_security-1.11.7/PKG-INFO → sanic_security-1.12.1/README.md +591 -591
  4. sanic_security-1.12.1/pyproject.toml +31 -0
  5. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/authentication.py +144 -140
  6. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/authorization.py +50 -42
  7. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/configuration.py +26 -20
  8. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/exceptions.py +48 -22
  9. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/models.py +114 -64
  10. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/test/server.py +78 -26
  11. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/test/tests.py +82 -15
  12. sanic_security-1.12.1/sanic_security/utils.py +86 -0
  13. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/verification.py +20 -19
  14. sanic_security-1.12.1/sanic_security.egg-info/PKG-INFO +622 -0
  15. sanic_security-1.12.1/sanic_security.egg-info/SOURCES.txt +19 -0
  16. sanic_security-1.12.1/sanic_security.egg-info/dependency_links.txt +1 -0
  17. sanic_security-1.12.1/sanic_security.egg-info/requires.txt +16 -0
  18. sanic_security-1.12.1/sanic_security.egg-info/top_level.txt +1 -0
  19. sanic_security-1.12.1/setup.cfg +4 -0
  20. sanic_security-1.11.7/LICENSE +0 -661
  21. sanic_security-1.11.7/pyproject.toml +0 -28
  22. sanic_security-1.11.7/sanic_security/test/__pycache__/__init__.cpython-39.pyc +0 -0
  23. sanic_security-1.11.7/sanic_security/test/__pycache__/server.cpython-39.pyc +0 -0
  24. sanic_security-1.11.7/sanic_security/test/__pycache__/tests.cpython-39.pyc +0 -0
  25. sanic_security-1.11.7/sanic_security/utils.py +0 -82
  26. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/__init__.py +0 -0
  27. {sanic_security-1.11.7 → sanic_security-1.12.1}/sanic_security/test/__init__.py +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Nicholas Aidan Stewart
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,3 +1,34 @@
1
+ Metadata-Version: 2.1
2
+ Name: sanic-security
3
+ Version: 1.12.1
4
+ Summary: An async security library for the Sanic framework.
5
+ Author-email: Aidan Stewart <na.stewart365@gmail.com>
6
+ Project-URL: Documentation, https://security.na-stewart.com/
7
+ Project-URL: Repository, https://github.com/na-stewart/sanic-security
8
+ Keywords: security,authentication,authorization,verification,async,sanic
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Security
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: tortoise-orm>=0.17.0
18
+ Requires-Dist: pyjwt>=1.7.0
19
+ Requires-Dist: captcha>=0.4
20
+ Requires-Dist: pillow>=9.5.0
21
+ Requires-Dist: argon2-cffi>=20.1.0
22
+ Requires-Dist: sanic>=21.3.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: httpx; extra == "dev"
25
+ Requires-Dist: black; extra == "dev"
26
+ Requires-Dist: blacken-docs; extra == "dev"
27
+ Requires-Dist: pdoc3; extra == "dev"
28
+ Requires-Dist: cryptography; extra == "dev"
29
+ Provides-Extra: crypto
30
+ Requires-Dist: cryptography>=3.3.1; extra == "crypto"
31
+
1
32
  <!-- PROJECT SHIELDS -->
2
33
  <!--
3
34
  *** I'm using markdown "reference style" links for readability.
@@ -8,7 +39,7 @@
8
39
  -->
9
40
 
10
41
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
11
- [![Downloads](https://pepy.tech/badge/sanic-security)](https://pepy.tech/project/sanic-security)
42
+ [![Downloads](https://static.pepy.tech/badge/sanic-security)](https://pepy.tech/project/sanic-security)
12
43
  [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/sanic-security.svg)](https://anaconda.org/conda-forge/sanic-security)
13
44
 
14
45
 
@@ -17,7 +48,7 @@
17
48
  <p align="center">
18
49
  <h3 align="center">Sanic Security</h3>
19
50
  <p align="center">
20
- An effective, simple, and async security library for the Sanic framework.
51
+ An async security library for the Sanic framework.
21
52
  </p>
22
53
  </p>
23
54
 
@@ -46,17 +77,14 @@
46
77
  ## About The Project
47
78
 
48
79
  Sanic Security is an authentication, authorization, and verification library designed for use with [Sanic](https://github.com/huge-success/sanic).
49
- This library contains a variety of features including:
50
80
 
51
- * Login, registration, and authentication
81
+ * Login, registration, and authentication with refresh mechanisms
52
82
  * Two-factor authentication
53
83
  * Captcha
54
84
  * Two-step verification
55
85
  * Role based authorization with wildcard permissions
56
86
 
57
- Please visit [security.na-stewart.com](https://security.na-stewart.com) for documentation,
58
-
59
- and check out [blog.na-stewart.com](https://github.com/na-stewart/Aidans-Page) for an example implementation.
87
+ Please visit [security.na-stewart.com](https://security.na-stewart.com) for documentation and [here for an implementation guide](https://blog.na-stewart.com/entry?id=3).
60
88
 
61
89
  <!-- GETTING STARTED -->
62
90
  ## Getting Started
@@ -74,7 +102,7 @@ pip3 install sanic-security
74
102
 
75
103
  If you are planning on encoding or decoding JWTs using certain digital signature algorithms (like RSA or ECDSA which use
76
104
  the public secret and private secret), you will need to install the `cryptography` library. This can be installed explicitly, or
77
- as a required extra in the `sanic-security` requirement.
105
+ as an extra requirement.
78
106
 
79
107
  ```shell
80
108
  pip3 install sanic-security[crypto]
@@ -109,7 +137,7 @@ You can also use the update() method like on regular dictionaries.
109
137
  Any environment variables defined with the SANIC_SECURITY_ prefix will be applied to the config. For example, setting
110
138
  SANIC_SECURITY_SECRET will be loaded by the application automatically and fed into the SECRET config variable.
111
139
 
112
- You can load environment variables with a different prefix via calling the `config.load_environment_variables("NEW_PREFIX_")` method.
140
+ You can load environment variables with a different prefix via `config.load_environment_variables("NEW_PREFIX_")` method.
113
141
 
114
142
  * Default configuration values:
115
143
 
@@ -126,15 +154,16 @@ You can load environment variables with a different prefix via calling the `conf
126
154
  | **MAX_CHALLENGE_ATTEMPTS** | 5 | The maximum amount of session challenge attempts allowed. |
127
155
  | **CAPTCHA_SESSION_EXPIRATION** | 60 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
128
156
  | **CAPTCHA_FONT** | captcha-font.ttf | The file path to the font being used for captcha generation. |
129
- | **TWO_STEP_SESSION_EXPIRATION** | 200 | The amount of seconds till two step session expiration on creation. Setting to 0 will disable expiration. |
130
- | **AUTHENTICATION_SESSION_EXPIRATION** | 2692000 | The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration. |
157
+ | **TWO_STEP_SESSION_EXPIRATION** | 200 | The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration. |
158
+ | **AUTHENTICATION_SESSION_EXPIRATION** | 86400 | The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration. |
159
+ | **AUTHENTICATION_REFRESH_EXPIRATION** | 2592000 | The amount of seconds till authentication refresh expiration. |
131
160
  | **ALLOW_LOGIN_WITH_USERNAME** | False | Allows login via username and email. |
132
161
  | **INITIAL_ADMIN_EMAIL** | admin@example.com | Email used when creating the initial admin account. |
133
162
  | **INITIAL_ADMIN_PASSWORD** | admin123 | Password used when creating the initial admin account. |
134
163
 
135
164
  ## Usage
136
165
 
137
- 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 would be sent
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
138
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.
139
168
 
140
169
  The tables in the below examples represent example [request form-data](https://sanicframework.org/en/guide/basics/request.html#form).
@@ -151,7 +180,7 @@ This account can be logged into and has complete authoritative access. Login cre
151
180
  app.run(host="127.0.0.1", port=8000)
152
181
  ```
153
182
 
154
- * Registration (With Two-step Verification)
183
+ * Registration (With two-step account verification)
155
184
 
156
185
  Phone can be null or empty.
157
186
 
@@ -168,11 +197,11 @@ async def on_register(request):
168
197
  account = await register(request)
169
198
  two_step_session = await request_two_step_verification(request, account)
170
199
  await email_code(
171
- account.email, two_step_session.code # Code = AJ8HGD
200
+ account.email, two_step_session.code # Code = 197251
172
201
  ) # Custom method for emailing verification code.
173
202
  response = json(
174
203
  "Registration successful! Email verification required.",
175
- two_step_session.bearer.json,
204
+ two_step_session.json,
176
205
  )
177
206
  two_step_session.encode(response)
178
207
  return response
@@ -184,18 +213,16 @@ Verifies the client's account via two-step session code.
184
213
 
185
214
  | Key | Value |
186
215
  |----------|--------|
187
- | **code** | AJ8HGD |
216
+ | **code** | 197251 |
188
217
 
189
218
  ```python
190
219
  @app.post("api/security/verify")
191
220
  async def on_verify(request):
192
221
  two_step_session = await verify_account(request)
193
- return json(
194
- "You have verified your account and may login!", two_step_session.bearer.json
195
- )
222
+ return json("You have verified your account and may login!", two_step_session.json)
196
223
  ```
197
224
 
198
- * Login (With Two-factor Authentication)
225
+ * Login (With two-factor authentication)
199
226
 
200
227
  Login credentials are retrieved via the Authorization header. Credentials are constructed by first combining the
201
228
  username and the password with a colon (aladdin:opensesame), and then by encoding the resulting string in base64
@@ -211,11 +238,11 @@ async def on_login(request):
211
238
  request, authentication_session.bearer
212
239
  )
213
240
  await email_code(
214
- authentication_session.bearer.email, two_step_session.code # Code = BG5KLP
241
+ authentication_session.bearer.email, two_step_session.code # Code = 197251
215
242
  ) # Custom method for emailing verification code.
216
243
  response = json(
217
244
  "Login successful! Two-factor authentication required.",
218
- authentication_session.bearer.json,
245
+ authentication_session.json,
219
246
  )
220
247
  authentication_session.encode(response)
221
248
  two_step_session.encode(response)
@@ -228,15 +255,30 @@ Fulfills client authentication session's second factor requirement via two-step
228
255
 
229
256
  | Key | Value |
230
257
  |----------|--------|
231
- | **code** | BG5KLP |
258
+ | **code** | 197251 |
232
259
 
233
260
  ```python
234
- @app.post("api/security/validate-2fa")
261
+ @app.post("api/security/fulfill-2fa")
235
262
  async def on_two_factor_authentication(request):
236
263
  authentication_session = await fulfill_second_factor(request)
237
264
  response = json(
238
265
  "Authentication session second-factor fulfilled! You are now authenticated.",
239
- authentication_session.bearer.json,
266
+ authentication_session.json,
267
+ )
268
+ authentication_session.encode(response)
269
+ return response
270
+ ```
271
+
272
+ * Anonymous Login
273
+
274
+ Simply create a new session and encode it.
275
+
276
+ ```python
277
+ @app.post("api/security/login/anon")
278
+ async def on_anonymous_login(request):
279
+ authentication_session = await AuthenticationSession.new(request)
280
+ response = json(
281
+ "Anonymous client now associated with session!", authentication_session.json
240
282
  )
241
283
  authentication_session.encode(response)
242
284
  return response
@@ -248,32 +290,57 @@ async def on_two_factor_authentication(request):
248
290
  @app.post("api/security/logout")
249
291
  async def on_logout(request):
250
292
  authentication_session = await logout(request)
251
- response = json("Logout successful!", authentication_session.bearer.json)
252
- return response
293
+ return json("Logout successful!", authentication_session.json)
253
294
  ```
254
295
 
255
296
  * Authenticate
256
297
 
298
+ New/Refreshed session will be returned if expired, requires encoding.
299
+
257
300
  ```python
258
301
  @app.post("api/security/auth")
259
302
  async def on_authenticate(request):
260
303
  authentication_session = await authenticate(request)
261
- return json(
304
+ response = json(
262
305
  "You have been authenticated.",
263
- authentication_session.bearer.json,
306
+ authentication_session.json,
264
307
  )
308
+ if authentication_session.is_refresh:
309
+ authentication_session.encode(response)
310
+ return response
265
311
  ```
266
312
 
267
- * Requires Authentication (This method is not called directly and instead used as a decorator.)
313
+ * Requires Authentication (This method is not called directly and instead used as a decorator)
314
+
315
+ New/Refreshed session will be returned if expired, requires encoding.
268
316
 
269
317
  ```python
270
318
  @app.post("api/security/auth")
271
319
  @requires_authentication
272
320
  async def on_authenticate(request):
273
- return json(
321
+ authentication_session = request.ctx.authentication_session
322
+ response = json(
274
323
  "You have been authenticated.",
275
- request.ctx.authentication_session.bearer.json,
324
+ authentication_session.json,
276
325
  )
326
+ if authentication_session.is_refresh:
327
+ authentication_session.encode(response)
328
+ return response
329
+ ```
330
+
331
+ * Authentication Refresh Middleware
332
+
333
+ If it's inconvenient to encode the refreshed session during authentication, it can also be done automatically via middleware.
334
+
335
+ ```python
336
+ @app.on_response
337
+ async def authentication_refresh_encoder(request, response):
338
+ try:
339
+ authentication_session = request.ctx.authentication_session
340
+ if authentication_session.is_refresh:
341
+ authentication_session.encode(response)
342
+ except AttributeError:
343
+ pass
277
344
  ```
278
345
 
279
346
  ## Captcha
@@ -285,17 +352,13 @@ downloading a .ttf font and defining the file's path in the configuration.
285
352
 
286
353
  [Recommended Font](https://www.1001fonts.com/source-sans-pro-font.html)
287
354
 
288
- Captcha challenge example:
289
-
290
- [![Captcha image.](https://github.com/na-stewart/sanic-security/blob/main/images/captcha.png?raw=true)](https://github.com/na-stewart/sanic-security/blob/main/images/captcha.png?raw=true)
291
-
292
355
  * Request Captcha
293
356
 
294
357
  ```python
295
358
  @app.get("api/security/captcha")
296
359
  async def on_captcha_img_request(request):
297
360
  captcha_session = await request_captcha(request)
298
- response = captcha_session.get_image() # Captcha: FV9NMQ
361
+ response = captcha_session.get_image() # Captcha: 192731
299
362
  captcha_session.encode(response)
300
363
  return response
301
364
  ```
@@ -304,7 +367,7 @@ async def on_captcha_img_request(request):
304
367
 
305
368
  | Key | Value |
306
369
  |-------------|--------|
307
- | **captcha** | FV9NMQ |
370
+ | **captcha** | 192731 |
308
371
 
309
372
  ```python
310
373
  @app.post("api/security/captcha")
@@ -313,14 +376,14 @@ async def on_captcha(request):
313
376
  return json("Captcha attempt successful!", captcha_session.json)
314
377
  ```
315
378
 
316
- * Requires Captcha (This method is not called directly and instead used as a decorator.)
379
+ * Requires Captcha (This method is not called directly and instead used as a decorator)
317
380
 
318
381
  | Key | Value |
319
382
  |-------------|--------|
320
- | **captcha** | FV9NMQ |
383
+ | **captcha** | 192731 |
321
384
 
322
385
  ```python
323
- @app.post("ap/security/captcha")
386
+ @app.post("api/security/captcha")
324
387
  @requires_captcha
325
388
  async def on_captcha(request):
326
389
  return json("Captcha attempt successful!", request.ctx.captcha_session.json)
@@ -339,48 +402,46 @@ Two-step verification should be integrated with other custom functionality. For
339
402
  ```python
340
403
  @app.post("api/security/two-step/request")
341
404
  async def on_two_step_request(request):
342
- two_step_session = await request_two_step_verification(request)
405
+ two_step_session = await request_two_step_verification(request) # Code = 197251
343
406
  await email_code(
344
- account.email, two_step_session.code # Code = DT6JZX
407
+ two_step_session.bearer.email, two_step_session.code
345
408
  ) # Custom method for emailing verification code.
346
- response = json("Verification request successful!", two_step_session.bearer.json)
409
+ response = json("Verification request successful!", two_step_session.json)
347
410
  two_step_session.encode(response)
348
411
  return response
349
- ```
412
+ ```
350
413
 
351
414
  * Resend Two-step Verification Code
352
415
 
353
416
  ```python
354
417
  @app.post("api/security/two-step/resend")
355
418
  async def on_two_step_resend(request):
356
- two_step_session = await TwoStepSession.decode(request)
419
+ two_step_session = await TwoStepSession.decode(request) # Code = 197251
357
420
  await email_code(
358
- account.email, two_step_session.code # Code = DT6JZX
421
+ two_step_session.bearer.email, two_step_session.code
359
422
  ) # Custom method for emailing verification code.
360
- return json("Verification code resend successful!", two_step_session.bearer.json)
423
+ return json("Verification code resend successful!", two_step_session.json)
361
424
  ```
362
425
 
363
426
  * Two-step Verification
364
427
 
365
428
  | Key | Value |
366
429
  |----------|--------|
367
- | **code** | DT6JZX |
430
+ | **code** | 197251 |
368
431
 
369
432
  ```python
370
433
  @app.post("api/security/two-step")
371
434
  async def on_two_step_verification(request):
372
435
  two_step_session = await two_step_verification(request)
373
- response = json(
374
- "Two-step verification attempt successful!", two_step_session.bearer.json
375
- )
436
+ response = json("Two-step verification attempt successful!", two_step_session.json)
376
437
  return response
377
438
  ```
378
439
 
379
- * Requires Two-step Verification (This method is not called directly and instead used as a decorator.)
440
+ * Requires Two-step Verification (This method is not called directly and instead used as a decorator)
380
441
 
381
442
  | Key | Value |
382
443
  |----------|--------|
383
- | **code** | DT6JZX |
444
+ | **code** | 197251 |
384
445
 
385
446
  ```python
386
447
  @app.post("api/security/two-step")
@@ -388,7 +449,7 @@ async def on_two_step_verification(request):
388
449
  async def on_two_step_verification(request):
389
450
  response = json(
390
451
  "Two-step verification attempt successful!",
391
- request.ctx.two_step_session.bearer.json,
452
+ request.ctx.two_step_session.json,
392
453
  )
393
454
  return response
394
455
  ```
@@ -445,7 +506,7 @@ async def on_check_roles(request):
445
506
  return text("Account is authorized.")
446
507
  ```
447
508
 
448
- * Require Roles (This method is not called directly and instead used as a decorator.)
509
+ * Require Roles (This method is not called directly and instead used as a decorator)
449
510
 
450
511
  ```python
451
512
  @app.post("api/security/roles")
@@ -532,7 +593,7 @@ Contributions are what make the open source community such an amazing place to b
532
593
  <!-- LICENSE -->
533
594
  ## License
534
595
 
535
- Distributed under the GNU Affero General Public License v3.0. See `LICENSE` for more information.
596
+ Distributed under the MIT License. See `LICENSE` for more information.
536
597
 
537
598
  <!-- Versioning -->
538
599
  ## Versioning