sanic-security 1.11.7__py3-none-any.whl → 1.12.0__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 +140 -136
- sanic_security/authorization.py +50 -42
- sanic_security/configuration.py +26 -20
- sanic_security/exceptions.py +43 -21
- sanic_security/models.py +114 -64
- sanic_security/test/server.py +67 -23
- sanic_security/test/tests.py +80 -15
- sanic_security/utils.py +24 -20
- sanic_security/verification.py +20 -19
- sanic_security-1.12.0.dist-info/LICENSE +21 -0
- {sanic_security-1.11.7.dist-info → sanic_security-1.12.0.dist-info}/METADATA +608 -591
- sanic_security-1.12.0.dist-info/RECORD +16 -0
- {sanic_security-1.11.7.dist-info → sanic_security-1.12.0.dist-info}/WHEEL +2 -1
- sanic_security-1.12.0.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
sanic_security/authentication.py
CHANGED
@@ -15,111 +15,35 @@ from sanic_security.exceptions import (
|
|
15
15
|
CredentialsError,
|
16
16
|
DeactivatedError,
|
17
17
|
SecondFactorFulfilledError,
|
18
|
+
ExpiredError,
|
18
19
|
)
|
19
20
|
from sanic_security.models import Account, AuthenticationSession, Role, TwoStepSession
|
20
|
-
from sanic_security.utils import get_ip
|
21
21
|
|
22
22
|
"""
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
23
|
+
Copyright (c) 2020-present Nicholas Aidan Stewart
|
24
|
+
|
25
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
26
|
+
of this software and associated documentation files (the "Software"), to deal
|
27
|
+
in the Software without restriction, including without limitation the rights
|
28
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
29
|
+
copies of the Software, and to permit persons to whom the Software is
|
30
|
+
furnished to do so, subject to the following conditions:
|
31
|
+
|
32
|
+
The above copyright notice and this permission notice shall be included in all
|
33
|
+
copies or substantial portions of the Software.
|
34
|
+
|
35
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
36
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
37
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
38
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
39
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
40
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
41
|
+
SOFTWARE.
|
38
42
|
"""
|
39
43
|
|
40
44
|
password_hasher = PasswordHasher()
|
41
45
|
|
42
46
|
|
43
|
-
def validate_email(email: str) -> str:
|
44
|
-
"""
|
45
|
-
Validates email format.
|
46
|
-
|
47
|
-
Args:
|
48
|
-
email (str): Email being validated.
|
49
|
-
|
50
|
-
Returns:
|
51
|
-
email
|
52
|
-
|
53
|
-
Raises:
|
54
|
-
CredentialsError
|
55
|
-
"""
|
56
|
-
if not re.search(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", email):
|
57
|
-
raise CredentialsError("Please use a valid email address.", 400)
|
58
|
-
return email
|
59
|
-
|
60
|
-
|
61
|
-
def validate_username(username: str) -> str:
|
62
|
-
"""
|
63
|
-
Validates username format.
|
64
|
-
|
65
|
-
Args:
|
66
|
-
username (str): Username being validated.
|
67
|
-
|
68
|
-
Returns:
|
69
|
-
username
|
70
|
-
|
71
|
-
Raises:
|
72
|
-
CredentialsError
|
73
|
-
"""
|
74
|
-
if not re.search(r"^[A-Za-z0-9_-]{3,32}$", username):
|
75
|
-
raise CredentialsError(
|
76
|
-
"Username must be between 3-32 characters and not contain any special characters other than _ or -.",
|
77
|
-
400,
|
78
|
-
)
|
79
|
-
return username
|
80
|
-
|
81
|
-
|
82
|
-
def validate_phone(phone: str) -> str:
|
83
|
-
"""
|
84
|
-
Validates phone number format.
|
85
|
-
|
86
|
-
Args:
|
87
|
-
phone (str): Phone number being validated.
|
88
|
-
|
89
|
-
Returns:
|
90
|
-
phone
|
91
|
-
|
92
|
-
Raises:
|
93
|
-
CredentialsError
|
94
|
-
"""
|
95
|
-
if phone and not re.search(
|
96
|
-
r"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$", phone
|
97
|
-
):
|
98
|
-
raise CredentialsError("Please use a valid phone number.", 400)
|
99
|
-
return phone
|
100
|
-
|
101
|
-
|
102
|
-
def validate_password(password: str) -> str:
|
103
|
-
"""
|
104
|
-
Validates password requirements.
|
105
|
-
|
106
|
-
Args:
|
107
|
-
password (str): Password being validated.
|
108
|
-
|
109
|
-
Returns:
|
110
|
-
password
|
111
|
-
|
112
|
-
Raises:
|
113
|
-
CredentialsError
|
114
|
-
"""
|
115
|
-
if not re.search(r"^(?=.*[A-Z])(?=.*\d)(?=.*[@#$%^&+=!]).*$", password):
|
116
|
-
raise CredentialsError(
|
117
|
-
"Password must contain one capital letter, one number, and one special character",
|
118
|
-
400,
|
119
|
-
)
|
120
|
-
return password
|
121
|
-
|
122
|
-
|
123
47
|
async def register(
|
124
48
|
request: Request, verified: bool = False, disabled: bool = False
|
125
49
|
) -> Account:
|
@@ -139,18 +63,20 @@ async def register(
|
|
139
63
|
"""
|
140
64
|
email_lower = validate_email(request.form.get("email").lower())
|
141
65
|
if await Account.filter(email=email_lower).exists():
|
142
|
-
raise CredentialsError("An account with this email already
|
66
|
+
raise CredentialsError("An account with this email may already exist.", 409)
|
143
67
|
elif await Account.filter(
|
144
68
|
username=validate_username(request.form.get("username"))
|
145
69
|
).exists():
|
146
|
-
raise CredentialsError("An account with this username already
|
70
|
+
raise CredentialsError("An account with this username may already exist.", 409)
|
147
71
|
elif (
|
148
72
|
request.form.get("phone")
|
149
73
|
and await Account.filter(
|
150
74
|
phone=validate_phone(request.form.get("phone"))
|
151
75
|
).exists()
|
152
76
|
):
|
153
|
-
raise CredentialsError(
|
77
|
+
raise CredentialsError(
|
78
|
+
"An account with this phone number may already exist.", 409
|
79
|
+
)
|
154
80
|
validate_password(request.form.get("password"))
|
155
81
|
account = await Account.create(
|
156
82
|
email=email_lower,
|
@@ -212,9 +138,6 @@ async def login(
|
|
212
138
|
request, account, requires_second_factor=require_second_factor
|
213
139
|
)
|
214
140
|
except VerifyMismatchError:
|
215
|
-
logger.warning(
|
216
|
-
f"Client ({get_ip(request)}) login password attempt is incorrect"
|
217
|
-
)
|
218
141
|
raise CredentialsError("Incorrect password.", 401)
|
219
142
|
|
220
143
|
|
@@ -241,32 +164,6 @@ async def logout(request: Request) -> AuthenticationSession:
|
|
241
164
|
return authentication_session
|
242
165
|
|
243
166
|
|
244
|
-
async def authenticate(request: Request) -> AuthenticationSession:
|
245
|
-
"""
|
246
|
-
Validates client's authentication session and account.
|
247
|
-
|
248
|
-
Args:
|
249
|
-
request (Request): Sanic request parameter.
|
250
|
-
|
251
|
-
Returns:
|
252
|
-
authentication_session
|
253
|
-
|
254
|
-
Raises:
|
255
|
-
NotFoundError
|
256
|
-
JWTDecodeError
|
257
|
-
DeletedError
|
258
|
-
ExpiredError
|
259
|
-
DeactivatedError
|
260
|
-
UnverifiedError
|
261
|
-
DisabledError
|
262
|
-
SecondFactorRequiredError
|
263
|
-
"""
|
264
|
-
authentication_session = await AuthenticationSession.decode(request)
|
265
|
-
authentication_session.validate()
|
266
|
-
authentication_session.bearer.validate()
|
267
|
-
return authentication_session
|
268
|
-
|
269
|
-
|
270
167
|
async def fulfill_second_factor(request: Request) -> AuthenticationSession:
|
271
168
|
"""
|
272
169
|
Fulfills client authentication session's second factor requirement via two-step session code.
|
@@ -288,9 +185,9 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession:
|
|
288
185
|
authentication_Session
|
289
186
|
"""
|
290
187
|
authentication_session = await AuthenticationSession.decode(request)
|
291
|
-
two_step_session = await TwoStepSession.decode(request)
|
292
188
|
if not authentication_session.requires_second_factor:
|
293
189
|
raise SecondFactorFulfilledError()
|
190
|
+
two_step_session = await TwoStepSession.decode(request)
|
294
191
|
two_step_session.validate()
|
295
192
|
await two_step_session.check_code(request, request.form.get("code"))
|
296
193
|
authentication_session.requires_second_factor = False
|
@@ -298,9 +195,40 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession:
|
|
298
195
|
return authentication_session
|
299
196
|
|
300
197
|
|
198
|
+
async def authenticate(request: Request) -> tuple[bool, AuthenticationSession]:
|
199
|
+
"""
|
200
|
+
Validates client's authentication session and account. New/Refreshed session automatically returned
|
201
|
+
if expired during authentication, requires encoding.
|
202
|
+
|
203
|
+
Args:
|
204
|
+
request (Request): Sanic request parameter.
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
authentication_session
|
208
|
+
|
209
|
+
Raises:
|
210
|
+
NotFoundError
|
211
|
+
JWTDecodeError
|
212
|
+
DeletedError
|
213
|
+
DeactivatedError
|
214
|
+
UnverifiedError
|
215
|
+
DisabledError
|
216
|
+
SecondFactorRequiredError
|
217
|
+
"""
|
218
|
+
authentication_session = await AuthenticationSession.decode(request)
|
219
|
+
try:
|
220
|
+
authentication_session.validate()
|
221
|
+
if not authentication_session.anonymous:
|
222
|
+
authentication_session.bearer.validate()
|
223
|
+
except ExpiredError:
|
224
|
+
authentication_session = await authentication_session.refresh(request)
|
225
|
+
return authentication_session
|
226
|
+
|
227
|
+
|
301
228
|
def requires_authentication(arg=None):
|
302
229
|
"""
|
303
|
-
Validates client's authentication session and account.
|
230
|
+
Validates client's authentication session and account. New/Refreshed session automatically returned if expired
|
231
|
+
during authentication, requires encoding.
|
304
232
|
|
305
233
|
Example:
|
306
234
|
This method is not called directly and instead used as a decorator:
|
@@ -314,7 +242,6 @@ def requires_authentication(arg=None):
|
|
314
242
|
NotFoundError
|
315
243
|
JWTDecodeError
|
316
244
|
DeletedError
|
317
|
-
ExpiredError
|
318
245
|
DeactivatedError
|
319
246
|
UnverifiedError
|
320
247
|
DisabledError
|
@@ -328,10 +255,7 @@ def requires_authentication(arg=None):
|
|
328
255
|
|
329
256
|
return wrapper
|
330
257
|
|
331
|
-
if callable(arg)
|
332
|
-
return decorator(arg)
|
333
|
-
else:
|
334
|
-
return decorator
|
258
|
+
return decorator(arg) if callable(arg) else decorator
|
335
259
|
|
336
260
|
|
337
261
|
def create_initial_admin_account(app: Sanic) -> None:
|
@@ -343,7 +267,7 @@ def create_initial_admin_account(app: Sanic) -> None:
|
|
343
267
|
"""
|
344
268
|
|
345
269
|
@app.listener("before_server_start")
|
346
|
-
async def
|
270
|
+
async def create(app, loop):
|
347
271
|
try:
|
348
272
|
role = await Role.filter(name="Head Admin").get()
|
349
273
|
except DoesNotExist:
|
@@ -371,3 +295,83 @@ def create_initial_admin_account(app: Sanic) -> None:
|
|
371
295
|
)
|
372
296
|
await account.roles.add(role)
|
373
297
|
logger.info("Initial admin account created.")
|
298
|
+
|
299
|
+
|
300
|
+
def validate_email(email: str) -> str:
|
301
|
+
"""
|
302
|
+
Validates email format.
|
303
|
+
|
304
|
+
Args:
|
305
|
+
email (str): Email being validated.
|
306
|
+
|
307
|
+
Returns:
|
308
|
+
email
|
309
|
+
|
310
|
+
Raises:
|
311
|
+
CredentialsError
|
312
|
+
"""
|
313
|
+
if not re.search(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", email):
|
314
|
+
raise CredentialsError("Please use a valid email address.", 400)
|
315
|
+
return email
|
316
|
+
|
317
|
+
|
318
|
+
def validate_username(username: str) -> str:
|
319
|
+
"""
|
320
|
+
Validates username format.
|
321
|
+
|
322
|
+
Args:
|
323
|
+
username (str): Username being validated.
|
324
|
+
|
325
|
+
Returns:
|
326
|
+
username
|
327
|
+
|
328
|
+
Raises:
|
329
|
+
CredentialsError
|
330
|
+
"""
|
331
|
+
if not re.search(r"^[A-Za-z0-9_-]{3,32}$", username):
|
332
|
+
raise CredentialsError(
|
333
|
+
"Username must be between 3-32 characters and not contain any special characters other than _ or -.",
|
334
|
+
400,
|
335
|
+
)
|
336
|
+
return username
|
337
|
+
|
338
|
+
|
339
|
+
def validate_phone(phone: str) -> str:
|
340
|
+
"""
|
341
|
+
Validates phone number format.
|
342
|
+
|
343
|
+
Args:
|
344
|
+
phone (str): Phone number being validated.
|
345
|
+
|
346
|
+
Returns:
|
347
|
+
phone
|
348
|
+
|
349
|
+
Raises:
|
350
|
+
CredentialsError
|
351
|
+
"""
|
352
|
+
if phone and not re.search(
|
353
|
+
r"^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$", phone
|
354
|
+
):
|
355
|
+
raise CredentialsError("Please use a valid phone number.", 400)
|
356
|
+
return phone
|
357
|
+
|
358
|
+
|
359
|
+
def validate_password(password: str) -> str:
|
360
|
+
"""
|
361
|
+
Validates password requirements.
|
362
|
+
|
363
|
+
Args:
|
364
|
+
password (str): Password being validated.
|
365
|
+
|
366
|
+
Returns:
|
367
|
+
password
|
368
|
+
|
369
|
+
Raises:
|
370
|
+
CredentialsError
|
371
|
+
"""
|
372
|
+
if not re.search(r"^(?=.*[A-Z])(?=.*\d)(?=.*[@#$%^&+=!]).*$", password):
|
373
|
+
raise CredentialsError(
|
374
|
+
"Password must contain one capital letter, one number, and one special character",
|
375
|
+
400,
|
376
|
+
)
|
377
|
+
return password
|
sanic_security/authorization.py
CHANGED
@@ -1,31 +1,33 @@
|
|
1
1
|
import functools
|
2
|
-
import logging
|
3
2
|
from fnmatch import fnmatch
|
4
3
|
|
5
4
|
from sanic.request import Request
|
6
5
|
from tortoise.exceptions import DoesNotExist
|
7
6
|
|
8
7
|
from sanic_security.authentication import authenticate
|
9
|
-
from sanic_security.exceptions import AuthorizationError
|
8
|
+
from sanic_security.exceptions import AuthorizationError, AnonymousError
|
10
9
|
from sanic_security.models import Role, Account, AuthenticationSession
|
11
|
-
from sanic_security.utils import get_ip
|
12
10
|
|
13
11
|
"""
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
12
|
+
Copyright (c) 2020-present Nicholas Aidan Stewart
|
13
|
+
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
16
|
+
in the Software without restriction, including without limitation the rights
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
19
|
+
furnished to do so, subject to the following conditions:
|
20
|
+
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
22
|
+
copies or substantial portions of the Software.
|
23
|
+
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
30
|
+
SOFTWARE.
|
29
31
|
"""
|
30
32
|
|
31
33
|
|
@@ -51,8 +53,11 @@ async def check_permissions(
|
|
51
53
|
UnverifiedError
|
52
54
|
DisabledError
|
53
55
|
AuthorizationError
|
56
|
+
AnonymousError
|
54
57
|
"""
|
55
58
|
authentication_session = await authenticate(request)
|
59
|
+
if authentication_session.anonymous:
|
60
|
+
raise AnonymousError()
|
56
61
|
roles = await authentication_session.bearer.roles.filter(deleted=False).all()
|
57
62
|
for role in roles:
|
58
63
|
for required_permission, role_permission in zip(
|
@@ -60,7 +65,6 @@ async def check_permissions(
|
|
60
65
|
):
|
61
66
|
if fnmatch(required_permission, role_permission):
|
62
67
|
return authentication_session
|
63
|
-
logging.warning(f"Client ({get_ip(request)}) has insufficient permissions.")
|
64
68
|
raise AuthorizationError("Insufficient permissions required for this action.")
|
65
69
|
|
66
70
|
|
@@ -84,16 +88,40 @@ async def check_roles(request: Request, *required_roles: str) -> AuthenticationS
|
|
84
88
|
UnverifiedError
|
85
89
|
DisabledError
|
86
90
|
AuthorizationError
|
91
|
+
AnonymousError
|
87
92
|
"""
|
88
93
|
authentication_session = await authenticate(request)
|
94
|
+
if authentication_session.anonymous:
|
95
|
+
raise AnonymousError()
|
89
96
|
roles = await authentication_session.bearer.roles.filter(deleted=False).all()
|
90
97
|
for role in roles:
|
91
98
|
if role.name in required_roles:
|
92
99
|
return authentication_session
|
93
|
-
logging.warning(f"Client ({get_ip(request)}) has insufficient roles.")
|
94
100
|
raise AuthorizationError("Insufficient roles required for this action.")
|
95
101
|
|
96
102
|
|
103
|
+
async def assign_role(
|
104
|
+
name: str, account: Account, permissions: str = None, description: str = None
|
105
|
+
) -> Role:
|
106
|
+
"""
|
107
|
+
Easy account role assignment. Role being assigned to an account will be created if it doesn't exist.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
name (str): The name of the role associated with the account.
|
111
|
+
account (Account): The account associated with the created role.
|
112
|
+
permissions (str): The permissions of the role associated with the account. Permissions must be separated via comma and in wildcard format.
|
113
|
+
description (str): The description of the role associated with the account.
|
114
|
+
"""
|
115
|
+
try:
|
116
|
+
role = await Role.filter(name=name).get()
|
117
|
+
except DoesNotExist:
|
118
|
+
role = await Role.create(
|
119
|
+
description=description, permissions=permissions, name=name
|
120
|
+
)
|
121
|
+
await account.roles.add(role)
|
122
|
+
return role
|
123
|
+
|
124
|
+
|
97
125
|
def require_permissions(*required_permissions: str):
|
98
126
|
"""
|
99
127
|
Authenticates client and determines if the account has sufficient permissions for an action.
|
@@ -118,6 +146,7 @@ def require_permissions(*required_permissions: str):
|
|
118
146
|
UnverifiedError
|
119
147
|
DisabledError
|
120
148
|
AuthorizationError
|
149
|
+
AnonymousError
|
121
150
|
"""
|
122
151
|
|
123
152
|
def decorator(func):
|
@@ -157,6 +186,7 @@ def require_roles(*required_roles: str):
|
|
157
186
|
UnverifiedError
|
158
187
|
DisabledError
|
159
188
|
AuthorizationError
|
189
|
+
AnonymousError
|
160
190
|
"""
|
161
191
|
|
162
192
|
def decorator(func):
|
@@ -170,25 +200,3 @@ def require_roles(*required_roles: str):
|
|
170
200
|
return wrapper
|
171
201
|
|
172
202
|
return decorator
|
173
|
-
|
174
|
-
|
175
|
-
async def assign_role(
|
176
|
-
name: str, account: Account, permissions: str = None, description: str = None
|
177
|
-
) -> Role:
|
178
|
-
"""
|
179
|
-
Easy account role assignment. Role being assigned to an account will be created if it doesn't exist.
|
180
|
-
|
181
|
-
Args:
|
182
|
-
name (str): The name of the role associated with the account.
|
183
|
-
account (Account): The account associated with the created role.
|
184
|
-
permissions (str): The permissions of the role associated with the account. Permissions must be separated via comma and in wildcard format.
|
185
|
-
description (str): The description of the role associated with the account.
|
186
|
-
"""
|
187
|
-
try:
|
188
|
-
role = await Role.filter(name=name).get()
|
189
|
-
except DoesNotExist:
|
190
|
-
role = await Role.create(
|
191
|
-
description=description, permissions=permissions, name=name
|
192
|
-
)
|
193
|
-
await account.roles.add(role)
|
194
|
-
return role
|
sanic_security/configuration.py
CHANGED
@@ -2,23 +2,26 @@ from os import environ
|
|
2
2
|
|
3
3
|
from sanic.utils import str_to_bool
|
4
4
|
|
5
|
-
|
6
5
|
"""
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
Copyright (c) 2020-present Nicholas Aidan Stewart
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
10
|
+
in the Software without restriction, including without limitation the rights
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
16
|
+
copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
SOFTWARE.
|
22
25
|
"""
|
23
26
|
|
24
27
|
|
@@ -34,8 +37,9 @@ DEFAULT_CONFIG = {
|
|
34
37
|
"MAX_CHALLENGE_ATTEMPTS": 5,
|
35
38
|
"CAPTCHA_SESSION_EXPIRATION": 60,
|
36
39
|
"CAPTCHA_FONT": "captcha-font.ttf",
|
37
|
-
"TWO_STEP_SESSION_EXPIRATION":
|
38
|
-
"AUTHENTICATION_SESSION_EXPIRATION":
|
40
|
+
"TWO_STEP_SESSION_EXPIRATION": 300,
|
41
|
+
"AUTHENTICATION_SESSION_EXPIRATION": 86400,
|
42
|
+
"AUTHENTICATION_REFRESH_EXPIRATION": 2592000,
|
39
43
|
"ALLOW_LOGIN_WITH_USERNAME": False,
|
40
44
|
"INITIAL_ADMIN_EMAIL": "admin@example.com",
|
41
45
|
"INITIAL_ADMIN_PASSWORD": "admin123",
|
@@ -59,8 +63,9 @@ class Config(dict):
|
|
59
63
|
MAX_CHALLENGE_ATTEMPTS (str): The maximum amount of session challenge attempts allowed.
|
60
64
|
CAPTCHA_SESSION_EXPIRATION (int): The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration.
|
61
65
|
CAPTCHA_FONT (str): The file path to the font being used for captcha generation.
|
62
|
-
TWO_STEP_SESSION_EXPIRATION (int): The amount of seconds till two
|
63
|
-
AUTHENTICATION_SESSION_EXPIRATION (
|
66
|
+
TWO_STEP_SESSION_EXPIRATION (int): The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration.
|
67
|
+
AUTHENTICATION_SESSION_EXPIRATION (int): The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration.
|
68
|
+
AUTHENTICATION_REFRESH_EXPIRATION (int): The amount of seconds till authentication session refresh expiration.
|
64
69
|
ALLOW_LOGIN_WITH_USERNAME (bool): Allows login via username and email.
|
65
70
|
INITIAL_ADMIN_EMAIL (str): Email used when creating the initial admin account.
|
66
71
|
INITIAL_ADMIN_PASSWORD (str): Password used when creating the initial admin account.
|
@@ -80,6 +85,7 @@ class Config(dict):
|
|
80
85
|
CAPTCHA_FONT: str
|
81
86
|
TWO_STEP_SESSION_EXPIRATION: int
|
82
87
|
AUTHENTICATION_SESSION_EXPIRATION: int
|
88
|
+
AUTHENTICATION_REFRESH_EXPIRATION: int
|
83
89
|
ALLOW_LOGIN_WITH_USERNAME: bool
|
84
90
|
INITIAL_ADMIN_EMAIL: str
|
85
91
|
INITIAL_ADMIN_PASSWORD: str
|