sanic-security 1.12.0__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 (21) hide show
  1. {sanic_security-1.12.0/sanic_security.egg-info → sanic_security-1.12.1}/PKG-INFO +41 -27
  2. {sanic_security-1.12.0 → sanic_security-1.12.1}/README.md +39 -25
  3. {sanic_security-1.12.0 → sanic_security-1.12.1}/pyproject.toml +2 -2
  4. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/authentication.py +5 -5
  5. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/exceptions.py +5 -1
  6. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/models.py +4 -4
  7. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/test/server.py +13 -5
  8. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/test/tests.py +2 -0
  9. {sanic_security-1.12.0 → sanic_security-1.12.1/sanic_security.egg-info}/PKG-INFO +41 -27
  10. {sanic_security-1.12.0 → sanic_security-1.12.1}/LICENSE +0 -0
  11. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/__init__.py +0 -0
  12. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/authorization.py +0 -0
  13. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/configuration.py +0 -0
  14. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/test/__init__.py +0 -0
  15. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/utils.py +0 -0
  16. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security/verification.py +0 -0
  17. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security.egg-info/SOURCES.txt +0 -0
  18. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security.egg-info/dependency_links.txt +0 -0
  19. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security.egg-info/requires.txt +0 -0
  20. {sanic_security-1.12.0 → sanic_security-1.12.1}/sanic_security.egg-info/top_level.txt +0 -0
  21. {sanic_security-1.12.0 → sanic_security-1.12.1}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sanic-security
3
- Version: 1.12.0
4
- Summary: An effective, simple, and async security library for the Sanic framework.
3
+ Version: 1.12.1
4
+ Summary: An async security library for the Sanic framework.
5
5
  Author-email: Aidan Stewart <na.stewart365@gmail.com>
6
6
  Project-URL: Documentation, https://security.na-stewart.com/
7
7
  Project-URL: Repository, https://github.com/na-stewart/sanic-security
@@ -48,7 +48,7 @@ Requires-Dist: cryptography>=3.3.1; extra == "crypto"
48
48
  <p align="center">
49
49
  <h3 align="center">Sanic Security</h3>
50
50
  <p align="center">
51
- An effective, simple, and async security library for the Sanic framework.
51
+ An async security library for the Sanic framework.
52
52
  </p>
53
53
  </p>
54
54
 
@@ -77,7 +77,6 @@ Requires-Dist: cryptography>=3.3.1; extra == "crypto"
77
77
  ## About The Project
78
78
 
79
79
  Sanic Security is an authentication, authorization, and verification library designed for use with [Sanic](https://github.com/huge-success/sanic).
80
- This library contains a variety of features including:
81
80
 
82
81
  * Login, registration, and authentication with refresh mechanisms
83
82
  * Two-factor authentication
@@ -85,7 +84,7 @@ This library contains a variety of features including:
85
84
  * Two-step verification
86
85
  * Role based authorization with wildcard permissions
87
86
 
88
- Please visit [security.na-stewart.com](https://security.na-stewart.com) for documentation and [click here for a comprehensive implementation guide](https://blog.na-stewart.com/entry?id=3).
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).
89
88
 
90
89
  <!-- GETTING STARTED -->
91
90
  ## Getting Started
@@ -103,7 +102,7 @@ pip3 install sanic-security
103
102
 
104
103
  If you are planning on encoding or decoding JWTs using certain digital signature algorithms (like RSA or ECDSA which use
105
104
  the public secret and private secret), you will need to install the `cryptography` library. This can be installed explicitly, or
106
- as a required extra in the `sanic-security` requirement.
105
+ as an extra requirement.
107
106
 
108
107
  ```shell
109
108
  pip3 install sanic-security[crypto]
@@ -138,7 +137,7 @@ You can also use the update() method like on regular dictionaries.
138
137
  Any environment variables defined with the SANIC_SECURITY_ prefix will be applied to the config. For example, setting
139
138
  SANIC_SECURITY_SECRET will be loaded by the application automatically and fed into the SECRET config variable.
140
139
 
141
- 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.
142
141
 
143
142
  * Default configuration values:
144
143
 
@@ -181,7 +180,7 @@ This account can be logged into and has complete authoritative access. Login cre
181
180
  app.run(host="127.0.0.1", port=8000)
182
181
  ```
183
182
 
184
- * Registration (With Two-step Verification)
183
+ * Registration (With two-step account verification)
185
184
 
186
185
  Phone can be null or empty.
187
186
 
@@ -214,7 +213,7 @@ Verifies the client's account via two-step session code.
214
213
 
215
214
  | Key | Value |
216
215
  |----------|--------|
217
- | **code** | AJ8HGD |
216
+ | **code** | 197251 |
218
217
 
219
218
  ```python
220
219
  @app.post("api/security/verify")
@@ -223,7 +222,7 @@ async def on_verify(request):
223
222
  return json("You have verified your account and may login!", two_step_session.json)
224
223
  ```
225
224
 
226
- * Login (With Two-factor Authentication)
225
+ * Login (With two-factor authentication)
227
226
 
228
227
  Login credentials are retrieved via the Authorization header. Credentials are constructed by first combining the
229
228
  username and the password with a colon (aladdin:opensesame), and then by encoding the resulting string in base64
@@ -256,7 +255,7 @@ Fulfills client authentication session's second factor requirement via two-step
256
255
 
257
256
  | Key | Value |
258
257
  |----------|--------|
259
- | **code** | BG5KLP |
258
+ | **code** | 197251 |
260
259
 
261
260
  ```python
262
261
  @app.post("api/security/fulfill-2fa")
@@ -296,7 +295,7 @@ async def on_logout(request):
296
295
 
297
296
  * Authenticate
298
297
 
299
- New/Refreshed session automatically returned if expired during authentication, requires encoding.
298
+ New/Refreshed session will be returned if expired, requires encoding.
300
299
 
301
300
  ```python
302
301
  @app.post("api/security/auth")
@@ -311,15 +310,15 @@ async def on_authenticate(request):
311
310
  return response
312
311
  ```
313
312
 
314
- * 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)
315
314
 
316
- New/Refreshed session automatically returned if expired during authentication, requires encoding.
315
+ New/Refreshed session will be returned if expired, requires encoding.
317
316
 
318
317
  ```python
319
318
  @app.post("api/security/auth")
320
319
  @requires_authentication
321
320
  async def on_authenticate(request):
322
- authentication_session = request.ctx.authentication_session.json
321
+ authentication_session = request.ctx.authentication_session
323
322
  response = json(
324
323
  "You have been authenticated.",
325
324
  authentication_session.json,
@@ -329,6 +328,21 @@ async def on_authenticate(request):
329
328
  return response
330
329
  ```
331
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
344
+ ```
345
+
332
346
  ## Captcha
333
347
 
334
348
  A pre-existing font for captcha challenges is included in the Sanic Security repository. You may set your own font by
@@ -344,7 +358,7 @@ downloading a .ttf font and defining the file's path in the configuration.
344
358
  @app.get("api/security/captcha")
345
359
  async def on_captcha_img_request(request):
346
360
  captcha_session = await request_captcha(request)
347
- response = captcha_session.get_image() # Captcha: FV9NMQ
361
+ response = captcha_session.get_image() # Captcha: 192731
348
362
  captcha_session.encode(response)
349
363
  return response
350
364
  ```
@@ -353,7 +367,7 @@ async def on_captcha_img_request(request):
353
367
 
354
368
  | Key | Value |
355
369
  |-------------|--------|
356
- | **captcha** | FV9NMQ |
370
+ | **captcha** | 192731 |
357
371
 
358
372
  ```python
359
373
  @app.post("api/security/captcha")
@@ -362,11 +376,11 @@ async def on_captcha(request):
362
376
  return json("Captcha attempt successful!", captcha_session.json)
363
377
  ```
364
378
 
365
- * 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)
366
380
 
367
381
  | Key | Value |
368
382
  |-------------|--------|
369
- | **captcha** | FV9NMQ |
383
+ | **captcha** | 192731 |
370
384
 
371
385
  ```python
372
386
  @app.post("api/security/captcha")
@@ -388,9 +402,9 @@ Two-step verification should be integrated with other custom functionality. For
388
402
  ```python
389
403
  @app.post("api/security/two-step/request")
390
404
  async def on_two_step_request(request):
391
- two_step_session = await request_two_step_verification(request)
405
+ two_step_session = await request_two_step_verification(request) # Code = 197251
392
406
  await email_code(
393
- two_step_session.bearer.email, two_step_session.code # Code = 197251
407
+ two_step_session.bearer.email, two_step_session.code
394
408
  ) # Custom method for emailing verification code.
395
409
  response = json("Verification request successful!", two_step_session.json)
396
410
  two_step_session.encode(response)
@@ -402,9 +416,9 @@ async def on_two_step_request(request):
402
416
  ```python
403
417
  @app.post("api/security/two-step/resend")
404
418
  async def on_two_step_resend(request):
405
- two_step_session = await TwoStepSession.decode(request)
419
+ two_step_session = await TwoStepSession.decode(request) # Code = 197251
406
420
  await email_code(
407
- two_step_session.bearer.email, two_step_session.code # Code = 197251
421
+ two_step_session.bearer.email, two_step_session.code
408
422
  ) # Custom method for emailing verification code.
409
423
  return json("Verification code resend successful!", two_step_session.json)
410
424
  ```
@@ -413,7 +427,7 @@ async def on_two_step_resend(request):
413
427
 
414
428
  | Key | Value |
415
429
  |----------|--------|
416
- | **code** | DT6JZX |
430
+ | **code** | 197251 |
417
431
 
418
432
  ```python
419
433
  @app.post("api/security/two-step")
@@ -423,11 +437,11 @@ async def on_two_step_verification(request):
423
437
  return response
424
438
  ```
425
439
 
426
- * 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)
427
441
 
428
442
  | Key | Value |
429
443
  |----------|--------|
430
- | **code** | DT6JZX |
444
+ | **code** | 197251 |
431
445
 
432
446
  ```python
433
447
  @app.post("api/security/two-step")
@@ -492,7 +506,7 @@ async def on_check_roles(request):
492
506
  return text("Account is authorized.")
493
507
  ```
494
508
 
495
- * 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)
496
510
 
497
511
  ```python
498
512
  @app.post("api/security/roles")
@@ -17,7 +17,7 @@
17
17
  <p align="center">
18
18
  <h3 align="center">Sanic Security</h3>
19
19
  <p align="center">
20
- An effective, simple, and async security library for the Sanic framework.
20
+ An async security library for the Sanic framework.
21
21
  </p>
22
22
  </p>
23
23
 
@@ -46,7 +46,6 @@
46
46
  ## About The Project
47
47
 
48
48
  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
49
 
51
50
  * Login, registration, and authentication with refresh mechanisms
52
51
  * Two-factor authentication
@@ -54,7 +53,7 @@ This library contains a variety of features including:
54
53
  * Two-step verification
55
54
  * Role based authorization with wildcard permissions
56
55
 
57
- Please visit [security.na-stewart.com](https://security.na-stewart.com) for documentation and [click here for a comprehensive implementation guide](https://blog.na-stewart.com/entry?id=3).
56
+ 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).
58
57
 
59
58
  <!-- GETTING STARTED -->
60
59
  ## Getting Started
@@ -72,7 +71,7 @@ pip3 install sanic-security
72
71
 
73
72
  If you are planning on encoding or decoding JWTs using certain digital signature algorithms (like RSA or ECDSA which use
74
73
  the public secret and private secret), you will need to install the `cryptography` library. This can be installed explicitly, or
75
- as a required extra in the `sanic-security` requirement.
74
+ as an extra requirement.
76
75
 
77
76
  ```shell
78
77
  pip3 install sanic-security[crypto]
@@ -107,7 +106,7 @@ You can also use the update() method like on regular dictionaries.
107
106
  Any environment variables defined with the SANIC_SECURITY_ prefix will be applied to the config. For example, setting
108
107
  SANIC_SECURITY_SECRET will be loaded by the application automatically and fed into the SECRET config variable.
109
108
 
110
- You can load environment variables with a different prefix via calling the `config.load_environment_variables("NEW_PREFIX_")` method.
109
+ You can load environment variables with a different prefix via `config.load_environment_variables("NEW_PREFIX_")` method.
111
110
 
112
111
  * Default configuration values:
113
112
 
@@ -150,7 +149,7 @@ This account can be logged into and has complete authoritative access. Login cre
150
149
  app.run(host="127.0.0.1", port=8000)
151
150
  ```
152
151
 
153
- * Registration (With Two-step Verification)
152
+ * Registration (With two-step account verification)
154
153
 
155
154
  Phone can be null or empty.
156
155
 
@@ -183,7 +182,7 @@ Verifies the client's account via two-step session code.
183
182
 
184
183
  | Key | Value |
185
184
  |----------|--------|
186
- | **code** | AJ8HGD |
185
+ | **code** | 197251 |
187
186
 
188
187
  ```python
189
188
  @app.post("api/security/verify")
@@ -192,7 +191,7 @@ async def on_verify(request):
192
191
  return json("You have verified your account and may login!", two_step_session.json)
193
192
  ```
194
193
 
195
- * Login (With Two-factor Authentication)
194
+ * Login (With two-factor authentication)
196
195
 
197
196
  Login credentials are retrieved via the Authorization header. Credentials are constructed by first combining the
198
197
  username and the password with a colon (aladdin:opensesame), and then by encoding the resulting string in base64
@@ -225,7 +224,7 @@ Fulfills client authentication session's second factor requirement via two-step
225
224
 
226
225
  | Key | Value |
227
226
  |----------|--------|
228
- | **code** | BG5KLP |
227
+ | **code** | 197251 |
229
228
 
230
229
  ```python
231
230
  @app.post("api/security/fulfill-2fa")
@@ -265,7 +264,7 @@ async def on_logout(request):
265
264
 
266
265
  * Authenticate
267
266
 
268
- New/Refreshed session automatically returned if expired during authentication, requires encoding.
267
+ New/Refreshed session will be returned if expired, requires encoding.
269
268
 
270
269
  ```python
271
270
  @app.post("api/security/auth")
@@ -280,15 +279,15 @@ async def on_authenticate(request):
280
279
  return response
281
280
  ```
282
281
 
283
- * Requires Authentication (This method is not called directly and instead used as a decorator.)
282
+ * Requires Authentication (This method is not called directly and instead used as a decorator)
284
283
 
285
- New/Refreshed session automatically returned if expired during authentication, requires encoding.
284
+ New/Refreshed session will be returned if expired, requires encoding.
286
285
 
287
286
  ```python
288
287
  @app.post("api/security/auth")
289
288
  @requires_authentication
290
289
  async def on_authenticate(request):
291
- authentication_session = request.ctx.authentication_session.json
290
+ authentication_session = request.ctx.authentication_session
292
291
  response = json(
293
292
  "You have been authenticated.",
294
293
  authentication_session.json,
@@ -298,6 +297,21 @@ async def on_authenticate(request):
298
297
  return response
299
298
  ```
300
299
 
300
+ * Authentication Refresh Middleware
301
+
302
+ If it's inconvenient to encode the refreshed session during authentication, it can also be done automatically via middleware.
303
+
304
+ ```python
305
+ @app.on_response
306
+ async def authentication_refresh_encoder(request, response):
307
+ try:
308
+ authentication_session = request.ctx.authentication_session
309
+ if authentication_session.is_refresh:
310
+ authentication_session.encode(response)
311
+ except AttributeError:
312
+ pass
313
+ ```
314
+
301
315
  ## Captcha
302
316
 
303
317
  A pre-existing font for captcha challenges is included in the Sanic Security repository. You may set your own font by
@@ -313,7 +327,7 @@ downloading a .ttf font and defining the file's path in the configuration.
313
327
  @app.get("api/security/captcha")
314
328
  async def on_captcha_img_request(request):
315
329
  captcha_session = await request_captcha(request)
316
- response = captcha_session.get_image() # Captcha: FV9NMQ
330
+ response = captcha_session.get_image() # Captcha: 192731
317
331
  captcha_session.encode(response)
318
332
  return response
319
333
  ```
@@ -322,7 +336,7 @@ async def on_captcha_img_request(request):
322
336
 
323
337
  | Key | Value |
324
338
  |-------------|--------|
325
- | **captcha** | FV9NMQ |
339
+ | **captcha** | 192731 |
326
340
 
327
341
  ```python
328
342
  @app.post("api/security/captcha")
@@ -331,11 +345,11 @@ async def on_captcha(request):
331
345
  return json("Captcha attempt successful!", captcha_session.json)
332
346
  ```
333
347
 
334
- * Requires Captcha (This method is not called directly and instead used as a decorator.)
348
+ * Requires Captcha (This method is not called directly and instead used as a decorator)
335
349
 
336
350
  | Key | Value |
337
351
  |-------------|--------|
338
- | **captcha** | FV9NMQ |
352
+ | **captcha** | 192731 |
339
353
 
340
354
  ```python
341
355
  @app.post("api/security/captcha")
@@ -357,9 +371,9 @@ Two-step verification should be integrated with other custom functionality. For
357
371
  ```python
358
372
  @app.post("api/security/two-step/request")
359
373
  async def on_two_step_request(request):
360
- two_step_session = await request_two_step_verification(request)
374
+ two_step_session = await request_two_step_verification(request) # Code = 197251
361
375
  await email_code(
362
- two_step_session.bearer.email, two_step_session.code # Code = 197251
376
+ two_step_session.bearer.email, two_step_session.code
363
377
  ) # Custom method for emailing verification code.
364
378
  response = json("Verification request successful!", two_step_session.json)
365
379
  two_step_session.encode(response)
@@ -371,9 +385,9 @@ async def on_two_step_request(request):
371
385
  ```python
372
386
  @app.post("api/security/two-step/resend")
373
387
  async def on_two_step_resend(request):
374
- two_step_session = await TwoStepSession.decode(request)
388
+ two_step_session = await TwoStepSession.decode(request) # Code = 197251
375
389
  await email_code(
376
- two_step_session.bearer.email, two_step_session.code # Code = 197251
390
+ two_step_session.bearer.email, two_step_session.code
377
391
  ) # Custom method for emailing verification code.
378
392
  return json("Verification code resend successful!", two_step_session.json)
379
393
  ```
@@ -382,7 +396,7 @@ async def on_two_step_resend(request):
382
396
 
383
397
  | Key | Value |
384
398
  |----------|--------|
385
- | **code** | DT6JZX |
399
+ | **code** | 197251 |
386
400
 
387
401
  ```python
388
402
  @app.post("api/security/two-step")
@@ -392,11 +406,11 @@ async def on_two_step_verification(request):
392
406
  return response
393
407
  ```
394
408
 
395
- * Requires Two-step Verification (This method is not called directly and instead used as a decorator.)
409
+ * Requires Two-step Verification (This method is not called directly and instead used as a decorator)
396
410
 
397
411
  | Key | Value |
398
412
  |----------|--------|
399
- | **code** | DT6JZX |
413
+ | **code** | 197251 |
400
414
 
401
415
  ```python
402
416
  @app.post("api/security/two-step")
@@ -461,7 +475,7 @@ async def on_check_roles(request):
461
475
  return text("Account is authorized.")
462
476
  ```
463
477
 
464
- * Require Roles (This method is not called directly and instead used as a decorator.)
478
+ * Require Roles (This method is not called directly and instead used as a decorator)
465
479
 
466
480
  ```python
467
481
  @app.post("api/security/roles")
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sanic-security"
7
- version = "1.12.0"
7
+ version = "1.12.1"
8
8
  requires-python = ">=3.8"
9
9
  dependencies = [
10
10
  "tortoise-orm>=0.17.0",
@@ -14,7 +14,7 @@ dependencies = [
14
14
  "argon2-cffi>=20.1.0",
15
15
  "sanic>=21.3.0"
16
16
  ]
17
- description = "An effective, simple, and async security library for the Sanic framework."
17
+ description = "An async security library for the Sanic framework."
18
18
  authors = [
19
19
  { name="Aidan Stewart", email="na.stewart365@gmail.com" },
20
20
  ]
@@ -195,7 +195,7 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession:
195
195
  return authentication_session
196
196
 
197
197
 
198
- async def authenticate(request: Request) -> tuple[bool, AuthenticationSession]:
198
+ async def authenticate(request: Request) -> AuthenticationSession:
199
199
  """
200
200
  Validates client's authentication session and account. New/Refreshed session automatically returned
201
201
  if expired during authentication, requires encoding.
@@ -214,6 +214,7 @@ async def authenticate(request: Request) -> tuple[bool, AuthenticationSession]:
214
214
  UnverifiedError
215
215
  DisabledError
216
216
  SecondFactorRequiredError
217
+ ExpiredError
217
218
  """
218
219
  authentication_session = await AuthenticationSession.decode(request)
219
220
  try:
@@ -245,6 +246,7 @@ def requires_authentication(arg=None):
245
246
  DeactivatedError
246
247
  UnverifiedError
247
248
  DisabledError
249
+ ExpiredError
248
250
  """
249
251
 
250
252
  def decorator(func):
@@ -272,7 +274,7 @@ def create_initial_admin_account(app: Sanic) -> None:
272
274
  role = await Role.filter(name="Head Admin").get()
273
275
  except DoesNotExist:
274
276
  role = await Role.create(
275
- description="Has the ability to control any aspect of the API, assign sparingly.",
277
+ description="Has root abilities, assign sparingly.",
276
278
  permissions="*:*",
277
279
  name="Head Admin",
278
280
  )
@@ -283,9 +285,7 @@ def create_initial_admin_account(app: Sanic) -> None:
283
285
  await account.fetch_related("roles")
284
286
  if role not in account.roles:
285
287
  await account.roles.add(role)
286
- logger.warning(
287
- 'The initial admin account role "Head Admin" was removed and has been reinstated.'
288
- )
288
+ logger.warning("Initial admin account role has been reinstated.")
289
289
  except DoesNotExist:
290
290
  account = await Account.create(
291
291
  username="Head-Admin",
@@ -128,7 +128,11 @@ class DeactivatedError(SessionError):
128
128
  Raised when session is deactivated.
129
129
  """
130
130
 
131
- def __init__(self, message: str = "Session is deactivated.", code: int = 401):
131
+ def __init__(
132
+ self,
133
+ message: str = "Session has been deactivated or refreshed.",
134
+ code: int = 401,
135
+ ):
132
136
  super().__init__(message, code)
133
137
 
134
138
 
@@ -524,13 +524,13 @@ class AuthenticationSession(Session):
524
524
  Used to authenticate and identify a client.
525
525
 
526
526
  Attributes:
527
- requires_second_factor (bool): Determines if session requires a second factor.
528
527
  refresh_expiration_date (bool): Date and time the session can no longer be refreshed.
529
- is_refresh (bool): Will only be true when instantiated during refresh of expired session.
528
+ requires_second_factor (bool): Determines if session requires a second factor.
529
+ is_refresh (bool): Will only be true once when instantiated during refresh of expired session.
530
530
  """
531
531
 
532
- requires_second_factor: bool = fields.BooleanField(default=False)
533
532
  refresh_expiration_date: datetime.datetime = fields.DatetimeField(null=True)
533
+ requires_second_factor: bool = fields.BooleanField(default=False)
534
534
  is_refresh: bool = False
535
535
 
536
536
  def validate(self) -> None:
@@ -549,7 +549,7 @@ class AuthenticationSession(Session):
549
549
 
550
550
  async def refresh(self, request: Request):
551
551
  """
552
- Seamlessly creates new session if within refresh date.
552
+ Refreshes session if expired and within refresh date.
553
553
 
554
554
  Raises:
555
555
  DeletedError
@@ -19,7 +19,7 @@ from sanic_security.authorization import (
19
19
  check_roles,
20
20
  )
21
21
  from sanic_security.configuration import config as security_config
22
- from sanic_security.exceptions import SecurityError, CredentialsError
22
+ from sanic_security.exceptions import SecurityError
23
23
  from sanic_security.models import Account, CaptchaSession, AuthenticationSession
24
24
  from sanic_security.utils import json
25
25
  from sanic_security.verification import (
@@ -110,7 +110,7 @@ async def on_login(request):
110
110
  )
111
111
  two_step_session.encode(response)
112
112
  else:
113
- response = json("Login successful!", authentication_session.bearer.json)
113
+ response = json("Login successful!", authentication_session.json)
114
114
  authentication_session.encode(response)
115
115
  return response
116
116
 
@@ -148,7 +148,7 @@ async def on_logout(request):
148
148
  Logout of currently logged in account.
149
149
  """
150
150
  authentication_session = await logout(request)
151
- response = json("Logout successful!", authentication_session.bearer.json)
151
+ response = json("Logout successful!", authentication_session.json)
152
152
  return response
153
153
 
154
154
 
@@ -170,11 +170,19 @@ async def on_authenticate(request):
170
170
  "refresh": authentication_session.is_refresh,
171
171
  },
172
172
  )
173
- if authentication_session.is_refresh:
174
- request.ctx.authentication_session.encode(response)
175
173
  return response
176
174
 
177
175
 
176
+ @app.on_response
177
+ async def authentication_refresh_encoder(request, response):
178
+ try:
179
+ authentication_session = request.ctx.authentication_session
180
+ if authentication_session.is_refresh:
181
+ authentication_session.encode(response)
182
+ except AttributeError:
183
+ pass
184
+
185
+
178
186
  @app.post("api/test/auth/expire")
179
187
  @requires_authentication
180
188
  async def on_authentication_expire(request):
@@ -306,6 +306,8 @@ class LoginTest(TestCase):
306
306
  "http://127.0.0.1:8000/api/test/auth",
307
307
  )
308
308
  assert authenticate_response.status_code == 200, authenticate_response.text
309
+ logout_response = self.client.post("http://127.0.0.1:8000/api/test/auth/logout")
310
+ assert logout_response.status_code == 200, logout_response.text
309
311
 
310
312
 
311
313
  class VerificationTest(TestCase):
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sanic-security
3
- Version: 1.12.0
4
- Summary: An effective, simple, and async security library for the Sanic framework.
3
+ Version: 1.12.1
4
+ Summary: An async security library for the Sanic framework.
5
5
  Author-email: Aidan Stewart <na.stewart365@gmail.com>
6
6
  Project-URL: Documentation, https://security.na-stewart.com/
7
7
  Project-URL: Repository, https://github.com/na-stewart/sanic-security
@@ -48,7 +48,7 @@ Requires-Dist: cryptography>=3.3.1; extra == "crypto"
48
48
  <p align="center">
49
49
  <h3 align="center">Sanic Security</h3>
50
50
  <p align="center">
51
- An effective, simple, and async security library for the Sanic framework.
51
+ An async security library for the Sanic framework.
52
52
  </p>
53
53
  </p>
54
54
 
@@ -77,7 +77,6 @@ Requires-Dist: cryptography>=3.3.1; extra == "crypto"
77
77
  ## About The Project
78
78
 
79
79
  Sanic Security is an authentication, authorization, and verification library designed for use with [Sanic](https://github.com/huge-success/sanic).
80
- This library contains a variety of features including:
81
80
 
82
81
  * Login, registration, and authentication with refresh mechanisms
83
82
  * Two-factor authentication
@@ -85,7 +84,7 @@ This library contains a variety of features including:
85
84
  * Two-step verification
86
85
  * Role based authorization with wildcard permissions
87
86
 
88
- Please visit [security.na-stewart.com](https://security.na-stewart.com) for documentation and [click here for a comprehensive implementation guide](https://blog.na-stewart.com/entry?id=3).
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).
89
88
 
90
89
  <!-- GETTING STARTED -->
91
90
  ## Getting Started
@@ -103,7 +102,7 @@ pip3 install sanic-security
103
102
 
104
103
  If you are planning on encoding or decoding JWTs using certain digital signature algorithms (like RSA or ECDSA which use
105
104
  the public secret and private secret), you will need to install the `cryptography` library. This can be installed explicitly, or
106
- as a required extra in the `sanic-security` requirement.
105
+ as an extra requirement.
107
106
 
108
107
  ```shell
109
108
  pip3 install sanic-security[crypto]
@@ -138,7 +137,7 @@ You can also use the update() method like on regular dictionaries.
138
137
  Any environment variables defined with the SANIC_SECURITY_ prefix will be applied to the config. For example, setting
139
138
  SANIC_SECURITY_SECRET will be loaded by the application automatically and fed into the SECRET config variable.
140
139
 
141
- 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.
142
141
 
143
142
  * Default configuration values:
144
143
 
@@ -181,7 +180,7 @@ This account can be logged into and has complete authoritative access. Login cre
181
180
  app.run(host="127.0.0.1", port=8000)
182
181
  ```
183
182
 
184
- * Registration (With Two-step Verification)
183
+ * Registration (With two-step account verification)
185
184
 
186
185
  Phone can be null or empty.
187
186
 
@@ -214,7 +213,7 @@ Verifies the client's account via two-step session code.
214
213
 
215
214
  | Key | Value |
216
215
  |----------|--------|
217
- | **code** | AJ8HGD |
216
+ | **code** | 197251 |
218
217
 
219
218
  ```python
220
219
  @app.post("api/security/verify")
@@ -223,7 +222,7 @@ async def on_verify(request):
223
222
  return json("You have verified your account and may login!", two_step_session.json)
224
223
  ```
225
224
 
226
- * Login (With Two-factor Authentication)
225
+ * Login (With two-factor authentication)
227
226
 
228
227
  Login credentials are retrieved via the Authorization header. Credentials are constructed by first combining the
229
228
  username and the password with a colon (aladdin:opensesame), and then by encoding the resulting string in base64
@@ -256,7 +255,7 @@ Fulfills client authentication session's second factor requirement via two-step
256
255
 
257
256
  | Key | Value |
258
257
  |----------|--------|
259
- | **code** | BG5KLP |
258
+ | **code** | 197251 |
260
259
 
261
260
  ```python
262
261
  @app.post("api/security/fulfill-2fa")
@@ -296,7 +295,7 @@ async def on_logout(request):
296
295
 
297
296
  * Authenticate
298
297
 
299
- New/Refreshed session automatically returned if expired during authentication, requires encoding.
298
+ New/Refreshed session will be returned if expired, requires encoding.
300
299
 
301
300
  ```python
302
301
  @app.post("api/security/auth")
@@ -311,15 +310,15 @@ async def on_authenticate(request):
311
310
  return response
312
311
  ```
313
312
 
314
- * 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)
315
314
 
316
- New/Refreshed session automatically returned if expired during authentication, requires encoding.
315
+ New/Refreshed session will be returned if expired, requires encoding.
317
316
 
318
317
  ```python
319
318
  @app.post("api/security/auth")
320
319
  @requires_authentication
321
320
  async def on_authenticate(request):
322
- authentication_session = request.ctx.authentication_session.json
321
+ authentication_session = request.ctx.authentication_session
323
322
  response = json(
324
323
  "You have been authenticated.",
325
324
  authentication_session.json,
@@ -329,6 +328,21 @@ async def on_authenticate(request):
329
328
  return response
330
329
  ```
331
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
344
+ ```
345
+
332
346
  ## Captcha
333
347
 
334
348
  A pre-existing font for captcha challenges is included in the Sanic Security repository. You may set your own font by
@@ -344,7 +358,7 @@ downloading a .ttf font and defining the file's path in the configuration.
344
358
  @app.get("api/security/captcha")
345
359
  async def on_captcha_img_request(request):
346
360
  captcha_session = await request_captcha(request)
347
- response = captcha_session.get_image() # Captcha: FV9NMQ
361
+ response = captcha_session.get_image() # Captcha: 192731
348
362
  captcha_session.encode(response)
349
363
  return response
350
364
  ```
@@ -353,7 +367,7 @@ async def on_captcha_img_request(request):
353
367
 
354
368
  | Key | Value |
355
369
  |-------------|--------|
356
- | **captcha** | FV9NMQ |
370
+ | **captcha** | 192731 |
357
371
 
358
372
  ```python
359
373
  @app.post("api/security/captcha")
@@ -362,11 +376,11 @@ async def on_captcha(request):
362
376
  return json("Captcha attempt successful!", captcha_session.json)
363
377
  ```
364
378
 
365
- * 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)
366
380
 
367
381
  | Key | Value |
368
382
  |-------------|--------|
369
- | **captcha** | FV9NMQ |
383
+ | **captcha** | 192731 |
370
384
 
371
385
  ```python
372
386
  @app.post("api/security/captcha")
@@ -388,9 +402,9 @@ Two-step verification should be integrated with other custom functionality. For
388
402
  ```python
389
403
  @app.post("api/security/two-step/request")
390
404
  async def on_two_step_request(request):
391
- two_step_session = await request_two_step_verification(request)
405
+ two_step_session = await request_two_step_verification(request) # Code = 197251
392
406
  await email_code(
393
- two_step_session.bearer.email, two_step_session.code # Code = 197251
407
+ two_step_session.bearer.email, two_step_session.code
394
408
  ) # Custom method for emailing verification code.
395
409
  response = json("Verification request successful!", two_step_session.json)
396
410
  two_step_session.encode(response)
@@ -402,9 +416,9 @@ async def on_two_step_request(request):
402
416
  ```python
403
417
  @app.post("api/security/two-step/resend")
404
418
  async def on_two_step_resend(request):
405
- two_step_session = await TwoStepSession.decode(request)
419
+ two_step_session = await TwoStepSession.decode(request) # Code = 197251
406
420
  await email_code(
407
- two_step_session.bearer.email, two_step_session.code # Code = 197251
421
+ two_step_session.bearer.email, two_step_session.code
408
422
  ) # Custom method for emailing verification code.
409
423
  return json("Verification code resend successful!", two_step_session.json)
410
424
  ```
@@ -413,7 +427,7 @@ async def on_two_step_resend(request):
413
427
 
414
428
  | Key | Value |
415
429
  |----------|--------|
416
- | **code** | DT6JZX |
430
+ | **code** | 197251 |
417
431
 
418
432
  ```python
419
433
  @app.post("api/security/two-step")
@@ -423,11 +437,11 @@ async def on_two_step_verification(request):
423
437
  return response
424
438
  ```
425
439
 
426
- * 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)
427
441
 
428
442
  | Key | Value |
429
443
  |----------|--------|
430
- | **code** | DT6JZX |
444
+ | **code** | 197251 |
431
445
 
432
446
  ```python
433
447
  @app.post("api/security/two-step")
@@ -492,7 +506,7 @@ async def on_check_roles(request):
492
506
  return text("Account is authorized.")
493
507
  ```
494
508
 
495
- * 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)
496
510
 
497
511
  ```python
498
512
  @app.post("api/security/roles")
File without changes