sanic-security 1.12.6__py3-none-any.whl → 1.12.7__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.
@@ -1,5 +1,6 @@
1
1
  import functools
2
2
  import re
3
+ import warnings
3
4
 
4
5
  from argon2 import PasswordHasher
5
6
  from argon2.exceptions import VerifyMismatchError
@@ -8,7 +9,7 @@ from sanic.log import logger
8
9
  from sanic.request import Request
9
10
  from tortoise.exceptions import DoesNotExist
10
11
 
11
- from sanic_security.configuration import config as security_config
12
+ from sanic_security.configuration import config as security_config, DEFAULT_CONFIG
12
13
  from sanic_security.exceptions import (
13
14
  CredentialsError,
14
15
  DeactivatedError,
@@ -16,6 +17,7 @@ from sanic_security.exceptions import (
16
17
  ExpiredError,
17
18
  )
18
19
  from sanic_security.models import Account, AuthenticationSession, Role, TwoStepSession
20
+ from sanic_security.utils import get_ip
19
21
 
20
22
  """
21
23
  Copyright (c) 2020-present Nicholas Aidan Stewart
@@ -75,15 +77,15 @@ async def register(
75
77
  raise CredentialsError(
76
78
  "An account with this phone number may already exist.", 409
77
79
  )
78
- validate_password(request.form.get("password"))
79
80
  account = await Account.create(
80
81
  email=email_lower,
81
82
  username=request.form.get("username"),
82
- password=password_hasher.hash(request.form.get("password")),
83
+ password=password_hasher.hash(validate_password(request.form.get("password"))),
83
84
  phone=request.form.get("phone"),
84
85
  verified=verified,
85
86
  disabled=disabled,
86
87
  )
88
+ logger.info(f"Client {get_ip(request)} has registered account {account.id}.")
87
89
  return account
88
90
 
89
91
 
@@ -122,9 +124,13 @@ async def login(
122
124
  account.password = password_hasher.hash(password)
123
125
  await account.save(update_fields=["password"])
124
126
  account.validate()
125
- return await AuthenticationSession.new(
127
+ authentication_session = await AuthenticationSession.new(
126
128
  request, account, requires_second_factor=require_second_factor
127
129
  )
130
+ logger.info(
131
+ f"Client has logged into account {account.id} with authentication session {authentication_session.id}."
132
+ )
133
+ return authentication_session
128
134
  except VerifyMismatchError:
129
135
  raise CredentialsError("Incorrect password.", 401)
130
136
 
@@ -149,6 +155,10 @@ async def logout(request: Request) -> AuthenticationSession:
149
155
  raise DeactivatedError("Already logged out.", 403)
150
156
  authentication_session.active = False
151
157
  await authentication_session.save(update_fields=["active"])
158
+ logger.info(
159
+ f"Client has logged out{" anonymously" if authentication_session.anonymous else
160
+ f" of account {authentication_session.bearer.id}"} with authentication session {authentication_session.id}."
161
+ )
152
162
  return authentication_session
153
163
 
154
164
 
@@ -177,9 +187,12 @@ async def fulfill_second_factor(request: Request) -> AuthenticationSession:
177
187
  raise SecondFactorFulfilledError()
178
188
  two_step_session = await TwoStepSession.decode(request)
179
189
  two_step_session.validate()
180
- await two_step_session.check_code(request, request.form.get("code"))
190
+ await two_step_session.check_code(request.form.get("code"))
181
191
  authentication_session.requires_second_factor = False
182
192
  await authentication_session.save(update_fields=["requires_second_factor"])
193
+ logger.info(
194
+ f"Authentication session {authentication_session.id} second factor has been fulfilled."
195
+ )
183
196
  return authentication_session
184
197
 
185
198
 
@@ -275,13 +288,22 @@ def create_initial_admin_account(app: Sanic) -> None:
275
288
 
276
289
  @app.listener("before_server_start")
277
290
  async def create(app, loop):
291
+ if security_config.SECRET == DEFAULT_CONFIG["SECRET"]:
292
+ warnings.warn("Secret should be changed from default.")
293
+ if security_config.INITIAL_ADMIN_EMAIL == DEFAULT_CONFIG["INITIAL_ADMIN_EMAIL"]:
294
+ warnings.warn("Initial admin email should be changed from default.")
295
+ if (
296
+ security_config.INITIAL_ADMIN_PASSWORD
297
+ == DEFAULT_CONFIG["INITIAL_ADMIN_PASSWORD"]
298
+ ):
299
+ warnings.warn("Initial admin password should be changed from default.")
278
300
  try:
279
- role = await Role.filter(name="Head Admin").get()
301
+ role = await Role.filter(name="Admin").get()
280
302
  except DoesNotExist:
281
303
  role = await Role.create(
282
304
  description="Has root abilities, assign sparingly.",
283
305
  permissions="*:*",
284
- name="Head Admin",
306
+ name="Admin",
285
307
  )
286
308
  try:
287
309
  account = await Account.filter(
@@ -293,7 +315,7 @@ def create_initial_admin_account(app: Sanic) -> None:
293
315
  logger.warning("Initial admin account role has been reinstated.")
294
316
  except DoesNotExist:
295
317
  account = await Account.create(
296
- username="Head-Admin",
318
+ username="Admin",
297
319
  email=security_config.INITIAL_ADMIN_EMAIL,
298
320
  password=password_hasher.hash(security_config.INITIAL_ADMIN_PASSWORD),
299
321
  verified=True,
@@ -1,6 +1,7 @@
1
1
  import functools
2
2
  from fnmatch import fnmatch
3
3
 
4
+ from sanic.log import logger
4
5
  from sanic.request import Request
5
6
  from tortoise.exceptions import DoesNotExist
6
7
 
@@ -119,6 +120,7 @@ async def assign_role(
119
120
  description=description, permissions=permissions, name=name
120
121
  )
121
122
  await account.roles.add(role)
123
+ logger.info(f"Role {role.id} has been assigned to account {account.id}.")
122
124
  return role
123
125
 
124
126
 
@@ -24,7 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  SOFTWARE.
25
25
  """
26
26
 
27
-
28
27
  DEFAULT_CONFIG = {
29
28
  "SECRET": "This is a big secret. Shhhhh",
30
29
  "PUBLIC_SECRET": None,
sanic_security/models.py CHANGED
@@ -142,6 +142,7 @@ class Account(BaseModel):
142
142
  else:
143
143
  self.disabled = True
144
144
  await self.save(update_fields=["disabled"])
145
+ logger.info(f"Account {self.id} has been disabled.")
145
146
 
146
147
  @property
147
148
  def json(self) -> dict:
@@ -319,6 +320,7 @@ class Session(BaseModel):
319
320
  if self.active:
320
321
  self.active = False
321
322
  await self.save(update_fields=["active"])
323
+ logger.info(f"Session {self.id} has been deactivated.")
322
324
  else:
323
325
  raise DeactivatedError("Session is already deactivated.", 403)
324
326
 
@@ -493,13 +495,12 @@ class VerificationSession(Session):
493
495
  attempts: int = fields.IntField(default=0)
494
496
  code: str = fields.CharField(max_length=10, default=get_code, null=True)
495
497
 
496
- async def check_code(self, request: Request, code: str) -> None:
498
+ async def check_code(self, code: str) -> None:
497
499
  """
498
500
  Checks if code passed is equivalent to the session code.
499
501
 
500
502
  Args:
501
503
  code (str): Code being cross-checked with session code.
502
- request (Request): Sanic request parameter.
503
504
 
504
505
  Raises:
505
506
  ChallengeError
@@ -513,13 +514,12 @@ class VerificationSession(Session):
513
514
  "Your code does not match verification session code."
514
515
  )
515
516
  else:
516
- logger.warning(
517
- f"Client ({get_ip(request)}) has maxed out on session challenge attempts"
518
- )
519
517
  raise MaxedOutChallengeError()
520
518
  else:
521
- self.active = False
522
- await self.save(update_fields=["active"])
519
+ logger.info(
520
+ f"Client has completed verification session {self.id} challenge."
521
+ )
522
+ await self.deactivate()
523
523
 
524
524
  @classmethod
525
525
  async def new(cls, request: Request, account: Account, **kwargs):
@@ -633,6 +633,7 @@ class AuthenticationSession(Session):
633
633
  ):
634
634
  self.active = False
635
635
  await self.save(update_fields=["active"])
636
+ logger.info(f"Client has refreshed authentication session {self.id}.")
636
637
  return await self.new(request, self.bearer, True)
637
638
  else:
638
639
  raise e
@@ -62,9 +62,7 @@ password_hasher = PasswordHasher()
62
62
 
63
63
  @app.post("api/test/auth/register")
64
64
  async def on_register(request):
65
- """
66
- Register an account with email and password.
67
- """
65
+ """Register an account with email and password."""
68
66
  account = await register(
69
67
  request,
70
68
  verified=request.form.get("verified") == "true",
@@ -83,9 +81,7 @@ async def on_register(request):
83
81
 
84
82
  @app.post("api/test/auth/verify")
85
83
  async def on_verify(request):
86
- """
87
- Verifies client account.
88
- """
84
+ """Verifies client account."""
89
85
  two_step_session = await verify_account(request)
90
86
  return json(
91
87
  "You have verified your account and may login!", two_step_session.bearer.json
@@ -94,9 +90,7 @@ async def on_verify(request):
94
90
 
95
91
  @app.post("api/test/auth/login")
96
92
  async def on_login(request):
97
- """
98
- Login to an account with an email and password.
99
- """
93
+ """Login to an account with an email and password."""
100
94
  two_factor_authentication = request.args.get("two-factor-authentication") == "true"
101
95
  authentication_session = await login(
102
96
  request, require_second_factor=two_factor_authentication
@@ -118,9 +112,7 @@ async def on_login(request):
118
112
 
119
113
  @app.post("api/test/auth/login/anon")
120
114
  async def on_login_anonymous(request):
121
- """
122
- Login as anonymous user.
123
- """
115
+ """Login as anonymous user."""
124
116
  authentication_session = await AuthenticationSession.new(request)
125
117
  response = json(
126
118
  "Anonymous user now associated with session!", authentication_session.json
@@ -131,9 +123,7 @@ async def on_login_anonymous(request):
131
123
 
132
124
  @app.post("api/test/auth/validate-2fa")
133
125
  async def on_two_factor_authentication(request):
134
- """
135
- Fulfills client authentication session's second factor requirement.
136
- """
126
+ """Fulfills client authentication session's second factor requirement."""
137
127
  authentication_session = await fulfill_second_factor(request)
138
128
  response = json(
139
129
  "Authentication session second-factor fulfilled! You are now authenticated.",
@@ -145,9 +135,7 @@ async def on_two_factor_authentication(request):
145
135
 
146
136
  @app.post("api/test/auth/logout")
147
137
  async def on_logout(request):
148
- """
149
- Logout of currently logged in account.
150
- """
138
+ """Logout of currently logged in account."""
151
139
  authentication_session = await logout(request)
152
140
  response = json("Logout successful!", authentication_session.json)
153
141
  return response
@@ -156,9 +144,7 @@ async def on_logout(request):
156
144
  @app.post("api/test/auth")
157
145
  @requires_authentication
158
146
  async def on_authenticate(request):
159
- """
160
- Authenticate client session and account.
161
- """
147
+ """Authenticate client session and account."""
162
148
  authentication_session = request.ctx.authentication_session
163
149
  response = json(
164
150
  "Authenticated!",
@@ -177,9 +163,7 @@ async def on_authenticate(request):
177
163
  @app.post("api/test/auth/expire")
178
164
  @requires_authentication
179
165
  async def on_authentication_expire(request):
180
- """
181
- Expire client's session.
182
- """
166
+ """Expire client's session."""
183
167
  authentication_session = request.ctx.authentication_session
184
168
  authentication_session.expiration_date = datetime.datetime.now(datetime.UTC)
185
169
  await authentication_session.save(update_fields=["expiration_date"])
@@ -189,9 +173,7 @@ async def on_authentication_expire(request):
189
173
  @app.post("api/test/auth/associated")
190
174
  @requires_authentication
191
175
  async def on_get_associated_authentication_sessions(request):
192
- """
193
- Retrieves authentication sessions associated with logged in account.
194
- """
176
+ """Retrieves authentication sessions associated with logged in account."""
195
177
  authentication_sessions = await AuthenticationSession.get_associated(
196
178
  request.ctx.authentication_session.bearer
197
179
  )
@@ -203,9 +185,7 @@ async def on_get_associated_authentication_sessions(request):
203
185
 
204
186
  @app.get("api/test/capt/request")
205
187
  async def on_captcha_request(request):
206
- """
207
- Request captcha with solution in response.
208
- """
188
+ """Request captcha with solution in response."""
209
189
  captcha_session = await request_captcha(request)
210
190
  response = json("Captcha request successful!", captcha_session.code)
211
191
  captcha_session.encode(response)
@@ -214,9 +194,7 @@ async def on_captcha_request(request):
214
194
 
215
195
  @app.get("api/test/capt/image")
216
196
  async def on_captcha_image(request):
217
- """
218
- Request captcha image.
219
- """
197
+ """Request captcha image."""
220
198
  captcha_session = await CaptchaSession.decode(request)
221
199
  response = captcha_session.get_image()
222
200
  captcha_session.encode(response)
@@ -226,17 +204,13 @@ async def on_captcha_image(request):
226
204
  @app.post("api/test/capt")
227
205
  @requires_captcha
228
206
  async def on_captcha_attempt(request):
229
- """
230
- Attempt captcha challenge.
231
- """
207
+ """Attempt captcha challenge."""
232
208
  return json("Captcha attempt successful!", request.ctx.captcha_session.json)
233
209
 
234
210
 
235
211
  @app.post("api/test/two-step/request")
236
212
  async def on_request_verification(request):
237
- """
238
- Request two-step verification with code in the response.
239
- """
213
+ """Request two-step verification with code in the response."""
240
214
  two_step_session = await request_two_step_verification(request)
241
215
  response = json("Verification request successful!", two_step_session.code)
242
216
  two_step_session.encode(response)
@@ -246,9 +220,7 @@ async def on_request_verification(request):
246
220
  @app.post("api/test/two-step")
247
221
  @requires_two_step_verification
248
222
  async def on_verification_attempt(request):
249
- """
250
- Attempt two-step verification challenge.
251
- """
223
+ """Attempt two-step verification challenge."""
252
224
  return json(
253
225
  "Two step verification attempt successful!", request.ctx.two_step_session.json
254
226
  )
@@ -257,9 +229,7 @@ async def on_verification_attempt(request):
257
229
  @app.post("api/test/auth/roles")
258
230
  @requires_authentication
259
231
  async def on_authorization(request):
260
- """
261
- Check if client is authorized with sufficient roles and permissions.
262
- """
232
+ """Check if client is authorized with sufficient roles and permissions."""
263
233
  await check_roles(request, request.form.get("role"))
264
234
  if request.form.get("permissions_required"):
265
235
  await check_permissions(
@@ -271,9 +241,7 @@ async def on_authorization(request):
271
241
  @app.post("api/test/auth/roles/assign")
272
242
  @requires_authentication
273
243
  async def on_role_assign(request):
274
- """
275
- Assign authenticated account a role.
276
- """
244
+ """Assign authenticated account a role."""
277
245
  await assign_role(
278
246
  request.form.get("name"),
279
247
  request.ctx.authentication_session.bearer,
@@ -285,9 +253,7 @@ async def on_role_assign(request):
285
253
 
286
254
  @app.post("api/test/account")
287
255
  async def on_account_creation(request):
288
- """
289
- Quick account creation.
290
- """
256
+ """Quick account creation."""
291
257
  account = await Account.create(
292
258
  username=request.form.get("username"),
293
259
  email=request.form.get("email").lower(),
@@ -301,9 +267,7 @@ async def on_account_creation(request):
301
267
 
302
268
  @app.exception(SecurityError)
303
269
  async def on_security_error(request, exception):
304
- """
305
- Handles security errors with correct response.
306
- """
270
+ """Handles security errors with correct response."""
307
271
  traceback.print_exc()
308
272
  return exception.json
309
273
 
@@ -30,9 +30,7 @@ SOFTWARE.
30
30
 
31
31
 
32
32
  class RegistrationTest(TestCase):
33
- """
34
- Registration tests.
35
- """
33
+ """Registration tests."""
36
34
 
37
35
  def setUp(self):
38
36
  self.client = httpx.Client()
@@ -62,9 +60,7 @@ class RegistrationTest(TestCase):
62
60
  return registration_response
63
61
 
64
62
  def test_registration(self):
65
- """
66
- Account registration and login.
67
- """
63
+ """Account registration and login."""
68
64
  registration_response = self.register(
69
65
  "account_registration@register.test",
70
66
  "account_registration",
@@ -80,9 +76,7 @@ class RegistrationTest(TestCase):
80
76
  assert login_response.status_code == 200, login_response.text
81
77
 
82
78
  def test_invalid_registration(self):
83
- """
84
- Registration with an intentionally invalid email, username, and phone.
85
- """
79
+ """Registration with an intentionally invalid email, username, and phone."""
86
80
  invalid_email_registration_response = self.register(
87
81
  "invalid_register.test", "invalid_register", False, True
88
82
  )
@@ -112,9 +106,7 @@ class RegistrationTest(TestCase):
112
106
  ), too_many_characters_registration_response.text
113
107
 
114
108
  def test_registration_disabled(self):
115
- """
116
- Registration and login with a disabled account.
117
- """
109
+ """Registration and login with a disabled account."""
118
110
  registration_response = self.register(
119
111
  "disabled@register.test", "disabled", True, True
120
112
  )
@@ -126,9 +118,7 @@ class RegistrationTest(TestCase):
126
118
  assert "DisabledError" in login_response.text, login_response.text
127
119
 
128
120
  def test_registration_unverified(self):
129
- """
130
- Registration and login with an unverified account.
131
- """
121
+ """Registration and login with an unverified account."""
132
122
  registration_response = self.register(
133
123
  "unverified@register.test", "unverified", False, False
134
124
  )
@@ -140,9 +130,7 @@ class RegistrationTest(TestCase):
140
130
  assert "UnverifiedError" in login_response.text, login_response.text
141
131
 
142
132
  def test_registration_unverified_disabled(self):
143
- """
144
- Registration and login with an unverified and disabled account.
145
- """
133
+ """Registration and login with an unverified and disabled account."""
146
134
  registration_response = self.register(
147
135
  "unverified_disabled@register.test", "unverified_disabled", True, False
148
136
  )
@@ -155,9 +143,7 @@ class RegistrationTest(TestCase):
155
143
 
156
144
 
157
145
  class LoginTest(TestCase):
158
- """
159
- Login tests.
160
- """
146
+ """Login tests."""
161
147
 
162
148
  def setUp(self):
163
149
  self.client = httpx.Client()
@@ -166,9 +152,7 @@ class LoginTest(TestCase):
166
152
  self.client.close()
167
153
 
168
154
  def test_login(self):
169
- """
170
- Login with an email and password.
171
- """
155
+ """Login with an email and password."""
172
156
  self.client.post(
173
157
  "http://127.0.0.1:8000/api/test/account",
174
158
  data={"email": "email_pass@login.test", "username": "email_pass"},
@@ -184,9 +168,7 @@ class LoginTest(TestCase):
184
168
  assert authenticate_response.status_code == 200, authenticate_response.text
185
169
 
186
170
  def test_login_with_username(self):
187
- """
188
- Login with a username instead of an email and password.
189
- """
171
+ """Login with a username instead of an email and password."""
190
172
  self.client.post(
191
173
  "http://127.0.0.1:8000/api/test/account",
192
174
  data={"email": "user_pass@login.test", "username": "user_pass"},
@@ -202,9 +184,7 @@ class LoginTest(TestCase):
202
184
  assert authenticate_response.status_code == 200, authenticate_response.text
203
185
 
204
186
  def test_invalid_login(self):
205
- """
206
- Login with an intentionally incorrect password and into a non existent account.
207
- """
187
+ """Login with an intentionally incorrect password and into a non-existent account."""
208
188
  self.client.post(
209
189
  "http://127.0.0.1:8000/api/test/account",
210
190
  data={"email": "incorrect_pass@login.test", "username": "incorrect_pass"},
@@ -225,9 +205,7 @@ class LoginTest(TestCase):
225
205
  ), unavailable_account_login_response
226
206
 
227
207
  def test_logout(self):
228
- """
229
- Logout of logged in account and attempt to authenticate.
230
- """
208
+ """Logout of logged in account and attempt to authenticate."""
231
209
  self.client.post(
232
210
  "http://127.0.0.1:8000/api/test/account",
233
211
  data={"email": "logout@login.test", "username": "logout"},
@@ -244,9 +222,7 @@ class LoginTest(TestCase):
244
222
  assert authenticate_response.status_code == 401, authenticate_response.text
245
223
 
246
224
  def test_initial_admin_login(self):
247
- """
248
- Initial admin account login and authorization.
249
- """
225
+ """Initial admin account login and authorization."""
250
226
  login_response = self.client.post(
251
227
  "http://127.0.0.1:8000/api/test/auth/login",
252
228
  auth=("admin@login.test", "admin123"),
@@ -255,7 +231,7 @@ class LoginTest(TestCase):
255
231
  permitted_authorization_response = self.client.post(
256
232
  "http://127.0.0.1:8000/api/test/auth/roles",
257
233
  data={
258
- "role": "Head Admin",
234
+ "role": "Admin",
259
235
  "permissions_required": "perm1:create,add, perm2:*",
260
236
  },
261
237
  )
@@ -264,9 +240,7 @@ class LoginTest(TestCase):
264
240
  ), permitted_authorization_response.text
265
241
 
266
242
  def test_two_factor_login(self):
267
- """
268
- Test login with two-factor authentication requirement.
269
- """
243
+ """Test login with two-factor authentication requirement."""
270
244
  self.client.post(
271
245
  "http://127.0.0.1:8000/api/test/account",
272
246
  data={"email": "two-factor@login.test", "username": "two-factor"},
@@ -295,9 +269,7 @@ class LoginTest(TestCase):
295
269
  assert authenticate_response.status_code == 200, authenticate_response.text
296
270
 
297
271
  def test_anonymous_login(self):
298
- """
299
- Test login of anonymous user.
300
- """
272
+ """Test login of anonymous user."""
301
273
  anon_login_response = self.client.post(
302
274
  "http://127.0.0.1:8000/api/test/auth/login/anon"
303
275
  )
@@ -311,9 +283,7 @@ class LoginTest(TestCase):
311
283
 
312
284
 
313
285
  class VerificationTest(TestCase):
314
- """
315
- Two-step verification and captcha tests.
316
- """
286
+ """Two-step verification and captcha tests."""
317
287
 
318
288
  def setUp(self):
319
289
  self.client = httpx.Client()
@@ -322,9 +292,7 @@ class VerificationTest(TestCase):
322
292
  self.client.close()
323
293
 
324
294
  def test_captcha(self):
325
- """
326
- Captcha request and attempt.
327
- """
295
+ """Captcha request and attempt."""
328
296
  captcha_request_response = self.client.get(
329
297
  "http://127.0.0.1:8000/api/test/capt/request"
330
298
  )
@@ -344,9 +312,7 @@ class VerificationTest(TestCase):
344
312
  ), captcha_attempt_response.text
345
313
 
346
314
  def test_two_step_verification(self):
347
- """
348
- Two-step verification request and attempt.
349
- """
315
+ """Two-step verification request and attempt."""
350
316
  self.client.post(
351
317
  "http://127.0.0.1:8000/api/test/account",
352
318
  data={"email": "two_step@verification.test", "username": "two_step"},
@@ -382,9 +348,7 @@ class VerificationTest(TestCase):
382
348
  ), two_step_verification_no_email_request_response.text
383
349
 
384
350
  def test_account_verification(self):
385
- """
386
- Account registration and verification process with successful login.
387
- """
351
+ """Account registration and verification process with successful login."""
388
352
  registration_response = self.client.post(
389
353
  "http://127.0.0.1:8000/api/test/auth/register",
390
354
  data={
@@ -404,9 +368,7 @@ class VerificationTest(TestCase):
404
368
 
405
369
 
406
370
  class AuthorizationTest(TestCase):
407
- """
408
- Role and permissions based authorization tests.
409
- """
371
+ """Role and permissions based authorization tests."""
410
372
 
411
373
  def setUp(self):
412
374
  self.client = httpx.Client()
@@ -415,9 +377,7 @@ class AuthorizationTest(TestCase):
415
377
  self.client.close()
416
378
 
417
379
  def test_permissions_authorization(self):
418
- """
419
- Authorization with permissions.
420
- """
380
+ """Authorization with permissions."""
421
381
  self.client.post(
422
382
  "http://127.0.0.1:8000/api/test/account",
423
383
  data={"email": "permissions@authorization.test", "username": "permissions"},
@@ -455,9 +415,7 @@ class AuthorizationTest(TestCase):
455
415
  ), prohibited_authorization_response.text
456
416
 
457
417
  def test_roles_authorization(self):
458
- """
459
- Authorization with roles.
460
- """
418
+ """Authorization with roles."""
461
419
  self.client.post(
462
420
  "http://127.0.0.1:8000/api/test/account",
463
421
  data={"email": "roles@authorization.test", "username": "roles"},
@@ -488,6 +446,7 @@ class AuthorizationTest(TestCase):
488
446
  ), prohibited_authorization_response.text
489
447
 
490
448
  def test_anonymous_authorization(self):
449
+ """Authorization with anonymous client."""
491
450
  anon_login_response = self.client.post(
492
451
  "http://127.0.0.1:8000/api/test/auth/login/anon"
493
452
  )
@@ -506,9 +465,7 @@ class AuthorizationTest(TestCase):
506
465
 
507
466
 
508
467
  class MiscTest(TestCase):
509
- """
510
- Miscellaneous tests that cannot be categorized.
511
- """
468
+ """Miscellaneous tests that cannot be categorized."""
512
469
 
513
470
  def setUp(self):
514
471
  self.client = httpx.Client()
@@ -517,18 +474,14 @@ class MiscTest(TestCase):
517
474
  self.client.close()
518
475
 
519
476
  def test_environment_variable_load(self):
520
- """
521
- Config loads environment variables.
522
- """
477
+ """Config loads environment variables."""
523
478
  os.environ["SANIC_SECURITY_SECRET"] = "test-secret"
524
479
  security_config = Config()
525
480
  security_config.load_environment_variables()
526
481
  assert security_config.SECRET == "test-secret"
527
482
 
528
483
  def test_get_associated_sessions(self):
529
- """
530
- Retrieve sessions associated to logged in account.
531
- """
484
+ """Retrieve sessions associated to logged in account."""
532
485
  self.client.post(
533
486
  "http://127.0.0.1:8000/api/test/account",
534
487
  data={
@@ -549,9 +502,7 @@ class MiscTest(TestCase):
549
502
  ), retrieve_associated_response.text
550
503
 
551
504
  def test_authentication_refresh(self):
552
- """
553
- Test automatic authentication refresh.
554
- """
505
+ """Test automatic authentication refresh."""
555
506
  self.client.post(
556
507
  "http://127.0.0.1:8000/api/test/account",
557
508
  data={
@@ -1,6 +1,7 @@
1
1
  import functools
2
2
  from contextlib import suppress
3
3
 
4
+ from sanic.log import logger
4
5
  from sanic.request import Request
5
6
 
6
7
  from sanic_security.exceptions import (
@@ -84,12 +85,12 @@ async def two_step_verification(request: Request) -> TwoStepSession:
84
85
  MaxedOutChallengeError
85
86
 
86
87
  Returns:
87
- two_step_session
88
+ two_step_session
88
89
  """
89
90
  two_step_session = await TwoStepSession.decode(request)
90
91
  two_step_session.validate()
91
92
  two_step_session.bearer.validate()
92
- await two_step_session.check_code(request, request.form.get("code"))
93
+ await two_step_session.check_code(request.form.get("code"))
93
94
  return two_step_session
94
95
 
95
96
 
@@ -153,9 +154,10 @@ async def verify_account(request: Request) -> TwoStepSession:
153
154
  if two_step_session.bearer.verified:
154
155
  raise VerifiedError()
155
156
  two_step_session.validate()
156
- await two_step_session.check_code(request, request.form.get("code"))
157
+ await two_step_session.check_code(request.form.get("code"))
157
158
  two_step_session.bearer.verified = True
158
159
  await two_step_session.bearer.save(update_fields=["verified"])
160
+ logger.info(f"Account {two_step_session.bearer.id} has been verified.")
159
161
  return two_step_session
160
162
 
161
163
 
@@ -197,7 +199,7 @@ async def captcha(request: Request) -> CaptchaSession:
197
199
  """
198
200
  captcha_session = await CaptchaSession.decode(request)
199
201
  captcha_session.validate()
200
- await captcha_session.check_code(request, request.form.get("captcha"))
202
+ await captcha_session.check_code(request.form.get("captcha"))
201
203
  return captcha_session
202
204
 
203
205
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sanic-security
3
- Version: 1.12.6
3
+ Version: 1.12.7
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/
@@ -172,7 +172,7 @@ The tables in the below examples represent example [request form-data](https://s
172
172
 
173
173
  * Initial Administrator Account
174
174
 
175
- Creates initial admin account, you should modify its credentials in config!
175
+ Creates root account if it doesn't exist, you should modify its credentials in config!
176
176
 
177
177
  ```python
178
178
  create_initial_admin_account(app)
@@ -0,0 +1,16 @@
1
+ sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ sanic_security/authentication.py,sha256=CzjbVh7uDjBs22oQCklcbmp5gTDW17AoYaa3lmyPnGk,13783
3
+ sanic_security/authorization.py,sha256=80vuM_2oNgfhOMuct89R6UBhDYmSWQzIgocKxhKzReE,7030
4
+ sanic_security/configuration.py,sha256=MKxYjq1q9RBRX2cMJkIe87ke0mLKa69RWoQ5MhVciho,5512
5
+ sanic_security/exceptions.py,sha256=MTPF4tm_68Nmf_z06RHH_6DTiC_CNiLER1jzEoW1dFk,5398
6
+ sanic_security/models.py,sha256=zWn9zXl-vwP8qJ-DzBuNNG7MCl1ggZX6yb6Zgh3Jfcs,22601
7
+ sanic_security/utils.py,sha256=XAUNalcTi53qTz0D8xiDyDyRlq7Z7ffNBzUONJZqe90,2705
8
+ sanic_security/verification.py,sha256=rNyZk_J53dOzk8qy5iG3yiMRuRcGaYeHwIwnFDH9TWw,7582
9
+ sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ sanic_security/test/server.py,sha256=-dQAB75C-l9kfN7PnaYNDOLiLXaqg0euZAWF5oXMxeE,12164
11
+ sanic_security/test/tests.py,sha256=jUZ5kgQF4rFx1bImKqHYR7UeoYAfjtNNXSQYTMfzlg4,21994
12
+ sanic_security-1.12.7.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
13
+ sanic_security-1.12.7.dist-info/METADATA,sha256=ZGVTjtv-OVMrs_3Ac__jKE1JgdhtIAyhMCW2n3VUW3Y,23393
14
+ sanic_security-1.12.7.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
15
+ sanic_security-1.12.7.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
16
+ sanic_security-1.12.7.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- sanic_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- sanic_security/authentication.py,sha256=obMKNnJXleeBGXqmsm1y5jFNI-FrW9krdO5SD6yOstE,12598
3
- sanic_security/authorization.py,sha256=aQztMiZG9LDctr_C6QEzO5qScwbxpiLk96XVxwdCChM,6921
4
- sanic_security/configuration.py,sha256=p44nTSrBQQSJZYN6qJEod_Ettf90rRNlmPxmNzxqQ9A,5514
5
- sanic_security/exceptions.py,sha256=MTPF4tm_68Nmf_z06RHH_6DTiC_CNiLER1jzEoW1dFk,5398
6
- sanic_security/models.py,sha256=nj5iYHzPZzdLs5dc3j6kdeScSk1SASizfK58Sa5YN8E,22527
7
- sanic_security/utils.py,sha256=XAUNalcTi53qTz0D8xiDyDyRlq7Z7ffNBzUONJZqe90,2705
8
- sanic_security/verification.py,sha256=vrxYborEOBKEirOHczul9WYub5j6T2ldXE1gsoA8iyY,7503
9
- sanic_security/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- sanic_security/test/server.py,sha256=pwqsDS81joMdxIynivaNPCCMamv9qzAjknfZ01ZxQHc,12380
11
- sanic_security/test/tests.py,sha256=6TUp5GVYIR27qCzwIw2qt7DvW7ohxj-seYpnpeMbuno,22407
12
- sanic_security-1.12.6.dist-info/LICENSE,sha256=sXlJs9_mG-dCkPfWsDnuzydJWagS82E2gYtkVH9enHA,1100
13
- sanic_security-1.12.6.dist-info/METADATA,sha256=aiKkOtkYiexSjoB4uysSQwxAVqRGAQnultZKvx5srAs,23382
14
- sanic_security-1.12.6.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
15
- sanic_security-1.12.6.dist-info/top_level.txt,sha256=ZybkhHXSjfzhmv8XeqLvnNmLmv21Z0oPX6Ep4DJN8b0,15
16
- sanic_security-1.12.6.dist-info/RECORD,,