sanic-security 1.11.7__py3-none-any.whl → 1.12.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 +144 -140
- sanic_security/authorization.py +50 -42
- sanic_security/configuration.py +26 -20
- sanic_security/exceptions.py +48 -22
- sanic_security/models.py +114 -64
- sanic_security/test/server.py +78 -26
- sanic_security/test/tests.py +82 -15
- sanic_security/utils.py +24 -20
- sanic_security/verification.py +20 -19
- sanic_security-1.12.1.dist-info/LICENSE +21 -0
- {sanic_security-1.11.7.dist-info → sanic_security-1.12.1.dist-info}/METADATA +622 -591
- sanic_security-1.12.1.dist-info/RECORD +16 -0
- {sanic_security-1.11.7.dist-info → sanic_security-1.12.1.dist-info}/WHEEL +2 -1
- sanic_security-1.12.1.dist-info/top_level.txt +1 -0
- sanic_security-1.11.7.dist-info/LICENSE +0 -661
- sanic_security-1.11.7.dist-info/RECORD +0 -15
@@ -1,591 +1,622 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: sanic-security
|
3
|
-
Version: 1.
|
4
|
-
Summary: An
|
5
|
-
Author: Aidan Stewart
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
Classifier:
|
10
|
-
Classifier:
|
11
|
-
Classifier:
|
12
|
-
Classifier:
|
13
|
-
Classifier: Programming Language :: Python
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Requires-Dist:
|
18
|
-
Requires-Dist:
|
19
|
-
Requires-Dist:
|
20
|
-
Requires-Dist:
|
21
|
-
Requires-Dist:
|
22
|
-
Requires-Dist:
|
23
|
-
|
24
|
-
Requires-Dist:
|
25
|
-
|
26
|
-
Requires-Dist:
|
27
|
-
Requires-Dist:
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
***
|
35
|
-
***
|
36
|
-
***
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
[.
|
170
|
-
|
171
|
-
## Authentication
|
172
|
-
|
173
|
-
* Initial Administrator Account
|
174
|
-
|
175
|
-
This account can be logged into and has complete authoritative access. Login credentials should be modified in config!
|
176
|
-
|
177
|
-
```python
|
178
|
-
create_initial_admin_account(app)
|
179
|
-
if __name__ == "__main__":
|
180
|
-
app.run(host="127.0.0.1", port=8000)
|
181
|
-
```
|
182
|
-
|
183
|
-
* Registration (With
|
184
|
-
|
185
|
-
Phone can be null or empty.
|
186
|
-
|
187
|
-
| Key | Value |
|
188
|
-
|--------------|---------------------|
|
189
|
-
| **username** | example |
|
190
|
-
| **email** | example@example.com |
|
191
|
-
| **phone** | 19811354186 |
|
192
|
-
| **password** | examplepass |
|
193
|
-
|
194
|
-
```python
|
195
|
-
@app.post("api/security/register")
|
196
|
-
async def on_register(request):
|
197
|
-
account = await register(request)
|
198
|
-
two_step_session = await request_two_step_verification(request, account)
|
199
|
-
await email_code(
|
200
|
-
account.email, two_step_session.code # Code =
|
201
|
-
) # Custom method for emailing verification code.
|
202
|
-
response = json(
|
203
|
-
"Registration successful! Email verification required.",
|
204
|
-
two_step_session.
|
205
|
-
)
|
206
|
-
two_step_session.encode(response)
|
207
|
-
return response
|
208
|
-
```
|
209
|
-
|
210
|
-
* Verify Account
|
211
|
-
|
212
|
-
Verifies the client's account via two-step session code.
|
213
|
-
|
214
|
-
| Key | Value |
|
215
|
-
|----------|--------|
|
216
|
-
| **code** |
|
217
|
-
|
218
|
-
```python
|
219
|
-
@app.post("api/security/verify")
|
220
|
-
async def on_verify(request):
|
221
|
-
two_step_session = await verify_account(request)
|
222
|
-
return json(
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
)
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
```python
|
277
|
-
@app.post("api/security/
|
278
|
-
async def
|
279
|
-
authentication_session = await
|
280
|
-
response = json(
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
)
|
294
|
-
```
|
295
|
-
|
296
|
-
*
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
return json("
|
390
|
-
```
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
```
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
*
|
501
|
-
|
502
|
-
```python
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
*
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
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: crypto
|
24
|
+
Requires-Dist: cryptography >=3.3.1 ; extra == 'crypto'
|
25
|
+
Provides-Extra: dev
|
26
|
+
Requires-Dist: httpx ; extra == 'dev'
|
27
|
+
Requires-Dist: black ; extra == 'dev'
|
28
|
+
Requires-Dist: blacken-docs ; extra == 'dev'
|
29
|
+
Requires-Dist: pdoc3 ; extra == 'dev'
|
30
|
+
Requires-Dist: cryptography ; extra == 'dev'
|
31
|
+
|
32
|
+
<!-- PROJECT SHIELDS -->
|
33
|
+
<!--
|
34
|
+
*** I'm using markdown "reference style" links for readability.
|
35
|
+
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
|
36
|
+
*** See the bottom of this document for the declaration of the reference variables
|
37
|
+
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
|
38
|
+
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
|
39
|
+
-->
|
40
|
+
|
41
|
+
[](https://github.com/psf/black)
|
42
|
+
[](https://pepy.tech/project/sanic-security)
|
43
|
+
[](https://anaconda.org/conda-forge/sanic-security)
|
44
|
+
|
45
|
+
|
46
|
+
<!-- PROJECT LOGO -->
|
47
|
+
<br />
|
48
|
+
<p align="center">
|
49
|
+
<h3 align="center">Sanic Security</h3>
|
50
|
+
<p align="center">
|
51
|
+
An async security library for the Sanic framework.
|
52
|
+
</p>
|
53
|
+
</p>
|
54
|
+
|
55
|
+
|
56
|
+
<!-- TABLE OF CONTENTS -->
|
57
|
+
## Table of Contents
|
58
|
+
|
59
|
+
* [About the Project](#about-the-project)
|
60
|
+
* [Getting Started](#getting-started)
|
61
|
+
* [Prerequisites](#prerequisites)
|
62
|
+
* [Installation](#installation)
|
63
|
+
* [Configuration](#configuration)
|
64
|
+
* [Usage](#usage)
|
65
|
+
* [Authentication](#authentication)
|
66
|
+
* [Captcha](#captcha)
|
67
|
+
* [Two Step Verification](#two-step-verification)
|
68
|
+
* [Authorization](#authorization)
|
69
|
+
* [Testing](#testing)
|
70
|
+
* [Tortoise](#tortoise)
|
71
|
+
* [Contributing](#contributing)
|
72
|
+
* [License](#license)
|
73
|
+
* [Versioning](#versioning)
|
74
|
+
* [Support](https://discord.gg/JHpZkMfKTJ)
|
75
|
+
|
76
|
+
<!-- ABOUT THE PROJECT -->
|
77
|
+
## About The Project
|
78
|
+
|
79
|
+
Sanic Security is an authentication, authorization, and verification library designed for use with [Sanic](https://github.com/huge-success/sanic).
|
80
|
+
|
81
|
+
* Login, registration, and authentication with refresh mechanisms
|
82
|
+
* Two-factor authentication
|
83
|
+
* Captcha
|
84
|
+
* Two-step verification
|
85
|
+
* Role based authorization with wildcard permissions
|
86
|
+
|
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).
|
88
|
+
|
89
|
+
<!-- GETTING STARTED -->
|
90
|
+
## Getting Started
|
91
|
+
|
92
|
+
In order to get started, please install [Pip](https://pypi.org/).
|
93
|
+
|
94
|
+
### Installation
|
95
|
+
|
96
|
+
* Install the Sanic Security pip package.
|
97
|
+
```shell
|
98
|
+
pip3 install sanic-security
|
99
|
+
````
|
100
|
+
|
101
|
+
* Install the Sanic Security pip package with the `cryptography` dependency included.
|
102
|
+
|
103
|
+
If you are planning on encoding or decoding JWTs using certain digital signature algorithms (like RSA or ECDSA which use
|
104
|
+
the public secret and private secret), you will need to install the `cryptography` library. This can be installed explicitly, or
|
105
|
+
as an extra requirement.
|
106
|
+
|
107
|
+
```shell
|
108
|
+
pip3 install sanic-security[crypto]
|
109
|
+
````
|
110
|
+
|
111
|
+
* For developers, fork Sanic Security and install development dependencies.
|
112
|
+
```shell
|
113
|
+
pip3 install -e ".[dev]"
|
114
|
+
````
|
115
|
+
|
116
|
+
* Update sanic-security if already installed.
|
117
|
+
```shell
|
118
|
+
pip3 install --upgrade sanic-security
|
119
|
+
```
|
120
|
+
|
121
|
+
### Configuration
|
122
|
+
|
123
|
+
Sanic Security configuration is merely an object that can be modified either using dot-notation or like a
|
124
|
+
dictionary.
|
125
|
+
|
126
|
+
For example:
|
127
|
+
|
128
|
+
```python
|
129
|
+
from sanic_security.configuration import config
|
130
|
+
|
131
|
+
config.SECRET = "This is a big secret. Shhhhh"
|
132
|
+
config["CAPTCHA_FONT"] = "./resources/captcha-font.ttf"
|
133
|
+
```
|
134
|
+
|
135
|
+
You can also use the update() method like on regular dictionaries.
|
136
|
+
|
137
|
+
Any environment variables defined with the SANIC_SECURITY_ prefix will be applied to the config. For example, setting
|
138
|
+
SANIC_SECURITY_SECRET will be loaded by the application automatically and fed into the SECRET config variable.
|
139
|
+
|
140
|
+
You can load environment variables with a different prefix via `config.load_environment_variables("NEW_PREFIX_")` method.
|
141
|
+
|
142
|
+
* Default configuration values:
|
143
|
+
|
144
|
+
| Key | Value | Description |
|
145
|
+
|---------------------------------------|------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
|
146
|
+
| **SECRET** | This is a big secret. Shhhhh | The secret used for generating and signing JWTs. This should be a string unique to your application. Keep it safe. |
|
147
|
+
| **PUBLIC_SECRET** | None | The secret used for verifying and decoding JWTs and can be publicly shared. This should be a string unique to your application. |
|
148
|
+
| **SESSION_SAMESITE** | strict | The SameSite attribute of session cookies. |
|
149
|
+
| **SESSION_SECURE** | True | The Secure attribute of session cookies. |
|
150
|
+
| **SESSION_HTTPONLY** | True | The HttpOnly attribute of session cookies. HIGHLY recommended that you do not turn this off, unless you know what you are doing. |
|
151
|
+
| **SESSION_DOMAIN** | None | The Domain attribute of session cookies. |
|
152
|
+
| **SESSION_ENCODING_ALGORITHM** | HS256 | The algorithm used to encode and decode session JWT's. |
|
153
|
+
| **SESSION_PREFIX** | token | Prefix attached to the beginning of session cookies. |
|
154
|
+
| **MAX_CHALLENGE_ATTEMPTS** | 5 | The maximum amount of session challenge attempts allowed. |
|
155
|
+
| **CAPTCHA_SESSION_EXPIRATION** | 60 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
|
156
|
+
| **CAPTCHA_FONT** | captcha-font.ttf | The file path to the font being used for captcha generation. |
|
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. |
|
160
|
+
| **ALLOW_LOGIN_WITH_USERNAME** | False | Allows login via username and email. |
|
161
|
+
| **INITIAL_ADMIN_EMAIL** | admin@example.com | Email used when creating the initial admin account. |
|
162
|
+
| **INITIAL_ADMIN_PASSWORD** | admin123 | Password used when creating the initial admin account. |
|
163
|
+
|
164
|
+
## Usage
|
165
|
+
|
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
|
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.
|
168
|
+
|
169
|
+
The tables in the below examples represent example [request form-data](https://sanicframework.org/en/guide/basics/request.html#form).
|
170
|
+
|
171
|
+
## Authentication
|
172
|
+
|
173
|
+
* Initial Administrator Account
|
174
|
+
|
175
|
+
This account can be logged into and has complete authoritative access. Login credentials should be modified in config!
|
176
|
+
|
177
|
+
```python
|
178
|
+
create_initial_admin_account(app)
|
179
|
+
if __name__ == "__main__":
|
180
|
+
app.run(host="127.0.0.1", port=8000)
|
181
|
+
```
|
182
|
+
|
183
|
+
* Registration (With two-step account verification)
|
184
|
+
|
185
|
+
Phone can be null or empty.
|
186
|
+
|
187
|
+
| Key | Value |
|
188
|
+
|--------------|---------------------|
|
189
|
+
| **username** | example |
|
190
|
+
| **email** | example@example.com |
|
191
|
+
| **phone** | 19811354186 |
|
192
|
+
| **password** | examplepass |
|
193
|
+
|
194
|
+
```python
|
195
|
+
@app.post("api/security/register")
|
196
|
+
async def on_register(request):
|
197
|
+
account = await register(request)
|
198
|
+
two_step_session = await request_two_step_verification(request, account)
|
199
|
+
await email_code(
|
200
|
+
account.email, two_step_session.code # Code = 197251
|
201
|
+
) # Custom method for emailing verification code.
|
202
|
+
response = json(
|
203
|
+
"Registration successful! Email verification required.",
|
204
|
+
two_step_session.json,
|
205
|
+
)
|
206
|
+
two_step_session.encode(response)
|
207
|
+
return response
|
208
|
+
```
|
209
|
+
|
210
|
+
* Verify Account
|
211
|
+
|
212
|
+
Verifies the client's account via two-step session code.
|
213
|
+
|
214
|
+
| Key | Value |
|
215
|
+
|----------|--------|
|
216
|
+
| **code** | 197251 |
|
217
|
+
|
218
|
+
```python
|
219
|
+
@app.post("api/security/verify")
|
220
|
+
async def on_verify(request):
|
221
|
+
two_step_session = await verify_account(request)
|
222
|
+
return json("You have verified your account and may login!", two_step_session.json)
|
223
|
+
```
|
224
|
+
|
225
|
+
* Login (With two-factor authentication)
|
226
|
+
|
227
|
+
Login credentials are retrieved via the Authorization header. Credentials are constructed by first combining the
|
228
|
+
username and the password with a colon (aladdin:opensesame), and then by encoding the resulting string in base64
|
229
|
+
(YWxhZGRpbjpvcGVuc2VzYW1l). Here is an example authorization header: `Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l`.
|
230
|
+
|
231
|
+
You can use a username as well as an email for login if `ALLOW_LOGIN_WITH_USERNAME` is true in the config.
|
232
|
+
|
233
|
+
```python
|
234
|
+
@app.post("api/security/login")
|
235
|
+
async def on_login(request):
|
236
|
+
authentication_session = await login(request, require_second_factor=True)
|
237
|
+
two_step_session = await request_two_step_verification(
|
238
|
+
request, authentication_session.bearer
|
239
|
+
)
|
240
|
+
await email_code(
|
241
|
+
authentication_session.bearer.email, two_step_session.code # Code = 197251
|
242
|
+
) # Custom method for emailing verification code.
|
243
|
+
response = json(
|
244
|
+
"Login successful! Two-factor authentication required.",
|
245
|
+
authentication_session.json,
|
246
|
+
)
|
247
|
+
authentication_session.encode(response)
|
248
|
+
two_step_session.encode(response)
|
249
|
+
return response
|
250
|
+
```
|
251
|
+
|
252
|
+
* Fulfill Second Factor
|
253
|
+
|
254
|
+
Fulfills client authentication session's second factor requirement via two-step session code.
|
255
|
+
|
256
|
+
| Key | Value |
|
257
|
+
|----------|--------|
|
258
|
+
| **code** | 197251 |
|
259
|
+
|
260
|
+
```python
|
261
|
+
@app.post("api/security/fulfill-2fa")
|
262
|
+
async def on_two_factor_authentication(request):
|
263
|
+
authentication_session = await fulfill_second_factor(request)
|
264
|
+
response = json(
|
265
|
+
"Authentication session second-factor fulfilled! You are now authenticated.",
|
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
|
282
|
+
)
|
283
|
+
authentication_session.encode(response)
|
284
|
+
return response
|
285
|
+
```
|
286
|
+
|
287
|
+
* Logout
|
288
|
+
|
289
|
+
```python
|
290
|
+
@app.post("api/security/logout")
|
291
|
+
async def on_logout(request):
|
292
|
+
authentication_session = await logout(request)
|
293
|
+
return json("Logout successful!", authentication_session.json)
|
294
|
+
```
|
295
|
+
|
296
|
+
* Authenticate
|
297
|
+
|
298
|
+
New/Refreshed session will be returned if expired, requires encoding.
|
299
|
+
|
300
|
+
```python
|
301
|
+
@app.post("api/security/auth")
|
302
|
+
async def on_authenticate(request):
|
303
|
+
authentication_session = await authenticate(request)
|
304
|
+
response = json(
|
305
|
+
"You have been authenticated.",
|
306
|
+
authentication_session.json,
|
307
|
+
)
|
308
|
+
if authentication_session.is_refresh:
|
309
|
+
authentication_session.encode(response)
|
310
|
+
return response
|
311
|
+
```
|
312
|
+
|
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.
|
316
|
+
|
317
|
+
```python
|
318
|
+
@app.post("api/security/auth")
|
319
|
+
@requires_authentication
|
320
|
+
async def on_authenticate(request):
|
321
|
+
authentication_session = request.ctx.authentication_session
|
322
|
+
response = json(
|
323
|
+
"You have been authenticated.",
|
324
|
+
authentication_session.json,
|
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
|
344
|
+
```
|
345
|
+
|
346
|
+
## Captcha
|
347
|
+
|
348
|
+
A pre-existing font for captcha challenges is included in the Sanic Security repository. You may set your own font by
|
349
|
+
downloading a .ttf font and defining the file's path in the configuration.
|
350
|
+
|
351
|
+
[1001 Free Fonts](https://www.1001fonts.com/)
|
352
|
+
|
353
|
+
[Recommended Font](https://www.1001fonts.com/source-sans-pro-font.html)
|
354
|
+
|
355
|
+
* Request Captcha
|
356
|
+
|
357
|
+
```python
|
358
|
+
@app.get("api/security/captcha")
|
359
|
+
async def on_captcha_img_request(request):
|
360
|
+
captcha_session = await request_captcha(request)
|
361
|
+
response = captcha_session.get_image() # Captcha: 192731
|
362
|
+
captcha_session.encode(response)
|
363
|
+
return response
|
364
|
+
```
|
365
|
+
|
366
|
+
* Captcha
|
367
|
+
|
368
|
+
| Key | Value |
|
369
|
+
|-------------|--------|
|
370
|
+
| **captcha** | 192731 |
|
371
|
+
|
372
|
+
```python
|
373
|
+
@app.post("api/security/captcha")
|
374
|
+
async def on_captcha(request):
|
375
|
+
captcha_session = await captcha(request)
|
376
|
+
return json("Captcha attempt successful!", captcha_session.json)
|
377
|
+
```
|
378
|
+
|
379
|
+
* Requires Captcha (This method is not called directly and instead used as a decorator)
|
380
|
+
|
381
|
+
| Key | Value |
|
382
|
+
|-------------|--------|
|
383
|
+
| **captcha** | 192731 |
|
384
|
+
|
385
|
+
```python
|
386
|
+
@app.post("api/security/captcha")
|
387
|
+
@requires_captcha
|
388
|
+
async def on_captcha(request):
|
389
|
+
return json("Captcha attempt successful!", request.ctx.captcha_session.json)
|
390
|
+
```
|
391
|
+
|
392
|
+
## Two-step Verification
|
393
|
+
|
394
|
+
Two-step verification should be integrated with other custom functionality. For example, account verification during registration.
|
395
|
+
|
396
|
+
* Request Two-step Verification
|
397
|
+
|
398
|
+
| Key | Value |
|
399
|
+
|-------------|---------------------|
|
400
|
+
| **email** | example@example.com |
|
401
|
+
|
402
|
+
```python
|
403
|
+
@app.post("api/security/two-step/request")
|
404
|
+
async def on_two_step_request(request):
|
405
|
+
two_step_session = await request_two_step_verification(request) # Code = 197251
|
406
|
+
await email_code(
|
407
|
+
two_step_session.bearer.email, two_step_session.code
|
408
|
+
) # Custom method for emailing verification code.
|
409
|
+
response = json("Verification request successful!", two_step_session.json)
|
410
|
+
two_step_session.encode(response)
|
411
|
+
return response
|
412
|
+
```
|
413
|
+
|
414
|
+
* Resend Two-step Verification Code
|
415
|
+
|
416
|
+
```python
|
417
|
+
@app.post("api/security/two-step/resend")
|
418
|
+
async def on_two_step_resend(request):
|
419
|
+
two_step_session = await TwoStepSession.decode(request) # Code = 197251
|
420
|
+
await email_code(
|
421
|
+
two_step_session.bearer.email, two_step_session.code
|
422
|
+
) # Custom method for emailing verification code.
|
423
|
+
return json("Verification code resend successful!", two_step_session.json)
|
424
|
+
```
|
425
|
+
|
426
|
+
* Two-step Verification
|
427
|
+
|
428
|
+
| Key | Value |
|
429
|
+
|----------|--------|
|
430
|
+
| **code** | 197251 |
|
431
|
+
|
432
|
+
```python
|
433
|
+
@app.post("api/security/two-step")
|
434
|
+
async def on_two_step_verification(request):
|
435
|
+
two_step_session = await two_step_verification(request)
|
436
|
+
response = json("Two-step verification attempt successful!", two_step_session.json)
|
437
|
+
return response
|
438
|
+
```
|
439
|
+
|
440
|
+
* Requires Two-step Verification (This method is not called directly and instead used as a decorator)
|
441
|
+
|
442
|
+
| Key | Value |
|
443
|
+
|----------|--------|
|
444
|
+
| **code** | 197251 |
|
445
|
+
|
446
|
+
```python
|
447
|
+
@app.post("api/security/two-step")
|
448
|
+
@requires_two_step_verification
|
449
|
+
async def on_two_step_verification(request):
|
450
|
+
response = json(
|
451
|
+
"Two-step verification attempt successful!",
|
452
|
+
request.ctx.two_step_session.json,
|
453
|
+
)
|
454
|
+
return response
|
455
|
+
```
|
456
|
+
|
457
|
+
## Authorization
|
458
|
+
|
459
|
+
Sanic Security uses role based authorization with wildcard permissions.
|
460
|
+
|
461
|
+
Roles are created for various job functions. The permissions to perform certain operations are assigned to specific roles.
|
462
|
+
Users are assigned particular roles, and through those role assignments acquire the permissions needed to perform
|
463
|
+
particular system functions. Since users are not assigned permissions directly, but only acquire them through their
|
464
|
+
role (or roles), management of individual user rights becomes a matter of simply assigning appropriate roles to the
|
465
|
+
user's account; this simplifies common operations, such as adding a user, or changing a user's department.
|
466
|
+
|
467
|
+
Wildcard permissions support the concept of multiple levels or parts. For example, you could grant a user the permission
|
468
|
+
`printer:query`, `printer:query,delete`, or `printer:*`.
|
469
|
+
* Assign Role
|
470
|
+
|
471
|
+
```python
|
472
|
+
await assign_role(
|
473
|
+
"Chat Room Moderator",
|
474
|
+
account,
|
475
|
+
"channels:view,delete, account:suspend,mute, voice:*",
|
476
|
+
"Can read and delete messages in all chat rooms, suspend and mute accounts, and control voice chat.",
|
477
|
+
)
|
478
|
+
```
|
479
|
+
|
480
|
+
* Check Permissions
|
481
|
+
|
482
|
+
```python
|
483
|
+
@app.post("api/security/perms")
|
484
|
+
async def on_check_perms(request):
|
485
|
+
authentication_session = await check_permissions(
|
486
|
+
request, "channels:view", "voice:*"
|
487
|
+
)
|
488
|
+
return text("Account is authorized.")
|
489
|
+
```
|
490
|
+
|
491
|
+
* Require Permissions (This method is not called directly and instead used as a decorator.)
|
492
|
+
|
493
|
+
```python
|
494
|
+
@app.post("api/security/perms")
|
495
|
+
@require_permissions("channels:view", "voice:*")
|
496
|
+
async def on_check_perms(request):
|
497
|
+
return text("Account is authorized.")
|
498
|
+
```
|
499
|
+
|
500
|
+
* Check Roles
|
501
|
+
|
502
|
+
```python
|
503
|
+
@app.post("api/security/roles")
|
504
|
+
async def on_check_roles(request):
|
505
|
+
authentication_session = await check_roles(request, "Chat Room Moderator")
|
506
|
+
return text("Account is authorized.")
|
507
|
+
```
|
508
|
+
|
509
|
+
* Require Roles (This method is not called directly and instead used as a decorator)
|
510
|
+
|
511
|
+
```python
|
512
|
+
@app.post("api/security/roles")
|
513
|
+
@require_roles("Chat Room Moderator")
|
514
|
+
async def on_check_roles(request):
|
515
|
+
return text("Account is authorized.")
|
516
|
+
```
|
517
|
+
|
518
|
+
## Testing
|
519
|
+
|
520
|
+
* Set the `TEST_DATABASE_URL` configuration value.
|
521
|
+
|
522
|
+
* Make sure the test Sanic instance (`test/server.py`) is running on your machine.
|
523
|
+
|
524
|
+
* Run the unit test client (`test/tests.py`) for results.
|
525
|
+
|
526
|
+
## Tortoise
|
527
|
+
|
528
|
+
Sanic Security uses [Tortoise ORM](https://tortoise-orm.readthedocs.io/en/latest/index.html) for database operations.
|
529
|
+
|
530
|
+
Tortoise ORM is an easy-to-use asyncio ORM (Object Relational Mapper).
|
531
|
+
|
532
|
+
* Initialise your models and database like so:
|
533
|
+
|
534
|
+
```python
|
535
|
+
async def init():
|
536
|
+
await Tortoise.init(
|
537
|
+
db_url="sqlite://db.sqlite3",
|
538
|
+
modules={"models": ["sanic_security.models", "app.models"]},
|
539
|
+
)
|
540
|
+
await Tortoise.generate_schemas()
|
541
|
+
```
|
542
|
+
|
543
|
+
or
|
544
|
+
|
545
|
+
```python
|
546
|
+
register_tortoise(
|
547
|
+
app,
|
548
|
+
db_url="sqlite://db.sqlite3",
|
549
|
+
modules={"models": ["sanic_security.models", "app.models"]},
|
550
|
+
generate_schemas=True,
|
551
|
+
)
|
552
|
+
```
|
553
|
+
|
554
|
+
* Define your models like so:
|
555
|
+
|
556
|
+
```python
|
557
|
+
from tortoise.models import Model
|
558
|
+
from tortoise import fields
|
559
|
+
|
560
|
+
|
561
|
+
class Tournament(Model):
|
562
|
+
id = fields.IntField(pk=True)
|
563
|
+
name = fields.TextField()
|
564
|
+
```
|
565
|
+
|
566
|
+
* Use it like so:
|
567
|
+
|
568
|
+
```python
|
569
|
+
# Create instance by save
|
570
|
+
tournament = Tournament(name="New Tournament")
|
571
|
+
await tournament.save()
|
572
|
+
|
573
|
+
# Or by .create()
|
574
|
+
await Tournament.create(name="Another Tournament")
|
575
|
+
|
576
|
+
# Now search for a record
|
577
|
+
tour = await Tournament.filter(name__contains="Another").first()
|
578
|
+
print(tour.name)
|
579
|
+
```
|
580
|
+
|
581
|
+
<!-- CONTRIBUTING -->
|
582
|
+
## Contributing
|
583
|
+
|
584
|
+
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
585
|
+
|
586
|
+
1. Fork the Project
|
587
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
588
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
589
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
590
|
+
5. Open a Pull Request
|
591
|
+
|
592
|
+
|
593
|
+
<!-- LICENSE -->
|
594
|
+
## License
|
595
|
+
|
596
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
597
|
+
|
598
|
+
<!-- Versioning -->
|
599
|
+
## Versioning
|
600
|
+
|
601
|
+
**0.0.0**
|
602
|
+
|
603
|
+
* MAJOR version when you make incompatible API changes.
|
604
|
+
|
605
|
+
* MINOR version when you add functionality in a backwards compatible manner.
|
606
|
+
|
607
|
+
* PATCH version when you make backwards compatible bug fixes.
|
608
|
+
|
609
|
+
[https://semver.org/](https://semver.org/)
|
610
|
+
|
611
|
+
<!-- MARKDOWN LINKS & IMAGES -->
|
612
|
+
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
613
|
+
[contributors-shield]: https://img.shields.io/github/contributors/sunset-developer/sanic-security.svg?style=flat-square
|
614
|
+
[contributors-url]: https://github.com/sunset-developer/sanic-security/graphs/contributors
|
615
|
+
[forks-shield]: https://img.shields.io/github/forks/sunset-developer/sanic-security.svg?style=flat-square
|
616
|
+
[forks-url]: https://github.com/sunset-developer/sanic-security/network/members
|
617
|
+
[stars-shield]: https://img.shields.io/github/stars/sunset-developer/sanic-security.svg?style=flat-square
|
618
|
+
[stars-url]: https://github.com/sunset-developer/sanic-security/stargazers
|
619
|
+
[issues-shield]: https://img.shields.io/github/issues/sunset-developer/sanic-security.svg?style=flat-square
|
620
|
+
[issues-url]: https://github.com/sunset-developer/sanic-security/issues
|
621
|
+
[license-shield]: https://img.shields.io/github/license/sunset-developer/sanic-security.svg?style=flat-square
|
622
|
+
[license-url]: https://github.com/sunset-developer/sanic-security/blob/master/LICENSE
|