square-authentication 6.2.2__py3-none-any.whl → 8.0.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.
@@ -1,21 +1,42 @@
1
+ import random
2
+ from datetime import timezone, datetime, timedelta
1
3
  from typing import Annotated, Optional
2
4
 
3
- from fastapi import APIRouter, HTTPException, status, Header, UploadFile
5
+ import bcrypt
6
+ from fastapi import APIRouter, Header, HTTPException, UploadFile, status
4
7
  from fastapi.responses import JSONResponse
5
8
  from square_commons import get_api_output_in_standard_format
9
+ from square_commons.email import send_email_using_mailgun
6
10
  from square_database_helper import FiltersV0
7
11
  from square_database_helper.pydantic_models import FilterConditionsV0
8
12
  from square_database_structure.square import global_string_database_name
9
13
  from square_database_structure.square.authentication import global_string_schema_name
10
- from square_database_structure.square.authentication.tables import UserProfile
14
+ from square_database_structure.square.authentication.enums import (
15
+ VerificationCodeTypeEnum,
16
+ )
17
+ from square_database_structure.square.authentication.tables import (
18
+ UserProfile,
19
+ UserVerificationCode,
20
+ )
21
+ from square_database_structure.square.email import (
22
+ global_string_schema_name as email_schema_name,
23
+ )
24
+ from square_database_structure.square.email.enums import EmailTypeEnum, EmailStatusEnum
25
+ from square_database_structure.square.email.tables import EmailLog
11
26
 
12
27
  from square_authentication.configuration import (
13
- global_object_square_logger,
14
28
  config_str_secret_key_for_access_token,
15
- global_object_square_file_store_helper,
16
29
  global_object_square_database_helper,
30
+ global_object_square_file_store_helper,
31
+ global_object_square_logger,
32
+ MAIL_GUN_API_KEY,
33
+ NUMBER_OF_DIGITS_IN_EMAIL_VERIFICATION_CODE,
34
+ EXPIRY_TIME_FOR_EMAIL_VERIFICATION_CODE_IN_SECONDS,
17
35
  )
18
36
  from square_authentication.messages import messages
37
+ from square_authentication.pydantic_models.profile import (
38
+ ValidateEmailVerificationCodeV0,
39
+ )
19
40
  from square_authentication.utils.token import get_jwt_payload
20
41
 
21
42
  router = APIRouter(
@@ -164,3 +185,431 @@ async def update_profile_photo_v0(
164
185
  return JSONResponse(
165
186
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
166
187
  )
188
+
189
+
190
+ @router.patch("/update_profile_details/v0")
191
+ @global_object_square_logger.auto_logger()
192
+ async def update_profile_details_v0(
193
+ access_token: Annotated[str, Header()],
194
+ first_name: Optional[str] = None,
195
+ last_name: Optional[str] = None,
196
+ email: Optional[str] = None,
197
+ phone_number_country_code: Optional[str] = None,
198
+ phone_number: Optional[str] = None,
199
+ ):
200
+ try:
201
+ """
202
+ validation
203
+ """
204
+ # validate access token
205
+ try:
206
+ local_dict_access_token_payload = get_jwt_payload(
207
+ access_token, config_str_secret_key_for_access_token
208
+ )
209
+ except Exception as error:
210
+ output_content = get_api_output_in_standard_format(
211
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
212
+ )
213
+ raise HTTPException(
214
+ status_code=status.HTTP_400_BAD_REQUEST,
215
+ detail=output_content,
216
+ )
217
+ user_id = local_dict_access_token_payload["user_id"]
218
+
219
+ # validate email format
220
+ if email and "@" not in email:
221
+ output_content = get_api_output_in_standard_format(
222
+ message=messages["INVALID_EMAIL_FORMAT"]
223
+ )
224
+ raise HTTPException(
225
+ status_code=status.HTTP_400_BAD_REQUEST,
226
+ detail=output_content,
227
+ )
228
+
229
+ # validate phone number format
230
+ if phone_number and not phone_number.isdigit():
231
+ output_content = get_api_output_in_standard_format(
232
+ message=messages["INVALID_PHONE_NUMBER_FORMAT"]
233
+ )
234
+ raise HTTPException(
235
+ status_code=status.HTTP_400_BAD_REQUEST,
236
+ detail=output_content,
237
+ )
238
+ if (phone_number and not phone_number_country_code) or (
239
+ phone_number_country_code and not phone_number
240
+ ):
241
+ output_content = get_api_output_in_standard_format(
242
+ message=messages["GENERIC_MISSING_REQUIRED_FIELD"],
243
+ log="both phone number and country code must be provided together.",
244
+ )
245
+ raise HTTPException(
246
+ status_code=status.HTTP_400_BAD_REQUEST,
247
+ detail=output_content,
248
+ )
249
+
250
+ """
251
+ main process
252
+ """
253
+ profile_update_data = {}
254
+ if first_name is not None:
255
+ profile_update_data[UserProfile.user_profile_first_name.name] = first_name
256
+ if last_name is not None:
257
+ profile_update_data[UserProfile.user_profile_last_name.name] = last_name
258
+ if email is not None:
259
+ profile_update_data[UserProfile.user_profile_email.name] = email
260
+ if phone_number is not None and phone_number_country_code is not None:
261
+ profile_update_data[UserProfile.user_profile_phone_number.name] = (
262
+ phone_number
263
+ )
264
+ profile_update_data[
265
+ UserProfile.user_profile_phone_number_country_code.name
266
+ ] = phone_number_country_code
267
+
268
+ # updating user profile
269
+ profile_update_response = global_object_square_database_helper.edit_rows_v0(
270
+ data=profile_update_data,
271
+ filters=FiltersV0(
272
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
273
+ ),
274
+ database_name=global_string_database_name,
275
+ schema_name=global_string_schema_name,
276
+ table_name=UserProfile.__tablename__,
277
+ apply_filters=True,
278
+ )
279
+
280
+ """
281
+ return value
282
+ """
283
+ output_content = get_api_output_in_standard_format(
284
+ data=profile_update_response["data"],
285
+ message=messages["GENERIC_UPDATE_SUCCESSFUL"],
286
+ )
287
+ return JSONResponse(
288
+ status_code=status.HTTP_200_OK,
289
+ content=output_content,
290
+ )
291
+ except HTTPException as http_exception:
292
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
293
+ return JSONResponse(
294
+ status_code=http_exception.status_code, content=http_exception.detail
295
+ )
296
+ except Exception as e:
297
+ """
298
+ rollback logic
299
+ """
300
+ global_object_square_logger.logger.error(e, exc_info=True)
301
+ output_content = get_api_output_in_standard_format(
302
+ message=messages["GENERIC_500"],
303
+ log=str(e),
304
+ )
305
+ return JSONResponse(
306
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
307
+ )
308
+
309
+
310
+ @router.post("/send_verification_email/v0")
311
+ @global_object_square_logger.auto_logger()
312
+ async def send_verification_email_v0(
313
+ access_token: Annotated[str, Header()],
314
+ ):
315
+ try:
316
+ """
317
+ validation
318
+ """
319
+ # validate access token
320
+ try:
321
+ local_dict_access_token_payload = get_jwt_payload(
322
+ access_token, config_str_secret_key_for_access_token
323
+ )
324
+ except Exception as error:
325
+ output_content = get_api_output_in_standard_format(
326
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
327
+ )
328
+ raise HTTPException(
329
+ status_code=status.HTTP_400_BAD_REQUEST,
330
+ detail=output_content,
331
+ )
332
+ user_id = local_dict_access_token_payload["user_id"]
333
+
334
+ # validate if user has email in profile
335
+ user_profile_response = global_object_square_database_helper.get_rows_v0(
336
+ database_name=global_string_database_name,
337
+ schema_name=global_string_schema_name,
338
+ table_name=UserProfile.__tablename__,
339
+ filters=FiltersV0(
340
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
341
+ ),
342
+ apply_filters=True,
343
+ )
344
+ user_profile_data = user_profile_response["data"]["main"][0]
345
+ if not user_profile_data.get(UserProfile.user_profile_email.name):
346
+ output_content = get_api_output_in_standard_format(
347
+ message=messages["GENERIC_MISSING_REQUIRED_FIELD"],
348
+ log="email is required to send verification email.",
349
+ )
350
+ raise HTTPException(
351
+ status_code=status.HTTP_400_BAD_REQUEST,
352
+ detail=output_content,
353
+ )
354
+ # check if email is already verified
355
+ if user_profile_data.get(UserProfile.user_profile_email_verified.name):
356
+ output_content = get_api_output_in_standard_format(
357
+ message=messages["EMAIL_ALREADY_VERIFIED"]
358
+ )
359
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
360
+
361
+ """
362
+ main process
363
+ """
364
+ verification_code = random.randint(
365
+ 10 ** (NUMBER_OF_DIGITS_IN_EMAIL_VERIFICATION_CODE - 1),
366
+ 10**NUMBER_OF_DIGITS_IN_EMAIL_VERIFICATION_CODE - 1,
367
+ )
368
+ # hash the verification code
369
+ hashed_verification_code = bcrypt.hashpw(
370
+ str(verification_code).encode("utf-8"), bcrypt.gensalt()
371
+ ).decode("utf-8")
372
+ expires_at = datetime.now(timezone.utc) + timedelta(
373
+ seconds=EXPIRY_TIME_FOR_EMAIL_VERIFICATION_CODE_IN_SECONDS
374
+ )
375
+ # add verification code to UserVerification code table
376
+ global_object_square_database_helper.insert_rows_v0(
377
+ database_name=global_string_database_name,
378
+ schema_name=global_string_schema_name,
379
+ table_name=UserVerificationCode.__tablename__,
380
+ data=[
381
+ {
382
+ UserVerificationCode.user_id.name: user_id,
383
+ UserVerificationCode.user_verification_code_type.name: VerificationCodeTypeEnum.EMAIL_VERIFICATION.value,
384
+ UserVerificationCode.user_verification_code_hash.name: hashed_verification_code,
385
+ UserVerificationCode.user_verification_code_expires_at.name: expires_at.strftime(
386
+ "%Y-%m-%d %H:%M:%S.%f+00"
387
+ ),
388
+ }
389
+ ],
390
+ )
391
+ # send verification email
392
+ if (
393
+ user_profile_data[UserProfile.user_profile_first_name.name]
394
+ and user_profile_data[UserProfile.user_profile_last_name.name]
395
+ ):
396
+ user_to_name = f"{user_profile_data[UserProfile.user_profile_first_name.name]} {user_profile_data[UserProfile.user_profile_last_name.name]}"
397
+ elif user_profile_data[UserProfile.user_profile_first_name.name]:
398
+ user_to_name = user_profile_data[UserProfile.user_profile_first_name.name]
399
+ elif user_profile_data[UserProfile.user_profile_last_name.name]:
400
+ user_to_name = user_profile_data[UserProfile.user_profile_last_name.name]
401
+ else:
402
+ user_to_name = ""
403
+
404
+ mailgun_response = send_email_using_mailgun(
405
+ from_email="auth@thepmsquare.com",
406
+ from_name="square_authentication",
407
+ to_email=user_profile_data[UserProfile.user_profile_email.name],
408
+ to_name=user_to_name,
409
+ subject="Email Verification",
410
+ body=f"Your verification code is {verification_code}. It will expire in {EXPIRY_TIME_FOR_EMAIL_VERIFICATION_CODE_IN_SECONDS/60} minutes.",
411
+ api_key=MAIL_GUN_API_KEY,
412
+ domain_name="thepmsquare.com",
413
+ )
414
+ # add log for email sending
415
+ global_object_square_database_helper.insert_rows_v0(
416
+ database_name=global_string_database_name,
417
+ schema_name=email_schema_name,
418
+ table_name=EmailLog.__tablename__,
419
+ data=[
420
+ {
421
+ EmailLog.user_id.name: user_id,
422
+ EmailLog.recipient_email.name: user_profile_data[
423
+ UserProfile.user_profile_email.name
424
+ ],
425
+ EmailLog.email_type.name: EmailTypeEnum.VERIFY_EMAIL.value,
426
+ EmailLog.status.name: EmailStatusEnum.SENT.value,
427
+ EmailLog.third_party_message_id.name: mailgun_response.get("id"),
428
+ }
429
+ ],
430
+ )
431
+ """
432
+ return value
433
+ """
434
+ output_content = get_api_output_in_standard_format(
435
+ data={
436
+ "expires_at": expires_at.isoformat(),
437
+ },
438
+ message=messages["GENERIC_ACTION_SUCCESSFUL"],
439
+ )
440
+ return JSONResponse(
441
+ status_code=status.HTTP_200_OK,
442
+ content=output_content,
443
+ )
444
+ except HTTPException as http_exception:
445
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
446
+ return JSONResponse(
447
+ status_code=http_exception.status_code, content=http_exception.detail
448
+ )
449
+ except Exception as e:
450
+ """
451
+ rollback logic
452
+ """
453
+ global_object_square_logger.logger.error(e, exc_info=True)
454
+ output_content = get_api_output_in_standard_format(
455
+ message=messages["GENERIC_500"],
456
+ log=str(e),
457
+ )
458
+ return JSONResponse(
459
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
460
+ )
461
+
462
+
463
+ @router.post("/validate_email_verification_code/v0")
464
+ @global_object_square_logger.auto_logger()
465
+ async def validate_email_verification_code_v0(
466
+ access_token: Annotated[str, Header()],
467
+ body: ValidateEmailVerificationCodeV0,
468
+ ):
469
+ verification_code = body.verification_code
470
+ try:
471
+ """
472
+ validation
473
+ """
474
+ # validate access token
475
+ try:
476
+ local_dict_access_token_payload = get_jwt_payload(
477
+ access_token, config_str_secret_key_for_access_token
478
+ )
479
+ except Exception as error:
480
+ output_content = get_api_output_in_standard_format(
481
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
482
+ )
483
+ raise HTTPException(
484
+ status_code=status.HTTP_400_BAD_REQUEST,
485
+ detail=output_content,
486
+ )
487
+ user_id = local_dict_access_token_payload["user_id"]
488
+
489
+ # validate if user has email in profile
490
+ user_profile_response = global_object_square_database_helper.get_rows_v0(
491
+ database_name=global_string_database_name,
492
+ schema_name=global_string_schema_name,
493
+ table_name=UserProfile.__tablename__,
494
+ filters=FiltersV0(
495
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
496
+ ),
497
+ apply_filters=True,
498
+ )
499
+ user_profile_data = user_profile_response["data"]["main"][0]
500
+ if not user_profile_data.get(UserProfile.user_profile_email.name):
501
+ output_content = get_api_output_in_standard_format(
502
+ message=messages["GENERIC_MISSING_REQUIRED_FIELD"],
503
+ log="email is required to send verification email.",
504
+ )
505
+ raise HTTPException(
506
+ status_code=status.HTTP_400_BAD_REQUEST,
507
+ detail=output_content,
508
+ )
509
+ # check if email is already verified
510
+ if user_profile_data.get(UserProfile.user_profile_email_verified.name):
511
+ output_content = get_api_output_in_standard_format(
512
+ message=messages["EMAIL_ALREADY_VERIFIED"]
513
+ )
514
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
515
+ # check for verification code in UserVerificationCode table
516
+ verification_code_response = global_object_square_database_helper.get_rows_v0(
517
+ database_name=global_string_database_name,
518
+ schema_name=global_string_schema_name,
519
+ table_name=UserVerificationCode.__tablename__,
520
+ filters=FiltersV0(
521
+ root={
522
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
523
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
524
+ eq=VerificationCodeTypeEnum.EMAIL_VERIFICATION.value
525
+ ),
526
+ UserVerificationCode.user_verification_code_used_at.name: FilterConditionsV0(
527
+ is_null=True
528
+ ),
529
+ UserVerificationCode.user_verification_code_expires_at.name: FilterConditionsV0(
530
+ gte=datetime.now(timezone.utc).strftime(
531
+ "%Y-%m-%d %H:%M:%S.%f+00"
532
+ )
533
+ ),
534
+ }
535
+ ),
536
+ order_by=[
537
+ "-" + UserVerificationCode.user_verification_code_created_at.name
538
+ ],
539
+ limit=1,
540
+ apply_filters=True,
541
+ )
542
+ if len(verification_code_response["data"]["main"]) != 1:
543
+ output_content = get_api_output_in_standard_format(
544
+ message=messages["INCORRECT_VERIFICATION_CODE"]
545
+ )
546
+ raise HTTPException(
547
+ status_code=status.HTTP_400_BAD_REQUEST,
548
+ detail=output_content,
549
+ )
550
+ """
551
+ main process
552
+ """
553
+
554
+ # check if the latest verification code matches the provided code
555
+ latest_verification_code_data = verification_code_response["data"]["main"][0]
556
+ latest_verification_code_hash = latest_verification_code_data[
557
+ UserVerificationCode.user_verification_code_hash.name
558
+ ]
559
+ if not bcrypt.checkpw(
560
+ str(verification_code).encode("utf-8"),
561
+ latest_verification_code_hash.encode("utf-8"),
562
+ ):
563
+ output_content = get_api_output_in_standard_format(
564
+ message=messages["INCORRECT_VERIFICATION_CODE"]
565
+ )
566
+ raise HTTPException(
567
+ status_code=status.HTTP_400_BAD_REQUEST,
568
+ detail=output_content,
569
+ )
570
+ # update user profile to mark email as verified
571
+ email_verified_time = datetime.now(timezone.utc)
572
+ global_object_square_database_helper.edit_rows_v0(
573
+ database_name=global_string_database_name,
574
+ schema_name=global_string_schema_name,
575
+ table_name=UserProfile.__tablename__,
576
+ filters=FiltersV0(
577
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
578
+ ),
579
+ data={
580
+ UserProfile.user_profile_email_verified.name: email_verified_time.strftime(
581
+ "%Y-%m-%d %H:%M:%S.%f+00"
582
+ ),
583
+ },
584
+ apply_filters=True,
585
+ )
586
+ """
587
+ return value
588
+ """
589
+ output_content = get_api_output_in_standard_format(
590
+ data={
591
+ UserProfile.user_profile_email_verified.name: email_verified_time.isoformat(),
592
+ },
593
+ message=messages["GENERIC_ACTION_SUCCESSFUL"],
594
+ )
595
+ return JSONResponse(
596
+ status_code=status.HTTP_200_OK,
597
+ content=output_content,
598
+ )
599
+ except HTTPException as http_exception:
600
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
601
+ return JSONResponse(
602
+ status_code=http_exception.status_code, content=http_exception.detail
603
+ )
604
+ except Exception as e:
605
+ """
606
+ rollback logic
607
+ """
608
+ global_object_square_logger.logger.error(e, exc_info=True)
609
+ output_content = get_api_output_in_standard_format(
610
+ message=messages["GENERIC_500"],
611
+ log=str(e),
612
+ )
613
+ return JSONResponse(
614
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
615
+ )
@@ -0,0 +1,37 @@
1
+ import random
2
+ import string
3
+ from typing import Optional
4
+
5
+ from square_database_helper import FiltersV0
6
+ from square_database_helper.pydantic_models import FilterConditionsV0
7
+ from square_database_structure.square import global_string_database_name
8
+ from square_database_structure.square.authentication import global_string_schema_name
9
+ from square_database_structure.square.authentication.tables import User
10
+
11
+ from square_authentication.configuration import global_object_square_database_helper
12
+
13
+
14
+ def generate_default_username_for_google_users(given_name: Optional[str], family_name: Optional[str]) -> str:
15
+
16
+ given = given_name.lower() if given_name else "user"
17
+ family = family_name.lower() if family_name else "user"
18
+
19
+ # sanitize (keep a-z, 0-9, _, -)
20
+ allowed_chars = set(string.ascii_lowercase + string.digits + "_-")
21
+ given = "".join(c for c in given if c in allowed_chars)
22
+ family = "".join(c for c in family if c in allowed_chars)
23
+
24
+ while True:
25
+ random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
26
+ username_candidate = f"{given}_{family}_{random_suffix}"
27
+
28
+ # check uniqueness
29
+ existing = global_object_square_database_helper.get_rows_v0(
30
+ database_name=global_string_database_name,
31
+ schema_name=global_string_schema_name,
32
+ table_name=User.__tablename__,
33
+ filters=FiltersV0(root={User.user_username.name: FilterConditionsV0(eq=username_candidate)}),
34
+ )["data"]["main"]
35
+
36
+ if not existing:
37
+ return username_candidate
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: square_authentication
3
- Version: 6.2.2
3
+ Version: 8.0.0
4
4
  Summary: authentication layer for my personal server.
5
5
  Home-page: https://github.com/thepmsquare/square_authentication
6
6
  Author: thePmSquare
@@ -19,13 +19,14 @@ Requires-Dist: bcrypt>=4.1.2
19
19
  Requires-Dist: pyjwt>=2.8.0
20
20
  Requires-Dist: requests>=2.32.3
21
21
  Requires-Dist: cryptography>=42.0.7
22
- Requires-Dist: square_commons>=2.1.0
22
+ Requires-Dist: square_commons>=3.0.0
23
23
  Requires-Dist: square_logger>=2.0.0
24
24
  Requires-Dist: square_database_helper>=2.0.0
25
25
  Requires-Dist: square_database_structure>=2.3.1
26
26
  Requires-Dist: square_file_store_helper>=3.0.0
27
27
  Requires-Dist: pytest>=8.0.0
28
28
  Requires-Dist: httpx>=0.27.2
29
+ Requires-Dist: google-auth>=2.40.3
29
30
  Dynamic: author
30
31
  Dynamic: author-email
31
32
  Dynamic: classifier
@@ -53,6 +54,59 @@ pip install square_authentication
53
54
 
54
55
  ## changelog
55
56
 
57
+ ### v8.0.0
58
+
59
+ - env
60
+ - add GOOGLE section and GOOGLE_AUTH_PLATFORM_CLIENT_ID variable.
61
+ - add LOGIC section with NUMBER_OF_RECOVERY_CODES, EXPIRY_TIME_FOR_EMAIL_VERIFICATION_CODE_IN_SECONDS,
62
+ NUMBER_OF_DIGITS_IN_EMAIL_VERIFICATION_CODE, EXPIRY_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS,
63
+ NUMBER_OF_DIGITS_IN_EMAIL_PASSWORD_RESET_CODE variables.
64
+ - dependencies
65
+ - add google-auth>=2.40.3.
66
+ - update square_commons to >=3.0.0.
67
+ - core
68
+ - add reset_password_and_login_using_reset_email_code_v0.
69
+ - **breaking change**: remove app_id from send_reset_password_email_v0.
70
+ - implement deletion of existing backup codes before generating new ones (generate_account_backup_codes_v0).
71
+ - implement deletion of existing backup codes before removing recovery method (update_user_recovery_methods_v0).
72
+ - implement logout_other_sessions in update_password_v0, reset_password_and_login_using_backup_code_v0 and
73
+ reset_password_and_login_using_reset_email_code_v0
74
+ - in update_password_v0, it will log out all other sessions except the current one if valid (optional)
75
+ refresh_token is passed in.
76
+ - add register_login_google_v0, **finally**.
77
+ - add validation in update_password_v0, reset_password_and_login_using_backup_code_v0, send_reset_password_email_v0,
78
+ reset_password_and_login_using_reset_email_code_v0 to check if user has credentials and has self as auth provider.
79
+ - remove profile_photo from file_store when user is deleted in delete_user_v0.
80
+ - utils
81
+ - add new core file with generate_default_username_for_google_users function.
82
+ - tests
83
+ - add test_login_fail_v0.
84
+
85
+ ### v7.0.0
86
+
87
+ - internal support for UserAuthProvider.
88
+ - internal support for username shifted from UserProfile to User.
89
+ - internal support for phone number country code in UserProfile.
90
+ - core
91
+ - register_username_v0 fixed to account for changes mentioned above and creates empty profile.
92
+ - login_username_v0 fixed to account for changes mentioned above.
93
+ - update_username_v0 fixed to account for changes mentioned above.
94
+ - **breaking change**: delete_user_v0 is now a POST method instead of DELETE.
95
+ - add generate_account_backup_codes_v0.
96
+ - add reset_password_and_login_using_backup_code_v0.
97
+ - add validation for email verification when adding email as recovery method in update_user_recovery_methods_v0.
98
+ - add send_reset_password_email_v0.
99
+ - profile
100
+ - add update_profile_details_v0.
101
+ - add send_verification_email_v0.
102
+ - add validate_email_verification_code_v0.
103
+ - tests
104
+ - add test cases and fixtures for login_username_v0.
105
+ - add test cases and fixtures for delete_user_v0.
106
+ - add test cases and fixtures for update_profile_details_v0.
107
+ - env
108
+ - add EMAIL section and MAIL_GUN_API_KEY variable.
109
+
56
110
  ### v6.2.2
57
111
 
58
112
  - remove config.ini and config.testing.ini from version control.
@@ -0,0 +1,21 @@
1
+ square_authentication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ square_authentication/configuration.py,sha256=6M_5Ua7HQblPdBDeywSyelkvhkrsaTmbqWyMKpVV5HU,6045
3
+ square_authentication/main.py,sha256=nhkv8U4E9b7VIH7Aaj8iMWIwA4VIL-vzRXjZaYEFWPw,1755
4
+ square_authentication/messages.py,sha256=yDyr3SpBKg5KHb7dUDIUnpuz21EEAg3dnoaLnvX0IcU,2812
5
+ square_authentication/data/config.sample.ini,sha256=3vn0s5USfksmjVERgk2fIF97Tvzrsjm8xNAOqt5zZoE,1523
6
+ square_authentication/data/config.testing.sample.ini,sha256=uXUq3Isz2x5fpRcRoQqY-U2t7ZIGgo5c5zoWBgJ8scI,1570
7
+ square_authentication/pydantic_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ square_authentication/pydantic_models/core.py,sha256=7H1N1y8VpPA_-NrudX8j0YG_YzpHnPXZuwtGaKRPSy0,1268
9
+ square_authentication/pydantic_models/profile.py,sha256=tq7RJMfbMBMi7FaRQksWJR3Iucr2l8P2ziKgQvV4owg,110
10
+ square_authentication/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ square_authentication/routes/core.py,sha256=zGvG8cqwkZVUyxqPC7aT8g3IdIH7BKMnVqPEjwTY5ZU,131694
12
+ square_authentication/routes/profile.py,sha256=4J-_ulVW0bDkoDO1utdzv_2_OraGbtVnQmcpws3Ecu4,24466
13
+ square_authentication/routes/utility.py,sha256=KDr8KdkT0jAGPjfP-b5XXYG7p49WU7J1FiK6oSIckQI,1779
14
+ square_authentication/utils/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
15
+ square_authentication/utils/core.py,sha256=2jMPnbbURNIMiajYfWIlGNblI7DocWNPZcU_psAq4SE,1567
16
+ square_authentication/utils/encryption.py,sha256=WakaiEAgWpTJltxBzqOtv81_DCDKfzJqt60fWSPoNvo,2027
17
+ square_authentication/utils/token.py,sha256=t-RPBY4cYyT1ro3lkLBTOy2BeRGBfluBVBivL5DLmDg,680
18
+ square_authentication-8.0.0.dist-info/METADATA,sha256=WtZMjlnbh0o3HmTabQoQrFh_dC--5lfLmrjXzQbVYOo,8407
19
+ square_authentication-8.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ square_authentication-8.0.0.dist-info/top_level.txt,sha256=wDssVJIl9KIEJPj5rR3rv4uRI7yCndMBrvHd_6BGXQA,22
21
+ square_authentication-8.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,19 +0,0 @@
1
- square_authentication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- square_authentication/configuration.py,sha256=Gl7LqVZ7XZEBOsLQrhETdKjn-bgDGliXoldZGnPJaBM,4748
3
- square_authentication/main.py,sha256=nhkv8U4E9b7VIH7Aaj8iMWIwA4VIL-vzRXjZaYEFWPw,1755
4
- square_authentication/messages.py,sha256=WVZtWBctx-YK1xGo97DFAMkSgCZpDEd9gABBSqlvd58,1575
5
- square_authentication/data/config.sample.ini,sha256=Mayh9AhTBZd8i08Y--ClZuDEjJjfvnfKQmtqablzXOA,1154
6
- square_authentication/data/config.testing.sample.ini,sha256=KB4PMPZ6a9yJGPXYJWwKlYcNET1Au3TQdJHQyngbZUA,1201
7
- square_authentication/pydantic_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- square_authentication/pydantic_models/core.py,sha256=qeNETcJv7mnRKGhATOW2bg0NlHuyzvot1dZ1b1qqhwU,610
9
- square_authentication/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- square_authentication/routes/core.py,sha256=7KqTA3y6CedH7XelV2HBCkne4Bql9p31OzSeioguhE0,63838
11
- square_authentication/routes/profile.py,sha256=3b-PtMaD9cxvf112MOn9rPu5F2KG4sRxAbuPvll8dUU,6216
12
- square_authentication/routes/utility.py,sha256=KDr8KdkT0jAGPjfP-b5XXYG7p49WU7J1FiK6oSIckQI,1779
13
- square_authentication/utils/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
14
- square_authentication/utils/encryption.py,sha256=WakaiEAgWpTJltxBzqOtv81_DCDKfzJqt60fWSPoNvo,2027
15
- square_authentication/utils/token.py,sha256=t-RPBY4cYyT1ro3lkLBTOy2BeRGBfluBVBivL5DLmDg,680
16
- square_authentication-6.2.2.dist-info/METADATA,sha256=uLZjXAFi-JLoYorU8XNSbM7m4p0-V07oW4w86ENOKhs,5579
17
- square_authentication-6.2.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
18
- square_authentication-6.2.2.dist-info/top_level.txt,sha256=wDssVJIl9KIEJPj5rR3rv4uRI7yCndMBrvHd_6BGXQA,22
19
- square_authentication-6.2.2.dist-info/RECORD,,