sanic-security 1.13.5__py3-none-any.whl → 1.14.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.
- sanic_security/authentication.py +11 -8
- sanic_security/authorization.py +4 -6
- sanic_security/test/server.py +10 -15
- sanic_security/verification.py +4 -2
- {sanic_security-1.13.5.dist-info → sanic_security-1.14.1.dist-info}/METADATA +11 -44
- sanic_security-1.14.1.dist-info/RECORD +16 -0
- {sanic_security-1.13.5.dist-info → sanic_security-1.14.1.dist-info}/WHEEL +1 -1
- sanic_security-1.13.5.dist-info/RECORD +0 -16
- {sanic_security-1.13.5.dist-info → sanic_security-1.14.1.dist-info}/LICENSE +0 -0
- {sanic_security-1.13.5.dist-info → sanic_security-1.14.1.dist-info}/top_level.txt +0 -0
sanic_security/authentication.py
CHANGED
@@ -223,7 +223,7 @@ async def authenticate(request: Request) -> AuthenticationSession:
|
|
223
223
|
authentication_session.bearer.validate()
|
224
224
|
except ExpiredError:
|
225
225
|
authentication_session = await authentication_session.refresh(request)
|
226
|
-
request.ctx.
|
226
|
+
request.ctx.session = authentication_session
|
227
227
|
return authentication_session
|
228
228
|
|
229
229
|
|
@@ -291,13 +291,6 @@ def initialize_security(app: Sanic, create_root=True) -> None:
|
|
291
291
|
create_root (bool): Determines root account creation on initialization.
|
292
292
|
"""
|
293
293
|
|
294
|
-
@app.on_response
|
295
|
-
async def response_handler_middleware(request, response):
|
296
|
-
if hasattr(request.ctx, "authentication_session"):
|
297
|
-
secure_headers.set_headers(response)
|
298
|
-
if request.ctx.authentication_session.is_refresh:
|
299
|
-
request.ctx.authentication_session.encode(response)
|
300
|
-
|
301
294
|
@app.listener("before_server_start")
|
302
295
|
async def audit_configuration(app, loop):
|
303
296
|
if security_config.SECRET == DEFAULT_CONFIG["SECRET"]:
|
@@ -361,3 +354,13 @@ def initialize_security(app: Sanic, create_root=True) -> None:
|
|
361
354
|
)
|
362
355
|
await account.roles.add(role)
|
363
356
|
logger.info("Initial admin account created.")
|
357
|
+
|
358
|
+
@app.on_response
|
359
|
+
async def response_handler_middleware(request, response):
|
360
|
+
if hasattr(request.ctx, "session"):
|
361
|
+
secure_headers.set_headers(response)
|
362
|
+
if (
|
363
|
+
hasattr(request.ctx.session, "is_refresh")
|
364
|
+
and request.ctx.session.is_refresh
|
365
|
+
):
|
366
|
+
request.ctx.session.encode(response)
|
sanic_security/authorization.py
CHANGED
@@ -69,6 +69,7 @@ async def check_permissions(
|
|
69
69
|
required_permissions, role.permissions.split(", ")
|
70
70
|
):
|
71
71
|
if fnmatch(required_permission, role_permission):
|
72
|
+
request.ctx.session = authentication_session
|
72
73
|
return authentication_session
|
73
74
|
logger.warning(
|
74
75
|
f"Client {get_ip(request)} with account {authentication_session.bearer.id} attempted an unauthorized action."
|
@@ -107,6 +108,7 @@ async def check_roles(request: Request, *required_roles: str) -> AuthenticationS
|
|
107
108
|
roles = await authentication_session.bearer.roles.filter(deleted=False).all()
|
108
109
|
for role in roles:
|
109
110
|
if role.name in required_roles:
|
111
|
+
request.ctx.session = authentication_session
|
110
112
|
return authentication_session
|
111
113
|
logger.warning(
|
112
114
|
f"Client {get_ip(request)} with account {authentication_session.bearer.id} attempted an unauthorized action. "
|
@@ -168,9 +170,7 @@ def require_permissions(*required_permissions: str):
|
|
168
170
|
def decorator(func):
|
169
171
|
@functools.wraps(func)
|
170
172
|
async def wrapper(request, *args, **kwargs):
|
171
|
-
|
172
|
-
request, *required_permissions
|
173
|
-
)
|
173
|
+
await check_permissions(request, *required_permissions)
|
174
174
|
return await func(request, *args, **kwargs)
|
175
175
|
|
176
176
|
return wrapper
|
@@ -208,9 +208,7 @@ def require_roles(*required_roles: str):
|
|
208
208
|
def decorator(func):
|
209
209
|
@functools.wraps(func)
|
210
210
|
async def wrapper(request, *args, **kwargs):
|
211
|
-
|
212
|
-
request, *required_roles
|
213
|
-
)
|
211
|
+
await check_roles(request, *required_roles)
|
214
212
|
return await func(request, *args, **kwargs)
|
215
213
|
|
216
214
|
return wrapper
|
sanic_security/test/server.py
CHANGED
@@ -128,7 +128,6 @@ async def on_two_factor_authentication(request):
|
|
128
128
|
"Authentication session second-factor fulfilled! You are now authenticated.",
|
129
129
|
authentication_session.bearer.json,
|
130
130
|
)
|
131
|
-
authentication_session.encode(response)
|
132
131
|
return response
|
133
132
|
|
134
133
|
|
@@ -144,16 +143,15 @@ async def on_logout(request):
|
|
144
143
|
@requires_authentication
|
145
144
|
async def on_authenticate(request):
|
146
145
|
"""Authenticate client session and account."""
|
147
|
-
authentication_session = request.ctx.authentication_session
|
148
146
|
response = json(
|
149
147
|
"Authenticated!",
|
150
148
|
{
|
151
149
|
"bearer": (
|
152
|
-
|
153
|
-
if not
|
150
|
+
request.ctx.session.bearer.json
|
151
|
+
if not request.ctx.session.anonymous
|
154
152
|
else None
|
155
153
|
),
|
156
|
-
"refresh":
|
154
|
+
"refresh": request.ctx.session.is_refresh,
|
157
155
|
},
|
158
156
|
)
|
159
157
|
return response
|
@@ -163,10 +161,9 @@ async def on_authenticate(request):
|
|
163
161
|
@requires_authentication
|
164
162
|
async def on_authentication_expire(request):
|
165
163
|
"""Expire client's session."""
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
return json("Authentication expired!", authentication_session.json)
|
164
|
+
request.ctx.session.expiration_date = datetime.datetime.now(datetime.UTC)
|
165
|
+
await request.ctx.session.save(update_fields=["expiration_date"])
|
166
|
+
return json("Authentication expired!", request.ctx.session.json)
|
170
167
|
|
171
168
|
|
172
169
|
@app.post("api/test/auth/associated")
|
@@ -174,7 +171,7 @@ async def on_authentication_expire(request):
|
|
174
171
|
async def on_get_associated_authentication_sessions(request):
|
175
172
|
"""Retrieves authentication sessions associated with logged in account."""
|
176
173
|
authentication_sessions = await AuthenticationSession.get_associated(
|
177
|
-
request.ctx.
|
174
|
+
request.ctx.session.bearer
|
178
175
|
)
|
179
176
|
return json(
|
180
177
|
"Associated authentication sessions retrieved!",
|
@@ -209,7 +206,7 @@ async def on_captcha_audio(request):
|
|
209
206
|
@requires_captcha
|
210
207
|
async def on_captcha_attempt(request):
|
211
208
|
"""Attempt captcha challenge."""
|
212
|
-
return json("Captcha attempt successful!", request.ctx.
|
209
|
+
return json("Captcha attempt successful!", request.ctx.session.json)
|
213
210
|
|
214
211
|
|
215
212
|
@app.post("api/test/two-step/request")
|
@@ -225,9 +222,7 @@ async def on_request_verification(request):
|
|
225
222
|
@requires_two_step_verification
|
226
223
|
async def on_verification_attempt(request):
|
227
224
|
"""Attempt two-step verification challenge."""
|
228
|
-
return json(
|
229
|
-
"Two step verification attempt successful!", request.ctx.two_step_session.json
|
230
|
-
)
|
225
|
+
return json("Two step verification attempt successful!", request.ctx.session.json)
|
231
226
|
|
232
227
|
|
233
228
|
@app.post("api/test/auth/roles")
|
@@ -248,7 +243,7 @@ async def on_role_assign(request):
|
|
248
243
|
"""Assign authenticated account a role."""
|
249
244
|
await assign_role(
|
250
245
|
request.form.get("name"),
|
251
|
-
request.ctx.
|
246
|
+
request.ctx.session.bearer,
|
252
247
|
request.form.get("permissions"),
|
253
248
|
"Role used for testing.",
|
254
249
|
)
|
sanic_security/verification.py
CHANGED
@@ -102,6 +102,7 @@ async def two_step_verification(request: Request) -> TwoStepSession:
|
|
102
102
|
logger.info(
|
103
103
|
f"Client {get_ip(request)} has completed two-step session {two_step_session.id} challenge."
|
104
104
|
)
|
105
|
+
request.ctx.session = two_step_session
|
105
106
|
return two_step_session
|
106
107
|
|
107
108
|
|
@@ -133,7 +134,7 @@ def requires_two_step_verification(arg=None):
|
|
133
134
|
def decorator(func):
|
134
135
|
@functools.wraps(func)
|
135
136
|
async def wrapper(request, *args, **kwargs):
|
136
|
-
|
137
|
+
await two_step_verification(request)
|
137
138
|
return await func(request, *args, **kwargs)
|
138
139
|
|
139
140
|
return wrapper
|
@@ -229,6 +230,7 @@ async def captcha(request: Request) -> CaptchaSession:
|
|
229
230
|
logger.info(
|
230
231
|
f"Client {get_ip(request)} has completed captcha session {captcha_session.id} challenge."
|
231
232
|
)
|
233
|
+
request.ctx.session = captcha_session
|
232
234
|
return captcha_session
|
233
235
|
|
234
236
|
|
@@ -257,7 +259,7 @@ def requires_captcha(arg=None):
|
|
257
259
|
def decorator(func):
|
258
260
|
@functools.wraps(func)
|
259
261
|
async def wrapper(request, *args, **kwargs):
|
260
|
-
|
262
|
+
await captcha(request)
|
261
263
|
return await func(request, *args, **kwargs)
|
262
264
|
|
263
265
|
return wrapper
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sanic-security
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.14.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/
|
@@ -21,14 +21,14 @@ Requires-Dist: pillow>=9.5.0
|
|
21
21
|
Requires-Dist: argon2-cffi>=20.1.0
|
22
22
|
Requires-Dist: sanic>=21.3.0
|
23
23
|
Requires-Dist: secure>=1.0.1
|
24
|
-
Provides-Extra: crypto
|
25
|
-
Requires-Dist: cryptography>=3.3.1; extra == "crypto"
|
26
24
|
Provides-Extra: dev
|
27
25
|
Requires-Dist: httpx; extra == "dev"
|
28
26
|
Requires-Dist: black; extra == "dev"
|
29
27
|
Requires-Dist: blacken-docs; extra == "dev"
|
30
28
|
Requires-Dist: pdoc3; extra == "dev"
|
31
29
|
Requires-Dist: cryptography; extra == "dev"
|
30
|
+
Provides-Extra: crypto
|
31
|
+
Requires-Dist: cryptography>=3.3.1; extra == "crypto"
|
32
32
|
|
33
33
|
<!-- PROJECT SHIELDS -->
|
34
34
|
<!--
|
@@ -212,7 +212,7 @@ Verifies the client's account via two-step session code.
|
|
212
212
|
| **code** | 24KF19 |
|
213
213
|
|
214
214
|
```python
|
215
|
-
@app.
|
215
|
+
@app.put("api/security/verify")
|
216
216
|
async def on_verify(request):
|
217
217
|
two_step_session = await verify_account(request)
|
218
218
|
return json("You have verified your account and may login!", two_step_session.json)
|
@@ -255,14 +255,13 @@ Fulfills client authentication session's second factor requirement via two-step
|
|
255
255
|
| **code** | XGED2U |
|
256
256
|
|
257
257
|
```python
|
258
|
-
@app.
|
258
|
+
@app.put("api/security/fulfill-2fa")
|
259
259
|
async def on_two_factor_authentication(request):
|
260
260
|
authentication_session = await fulfill_second_factor(request)
|
261
261
|
response = json(
|
262
262
|
"Authentication session second-factor fulfilled! You are now authenticated.",
|
263
263
|
authentication_session.json,
|
264
264
|
)
|
265
|
-
authentication_session.encode(response)
|
266
265
|
return response
|
267
266
|
```
|
268
267
|
|
@@ -309,46 +308,15 @@ async def on_authenticate(request):
|
|
309
308
|
@app.post("api/security/auth")
|
310
309
|
@requires_authentication
|
311
310
|
async def on_authenticate(request):
|
312
|
-
|
313
|
-
response = json(
|
314
|
-
"You have been authenticated.",
|
315
|
-
authentication_session.json,
|
316
|
-
)
|
311
|
+
response = json("You have been authenticated.", request.ctx.session.json)
|
317
312
|
return response
|
318
313
|
```
|
319
314
|
|
320
315
|
## CAPTCHA
|
321
316
|
|
322
|
-
Protects against spam and malicious activities by ensuring that only real humans can complete certain actions
|
323
|
-
submitting a form or creating an account.
|
324
|
-
|
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.
|
329
|
-
|
330
|
-
[1001 Free Fonts](https://www.1001fonts.com/)
|
331
|
-
|
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.
|
336
|
-
|
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
|
-
```
|
317
|
+
Protects against spam and malicious activities by ensuring that only real humans can complete certain actions like
|
318
|
+
submitting a form or creating an account. A font and voice library for CAPTCHA challenges is included in the repository,
|
319
|
+
or you can download/create your own and specify its path in the configuration.
|
352
320
|
|
353
321
|
* Request CAPTCHA
|
354
322
|
|
@@ -393,7 +361,7 @@ async def on_captcha(request):
|
|
393
361
|
@app.post("api/security/captcha")
|
394
362
|
@requires_captcha
|
395
363
|
async def on_captcha(request):
|
396
|
-
return json("Captcha attempt successful!", request.ctx.
|
364
|
+
return json("Captcha attempt successful!", request.ctx.session.json)
|
397
365
|
```
|
398
366
|
|
399
367
|
## Two-step Verification
|
@@ -455,8 +423,7 @@ async def on_two_step_verification(request):
|
|
455
423
|
@requires_two_step_verification
|
456
424
|
async def on_two_step_verification(request):
|
457
425
|
response = json(
|
458
|
-
"Two-step verification attempt successful!",
|
459
|
-
request.ctx.two_step_session.json,
|
426
|
+
"Two-step verification attempt successful!", request.ctx.session.json
|
460
427
|
)
|
461
428
|
return response
|
462
429
|
```
|
@@ -0,0 +1,16 @@
|
|
1
|
+
sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
sanic_security/authentication.py,sha256=ksHa4E82kNXNRKGSqeO28xfQWdF2pJDxUaaQy9snKwU,13299
|
3
|
+
sanic_security/authorization.py,sha256=QvLsaOWu3te0Y75tqChrEXQP5CR92nsRU7LXiUWy5cw,7541
|
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=olmpP2AwXKILRVRnDf7AMRJuK5Fs_i5ESHXSH94A-Yk,8694
|
9
|
+
sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
sanic_security/test/server.py,sha256=RjL9Kfvkfqpm5TXWwFQKKa0J4hfTKgwI6U0s_TAKO8w,11984
|
11
|
+
sanic_security/test/tests.py,sha256=bW5fHJfsCrg8eBmcSqVMLm0R5XRL1ou-XJJRgz09GOE,21993
|
12
|
+
sanic_security-1.14.1.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
|
13
|
+
sanic_security-1.14.1.dist-info/METADATA,sha256=It9-aEffADsbk5GJGO0SEG2DUd1wTq7FRl64l5oMcdA,23259
|
14
|
+
sanic_security-1.14.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
15
|
+
sanic_security-1.14.1.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
|
16
|
+
sanic_security-1.14.1.dist-info/RECORD,,
|
@@ -1,16 +0,0 @@
|
|
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,,
|
File without changes
|
File without changes
|