square-authentication 7.0.0__py3-none-any.whl → 8.0.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.
@@ -1,4 +1,6 @@
1
1
  import copy
2
+ import io
3
+ import mimetypes
2
4
  import random
3
5
  import re
4
6
  import uuid
@@ -10,8 +12,11 @@ import jwt
10
12
  from fastapi import APIRouter, Header, HTTPException, status
11
13
  from fastapi.params import Query
12
14
  from fastapi.responses import JSONResponse
15
+ from google.auth.transport import requests as google_requests
16
+ from google.oauth2 import id_token
13
17
  from requests import HTTPError
14
18
  from square_commons import get_api_output_in_standard_format, send_email_using_mailgun
19
+ from square_commons.api_utils import make_request
15
20
  from square_database_helper.pydantic_models import FilterConditionsV0, FiltersV0
16
21
  from square_database_structure.square import global_string_database_name
17
22
  from square_database_structure.square.authentication import global_string_schema_name
@@ -48,6 +53,11 @@ from square_authentication.configuration import (
48
53
  global_object_square_logger,
49
54
  global_object_square_database_helper,
50
55
  MAIL_GUN_API_KEY,
56
+ GOOGLE_AUTH_PLATFORM_CLIENT_ID,
57
+ NUMBER_OF_RECOVERY_CODES,
58
+ NUMBER_OF_DIGITS_IN_EMAIL_PASSWORD_RESET_CODE,
59
+ EXPIRY_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS,
60
+ global_object_square_file_store_helper,
51
61
  )
52
62
  from square_authentication.messages import messages
53
63
  from square_authentication.pydantic_models.core import (
@@ -59,7 +69,10 @@ from square_authentication.pydantic_models.core import (
59
69
  UpdatePasswordV0,
60
70
  ResetPasswordAndLoginUsingBackupCodeV0,
61
71
  SendResetPasswordEmailV0,
72
+ ResetPasswordAndLoginUsingResetEmailCodeV0,
73
+ RegisterLoginGoogleV0,
62
74
  )
75
+ from square_authentication.utils.core import generate_default_username_for_google_users
63
76
  from square_authentication.utils.token import get_jwt_payload
64
77
 
65
78
  router = APIRouter(
@@ -135,7 +148,7 @@ async def register_username_v0(
135
148
  local_str_user_id = local_list_response_user[0][User.user_id.name]
136
149
 
137
150
  # entry in user auth provider table
138
- local_list_response_user_auth_provider = global_object_square_database_helper.insert_rows_v0(
151
+ global_object_square_database_helper.insert_rows_v0(
139
152
  data=[
140
153
  {
141
154
  UserAuthProvider.user_id.name: local_str_user_id,
@@ -145,12 +158,7 @@ async def register_username_v0(
145
158
  database_name=global_string_database_name,
146
159
  schema_name=global_string_schema_name,
147
160
  table_name=UserAuthProvider.__tablename__,
148
- )[
149
- "data"
150
- ][
151
- "main"
152
- ]
153
- local_str_user_id = local_list_response_user[0][User.user_id.name]
161
+ )
154
162
 
155
163
  # entry in user profile table
156
164
  global_object_square_database_helper.insert_rows_v0(
@@ -287,6 +295,355 @@ async def register_username_v0(
287
295
  )
288
296
 
289
297
 
298
+ @router.post("/register_login_google/v0")
299
+ async def register_login_google_v0(body: RegisterLoginGoogleV0):
300
+ app_id = body.app_id
301
+ google_id = body.google_id
302
+ assign_app_id_if_missing = body.assign_app_id_if_missing
303
+ was_new_user = False
304
+ try:
305
+ """
306
+ validation
307
+ """
308
+ # verify id token
309
+ id_info = id_token.verify_oauth2_token(
310
+ google_id,
311
+ google_requests.Request(),
312
+ GOOGLE_AUTH_PLATFORM_CLIENT_ID,
313
+ )
314
+ # validate if email is verified
315
+ if id_info.get("email_verified") is not True:
316
+ output_content = get_api_output_in_standard_format(
317
+ message=messages["EMAIL_NOT_VERIFIED"],
318
+ log="Google account email is not verified.",
319
+ )
320
+ raise HTTPException(
321
+ status_code=status.HTTP_400_BAD_REQUEST,
322
+ detail=output_content,
323
+ )
324
+ """
325
+ processing
326
+ """
327
+ google_sub = id_info["sub"]
328
+ email = id_info.get("email")
329
+ given_name = id_info.get("given_name")
330
+ family_name = id_info.get("family_name")
331
+
332
+ profile_picture = id_info.get("picture")
333
+
334
+ # check if user exists
335
+ user_rows = 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=UserAuthProvider.__tablename__,
339
+ filters=FiltersV0(
340
+ root={
341
+ UserAuthProvider.auth_provider_user_id.name: FilterConditionsV0(
342
+ eq=google_sub
343
+ ),
344
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
345
+ eq=AuthProviderEnum.GOOGLE.value
346
+ ),
347
+ }
348
+ ),
349
+ )["data"]["main"]
350
+
351
+ if user_rows:
352
+ # login
353
+
354
+ # validate if app_id is assigned to user
355
+ # this will also validate if app_id is valid
356
+ local_str_user_id = user_rows[0][User.user_id.name]
357
+ user_record = global_object_square_database_helper.get_rows_v0(
358
+ database_name=global_string_database_name,
359
+ schema_name=global_string_schema_name,
360
+ table_name=User.__tablename__,
361
+ filters=FiltersV0(
362
+ root={User.user_id.name: FilterConditionsV0(eq=local_str_user_id)}
363
+ ),
364
+ )["data"]["main"][0]
365
+ username = user_record[User.user_username.name]
366
+ local_list_user_app_response = (
367
+ global_object_square_database_helper.get_rows_v0(
368
+ database_name=global_string_database_name,
369
+ schema_name=global_string_schema_name,
370
+ table_name=UserApp.__tablename__,
371
+ filters=FiltersV0(
372
+ root={
373
+ UserApp.user_id.name: FilterConditionsV0(
374
+ eq=local_str_user_id
375
+ ),
376
+ UserApp.app_id.name: FilterConditionsV0(eq=app_id),
377
+ }
378
+ ),
379
+ )["data"]["main"]
380
+ )
381
+ if len(local_list_user_app_response) == 0:
382
+ if assign_app_id_if_missing:
383
+ global_object_square_database_helper.insert_rows_v0(
384
+ database_name=global_string_database_name,
385
+ schema_name=global_string_schema_name,
386
+ table_name=UserApp.__tablename__,
387
+ data=[
388
+ {
389
+ UserApp.user_id.name: local_str_user_id,
390
+ UserApp.app_id.name: app_id,
391
+ }
392
+ ],
393
+ )
394
+ else:
395
+ output_content = get_api_output_in_standard_format(
396
+ message=messages["GENERIC_400"],
397
+ log=f"user_id {local_str_user_id}({username}) not assigned to app {app_id}.",
398
+ )
399
+ raise HTTPException(
400
+ status_code=status.HTTP_400_BAD_REQUEST,
401
+ detail=output_content,
402
+ )
403
+ else:
404
+ # register
405
+
406
+ was_new_user = True
407
+ # check if account with same email address exists
408
+ profile_rows = global_object_square_database_helper.get_rows_v0(
409
+ database_name=global_string_database_name,
410
+ schema_name=global_string_schema_name,
411
+ table_name=UserProfile.__tablename__,
412
+ filters=FiltersV0(
413
+ root={
414
+ UserProfile.user_profile_email.name: FilterConditionsV0(
415
+ eq=email
416
+ )
417
+ }
418
+ ),
419
+ )["data"]["main"]
420
+ if len(profile_rows) > 0:
421
+ output_content = get_api_output_in_standard_format(
422
+ message=messages["ACCOUNT_WITH_EMAIL_ALREADY_EXISTS"],
423
+ log=f"An account with the email {email} already exists.",
424
+ )
425
+ raise HTTPException(
426
+ status_code=status.HTTP_409_CONFLICT,
427
+ detail=output_content,
428
+ )
429
+ # generate a default username
430
+ username = generate_default_username_for_google_users(
431
+ family_name=family_name, given_name=given_name
432
+ )
433
+ # create user
434
+ user_rows = global_object_square_database_helper.insert_rows_v0(
435
+ database_name=global_string_database_name,
436
+ schema_name=global_string_schema_name,
437
+ table_name=User.__tablename__,
438
+ data=[
439
+ {
440
+ User.user_username.name: username,
441
+ }
442
+ ],
443
+ )["data"]["main"]
444
+ local_str_user_id = user_rows[0][User.user_id.name]
445
+
446
+ # link to user_auth_provider
447
+ global_object_square_database_helper.insert_rows_v0(
448
+ database_name=global_string_database_name,
449
+ schema_name=global_string_schema_name,
450
+ table_name=UserAuthProvider.__tablename__,
451
+ data=[
452
+ {
453
+ UserAuthProvider.user_id.name: local_str_user_id,
454
+ UserAuthProvider.auth_provider.name: AuthProviderEnum.GOOGLE.value,
455
+ UserAuthProvider.auth_provider_user_id.name: google_sub,
456
+ }
457
+ ],
458
+ )
459
+ # getting profile picture
460
+ if profile_picture:
461
+ try:
462
+ profile_picture_response = make_request(
463
+ "GET", profile_picture, return_type="response"
464
+ )
465
+
466
+ # finding content type and filename
467
+ headers = profile_picture_response.headers
468
+ content_type = headers.get(
469
+ "Content-Type", "application/octet-stream"
470
+ )
471
+ content_disposition = headers.get("Content-Disposition", "")
472
+
473
+ if content_disposition:
474
+ match = re.search(r'filename="([^"]+)"', content_disposition)
475
+ if match:
476
+ filename = match.group(1)
477
+ else:
478
+ filename = None
479
+ else:
480
+ filename = None
481
+ if filename is None:
482
+ global_object_square_logger.logger.warning(
483
+ f"user_id {local_str_user_id}'s profile picture from Google missing filename; guessing extension from Content-Type: {content_type}."
484
+ )
485
+ ext = (
486
+ mimetypes.guess_extension(
487
+ content_type.split(";")[0].strip()
488
+ )
489
+ or ""
490
+ )
491
+ filename = f"profile_photo{ext}"
492
+ if not ext:
493
+ filename += ".bin"
494
+
495
+ # upload bytes to square_file_storage
496
+ file_upload_response = global_object_square_file_store_helper.upload_file_using_tuple_v0(
497
+ file=(
498
+ filename,
499
+ io.BytesIO(profile_picture_response.content),
500
+ content_type,
501
+ ),
502
+ system_relative_path="global/users/profile_photos",
503
+ )
504
+ user_profile_photo_storage_token = file_upload_response["data"][
505
+ "main"
506
+ ]
507
+ except HTTPError:
508
+ global_object_square_logger.logger.error(
509
+ f"Failed to fetch profile picture for user_id {local_str_user_id} from google account.",
510
+ exc_info=True,
511
+ )
512
+ user_profile_photo_storage_token = None
513
+ except Exception as e:
514
+ global_object_square_logger.logger.error(
515
+ f"Error while fetching profile picture for user_id {local_str_user_id} from google account: {str(e)}",
516
+ exc_info=True,
517
+ )
518
+ user_profile_photo_storage_token = None
519
+ else:
520
+ global_object_square_logger.logger.warning(
521
+ f"user_id {local_str_user_id} has no profile picture in google account."
522
+ )
523
+ user_profile_photo_storage_token = None
524
+ # create user profile
525
+ global_object_square_database_helper.insert_rows_v0(
526
+ database_name=global_string_database_name,
527
+ schema_name=global_string_schema_name,
528
+ table_name=UserProfile.__tablename__,
529
+ data=[
530
+ {
531
+ UserProfile.user_id.name: local_str_user_id,
532
+ UserProfile.user_profile_email.name: email,
533
+ UserProfile.user_profile_email_verified.name: datetime.now(
534
+ timezone.utc
535
+ ).strftime("%Y-%m-%d %H:%M:%S.%f+00"),
536
+ UserProfile.user_profile_first_name.name: given_name,
537
+ UserProfile.user_profile_last_name.name: family_name,
538
+ UserProfile.user_profile_photo_storage_token.name: user_profile_photo_storage_token,
539
+ }
540
+ ],
541
+ )
542
+
543
+ # assign app if provided
544
+ if app_id is not None:
545
+ global_object_square_database_helper.insert_rows_v0(
546
+ database_name=global_string_database_name,
547
+ schema_name=global_string_schema_name,
548
+ table_name=UserApp.__tablename__,
549
+ data=[
550
+ {
551
+ UserApp.user_id.name: local_str_user_id,
552
+ UserApp.app_id.name: app_id,
553
+ }
554
+ ],
555
+ )
556
+
557
+ # generate tokens
558
+ now = datetime.now(timezone.utc)
559
+ access_token_payload = {
560
+ "app_id": app_id,
561
+ "user_id": local_str_user_id,
562
+ "exp": now + timedelta(minutes=config_int_access_token_valid_minutes),
563
+ }
564
+ access_token_str = jwt.encode(
565
+ access_token_payload,
566
+ config_str_secret_key_for_access_token,
567
+ )
568
+
569
+ refresh_token_expiry = now + timedelta(
570
+ minutes=config_int_refresh_token_valid_minutes
571
+ )
572
+ refresh_token_payload = {
573
+ "app_id": app_id,
574
+ "user_id": local_str_user_id,
575
+ "exp": refresh_token_expiry,
576
+ }
577
+ refresh_token_str = jwt.encode(
578
+ refresh_token_payload,
579
+ config_str_secret_key_for_refresh_token,
580
+ )
581
+
582
+ # store refresh token
583
+ global_object_square_database_helper.insert_rows_v0(
584
+ database_name=global_string_database_name,
585
+ schema_name=global_string_schema_name,
586
+ table_name=UserSession.__tablename__,
587
+ data=[
588
+ {
589
+ UserSession.user_id.name: local_str_user_id,
590
+ UserSession.app_id.name: app_id,
591
+ UserSession.user_session_refresh_token.name: refresh_token_str,
592
+ UserSession.user_session_expiry_time.name: refresh_token_expiry.strftime(
593
+ "%Y-%m-%d %H:%M:%S.%f+00"
594
+ ),
595
+ }
596
+ ],
597
+ )
598
+ """
599
+ return value
600
+ """
601
+ if was_new_user:
602
+ message = messages["REGISTRATION_SUCCESSFUL"]
603
+ else:
604
+ message = messages["LOGIN_SUCCESSFUL"]
605
+ output_content = get_api_output_in_standard_format(
606
+ message=message,
607
+ data={
608
+ "main": {
609
+ "user_id": local_str_user_id,
610
+ "username": username,
611
+ "app_id": app_id,
612
+ "access_token": access_token_str,
613
+ "refresh_token": refresh_token_str,
614
+ "refresh_token_expiry_time": refresh_token_expiry.isoformat(),
615
+ "was_new_user": was_new_user,
616
+ },
617
+ },
618
+ )
619
+
620
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
621
+ except HTTPError as http_error:
622
+ global_object_square_logger.logger.error(http_error, exc_info=True)
623
+ return JSONResponse(
624
+ status_code=http_error.response.status_code,
625
+ content=http_error.response.text,
626
+ )
627
+ except HTTPException as http_exception:
628
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
629
+ return JSONResponse(
630
+ status_code=http_exception.status_code, content=http_exception.detail
631
+ )
632
+ except Exception as e:
633
+ """
634
+ rollback logic
635
+ """
636
+ global_object_square_logger.logger.error(e, exc_info=True)
637
+ output_content = get_api_output_in_standard_format(
638
+ message=messages["GENERIC_500"],
639
+ log=str(e),
640
+ )
641
+ return JSONResponse(
642
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
643
+ content=output_content,
644
+ )
645
+
646
+
290
647
  @router.get("/get_user_details/v0")
291
648
  @global_object_square_logger.auto_logger()
292
649
  async def get_user_details_v0(
@@ -649,8 +1006,8 @@ async def login_username_v0(body: LoginUsernameV0):
649
1006
  )
650
1007
  if len(local_list_authentication_user_response) != 1:
651
1008
  output_content = get_api_output_in_standard_format(
652
- message=messages["INCORRECT_USERNAME"],
653
- log=f"incorrect username {username}",
1009
+ message=messages["MALFORMED_USER"],
1010
+ log=f"username: {username} does not have credentials set.",
654
1011
  )
655
1012
  raise HTTPException(
656
1013
  status_code=status.HTTP_400_BAD_REQUEST,
@@ -1340,6 +1697,17 @@ async def delete_user_v0(
1340
1697
  """
1341
1698
  main process
1342
1699
  """
1700
+ # fetch user profile photo storage token
1701
+ user_profile_storage_token = global_object_square_database_helper.get_rows_v0(
1702
+ database_name=global_string_database_name,
1703
+ schema_name=global_string_schema_name,
1704
+ table_name=UserProfile.__tablename__,
1705
+ filters=FiltersV0(
1706
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
1707
+ ),
1708
+ columns=[UserProfile.user_profile_photo_storage_token.name],
1709
+ )["data"]["main"][0][UserProfile.user_profile_photo_storage_token.name]
1710
+
1343
1711
  # delete the user.
1344
1712
  global_object_square_database_helper.delete_rows_v0(
1345
1713
  database_name=global_string_database_name,
@@ -1351,6 +1719,18 @@ async def delete_user_v0(
1351
1719
  }
1352
1720
  ),
1353
1721
  )
1722
+ # delete profile photo if exists
1723
+ if user_profile_storage_token:
1724
+ try:
1725
+ global_object_square_file_store_helper.delete_file_v0(
1726
+ list_file_storage_token=[user_profile_storage_token]
1727
+ )
1728
+ except HTTPError as he:
1729
+ global_object_square_logger.warning(
1730
+ f"Failed to delete user profile photo with storage token {user_profile_storage_token}. "
1731
+ f"Error: {he.response.text}",
1732
+ exc_info=True,
1733
+ )
1354
1734
  """
1355
1735
  return value
1356
1736
  """
@@ -1386,6 +1766,8 @@ async def update_password_v0(
1386
1766
  ):
1387
1767
  old_password = body.old_password
1388
1768
  new_password = body.new_password
1769
+ logout_other_sessions = body.logout_other_sessions
1770
+ preserve_session_refresh_token = body.preserve_session_refresh_token
1389
1771
  try:
1390
1772
  """
1391
1773
  validation
@@ -1425,7 +1807,47 @@ async def update_password_v0(
1425
1807
  status_code=status.HTTP_400_BAD_REQUEST,
1426
1808
  detail=output_content,
1427
1809
  )
1428
-
1810
+ # check if user has SELF auth provider
1811
+ local_list_response_user_auth_provider = (
1812
+ global_object_square_database_helper.get_rows_v0(
1813
+ database_name=global_string_database_name,
1814
+ schema_name=global_string_schema_name,
1815
+ table_name=UserAuthProvider.__tablename__,
1816
+ filters=FiltersV0(
1817
+ root={
1818
+ UserAuthProvider.user_id.name: FilterConditionsV0(eq=user_id),
1819
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
1820
+ eq=AuthProviderEnum.SELF.value
1821
+ ),
1822
+ }
1823
+ ),
1824
+ )["data"]["main"]
1825
+ )
1826
+ if len(local_list_response_user_auth_provider) != 1:
1827
+ output_content = get_api_output_in_standard_format(
1828
+ message=messages["INCORRECT_AUTH_PROVIDER"],
1829
+ log=f"user_id: {user_id} does not have {AuthProviderEnum.SELF.value} auth provider.",
1830
+ )
1831
+ raise HTTPException(
1832
+ status_code=status.HTTP_400_BAD_REQUEST,
1833
+ detail=output_content,
1834
+ )
1835
+ # check if user has credentials (might not be set in case of errors in registration.)
1836
+ local_list_response_user = global_object_square_database_helper.get_rows_v0(
1837
+ database_name=global_string_database_name,
1838
+ schema_name=global_string_schema_name,
1839
+ table_name=User.__tablename__,
1840
+ filters=FiltersV0(root={User.user_id.name: FilterConditionsV0(eq=user_id)}),
1841
+ )["data"]["main"]
1842
+ if len(local_list_response_user) != 1:
1843
+ output_content = get_api_output_in_standard_format(
1844
+ message=messages["MALFORMED_USER"],
1845
+ log=f"user_id: {user_id} does not have credentials.",
1846
+ )
1847
+ raise HTTPException(
1848
+ status_code=status.HTTP_400_BAD_REQUEST,
1849
+ detail=output_content,
1850
+ )
1429
1851
  # validate password
1430
1852
  local_dict_user = local_list_authentication_user_response[0]
1431
1853
  if not (
@@ -1444,18 +1866,49 @@ async def update_password_v0(
1444
1866
  status_code=status.HTTP_400_BAD_REQUEST,
1445
1867
  detail=output_content,
1446
1868
  )
1447
- """
1448
- main process
1449
- """
1450
- # update the password
1451
- local_str_hashed_password = bcrypt.hashpw(
1452
- new_password.encode("utf-8"), bcrypt.gensalt()
1453
- ).decode("utf-8")
1454
- global_object_square_database_helper.edit_rows_v0(
1455
- database_name=global_string_database_name,
1456
- schema_name=global_string_schema_name,
1457
- table_name=UserCredential.__tablename__,
1458
- filters=FiltersV0(
1869
+ # check if provided refresh token is valid
1870
+ if preserve_session_refresh_token:
1871
+ local_dict_token_payload = get_jwt_payload(
1872
+ preserve_session_refresh_token, config_str_secret_key_for_refresh_token
1873
+ )
1874
+ local_list_response_user_session = global_object_square_database_helper.get_rows_v0(
1875
+ database_name=global_string_database_name,
1876
+ schema_name=global_string_schema_name,
1877
+ table_name=UserSession.__tablename__,
1878
+ filters=FiltersV0(
1879
+ root={
1880
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
1881
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
1882
+ eq=preserve_session_refresh_token
1883
+ ),
1884
+ }
1885
+ ),
1886
+ )[
1887
+ "data"
1888
+ ][
1889
+ "main"
1890
+ ]
1891
+ if len(local_list_response_user_session) != 1:
1892
+ output_content = get_api_output_in_standard_format(
1893
+ message=messages["INCORRECT_REFRESH_TOKEN"],
1894
+ log=f"incorrect refresh token: {preserve_session_refresh_token}.",
1895
+ )
1896
+ raise HTTPException(
1897
+ status_code=status.HTTP_400_BAD_REQUEST,
1898
+ detail=output_content,
1899
+ )
1900
+ """
1901
+ main process
1902
+ """
1903
+ # update the password
1904
+ local_str_hashed_password = bcrypt.hashpw(
1905
+ new_password.encode("utf-8"), bcrypt.gensalt()
1906
+ ).decode("utf-8")
1907
+ global_object_square_database_helper.edit_rows_v0(
1908
+ database_name=global_string_database_name,
1909
+ schema_name=global_string_schema_name,
1910
+ table_name=UserCredential.__tablename__,
1911
+ filters=FiltersV0(
1459
1912
  root={
1460
1913
  UserCredential.user_id.name: FilterConditionsV0(eq=user_id),
1461
1914
  }
@@ -1464,6 +1917,34 @@ async def update_password_v0(
1464
1917
  UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
1465
1918
  },
1466
1919
  )
1920
+ if logout_other_sessions:
1921
+ if preserve_session_refresh_token:
1922
+ # delete all sessions for user except the one with the given refresh token
1923
+ global_object_square_database_helper.delete_rows_v0(
1924
+ database_name=global_string_database_name,
1925
+ schema_name=global_string_schema_name,
1926
+ table_name=UserSession.__tablename__,
1927
+ filters=FiltersV0(
1928
+ root={
1929
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
1930
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
1931
+ ne=preserve_session_refresh_token
1932
+ ),
1933
+ }
1934
+ ),
1935
+ )
1936
+ else:
1937
+ # delete all sessions for user
1938
+ global_object_square_database_helper.delete_rows_v0(
1939
+ database_name=global_string_database_name,
1940
+ schema_name=global_string_schema_name,
1941
+ table_name=UserSession.__tablename__,
1942
+ filters=FiltersV0(
1943
+ root={
1944
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
1945
+ }
1946
+ ),
1947
+ )
1467
1948
  """
1468
1949
  return value
1469
1950
  """
@@ -1594,9 +2075,13 @@ async def validate_and_get_payload_from_token_v0(
1594
2075
  @global_object_square_logger.auto_logger()
1595
2076
  async def update_user_recovery_methods_v0(
1596
2077
  access_token: Annotated[str, Header()],
1597
- recovery_methods_to_add: List[RecoveryMethodEnum],
1598
- recovery_methods_to_remove: List[RecoveryMethodEnum],
2078
+ recovery_methods_to_add: List[RecoveryMethodEnum] = None,
2079
+ recovery_methods_to_remove: List[RecoveryMethodEnum] = None,
1599
2080
  ):
2081
+ if recovery_methods_to_add is None:
2082
+ recovery_methods_to_add = []
2083
+ if recovery_methods_to_remove is None:
2084
+ recovery_methods_to_remove = []
1600
2085
  try:
1601
2086
 
1602
2087
  """
@@ -1710,6 +2195,28 @@ async def update_user_recovery_methods_v0(
1710
2195
  )
1711
2196
 
1712
2197
  # logic for removing recovery_methods
2198
+ remove_old_backup_codes = (
2199
+ RecoveryMethodEnum.BACKUP_CODE.value in recovery_methods_to_remove
2200
+ )
2201
+ old_backup_code_hashes = None
2202
+ if remove_old_backup_codes:
2203
+ # delete existing backup codes if any
2204
+ old_backup_code_hashes = global_object_square_database_helper.get_rows_v0(
2205
+ database_name=global_string_database_name,
2206
+ schema_name=global_string_schema_name,
2207
+ table_name=UserVerificationCode.__tablename__,
2208
+ filters=FiltersV0(
2209
+ root={
2210
+ UserVerificationCode.user_id.name: FilterConditionsV0(
2211
+ eq=user_id
2212
+ ),
2213
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
2214
+ eq=VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value
2215
+ ),
2216
+ }
2217
+ ),
2218
+ columns=[UserVerificationCode.user_verification_code_hash.name],
2219
+ )["data"]["main"]
1713
2220
  global_object_square_database_helper.delete_rows_v0(
1714
2221
  database_name=global_string_database_name,
1715
2222
  schema_name=global_string_schema_name,
@@ -1723,6 +2230,22 @@ async def update_user_recovery_methods_v0(
1723
2230
  }
1724
2231
  ),
1725
2232
  )
2233
+ if remove_old_backup_codes and old_backup_code_hashes:
2234
+ global_object_square_database_helper.delete_rows_v0(
2235
+ database_name=global_string_database_name,
2236
+ schema_name=global_string_schema_name,
2237
+ table_name=UserVerificationCode.__tablename__,
2238
+ filters=FiltersV0(
2239
+ root={
2240
+ UserVerificationCode.user_verification_code_hash.name: FilterConditionsV0(
2241
+ in_=[
2242
+ x[UserVerificationCode.user_verification_code_hash.name]
2243
+ for x in old_backup_code_hashes
2244
+ ]
2245
+ ),
2246
+ }
2247
+ ),
2248
+ )
1726
2249
 
1727
2250
  """
1728
2251
  return value
@@ -1826,11 +2349,27 @@ async def generate_account_backup_codes_v0(
1826
2349
  """
1827
2350
  main process
1828
2351
  """
2352
+ # delete existing backup codes if any
2353
+ old_backup_code_hashes = global_object_square_database_helper.get_rows_v0(
2354
+ database_name=global_string_database_name,
2355
+ schema_name=global_string_schema_name,
2356
+ table_name=UserVerificationCode.__tablename__,
2357
+ filters=FiltersV0(
2358
+ root={
2359
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
2360
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
2361
+ eq=VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value
2362
+ ),
2363
+ }
2364
+ ),
2365
+ columns=[UserVerificationCode.user_verification_code_hash.name],
2366
+ )["data"]["main"]
2367
+
1829
2368
  # generate backup codes
1830
2369
  backup_codes = []
1831
2370
  db_data = []
1832
2371
 
1833
- for i in range(10):
2372
+ for i in range(NUMBER_OF_RECOVERY_CODES):
1834
2373
  backup_code = str(uuid.uuid4())
1835
2374
  backup_codes.append(backup_code)
1836
2375
  # hash the backup code
@@ -1851,7 +2390,21 @@ async def generate_account_backup_codes_v0(
1851
2390
  table_name=UserVerificationCode.__tablename__,
1852
2391
  data=db_data,
1853
2392
  )
1854
-
2393
+ global_object_square_database_helper.delete_rows_v0(
2394
+ database_name=global_string_database_name,
2395
+ schema_name=global_string_schema_name,
2396
+ table_name=UserVerificationCode.__tablename__,
2397
+ filters=FiltersV0(
2398
+ root={
2399
+ UserVerificationCode.user_verification_code_hash.name: FilterConditionsV0(
2400
+ in_=[
2401
+ x[UserVerificationCode.user_verification_code_hash.name]
2402
+ for x in old_backup_code_hashes
2403
+ ]
2404
+ ),
2405
+ }
2406
+ ),
2407
+ )
1855
2408
  """
1856
2409
  return value
1857
2410
  """
@@ -1893,6 +2446,7 @@ async def reset_password_and_login_using_backup_code_v0(
1893
2446
  username = body.username
1894
2447
  new_password = body.new_password
1895
2448
  app_id = body.app_id
2449
+ logout_other_sessions = body.logout_other_sessions
1896
2450
  try:
1897
2451
  """
1898
2452
  validation
@@ -1918,6 +2472,31 @@ async def reset_password_and_login_using_backup_code_v0(
1918
2472
  detail=output_content,
1919
2473
  )
1920
2474
  user_id = local_list_authentication_user_response[0][User.user_id.name]
2475
+ # check if user has SELF auth provider
2476
+ local_list_response_user_auth_provider = (
2477
+ global_object_square_database_helper.get_rows_v0(
2478
+ database_name=global_string_database_name,
2479
+ schema_name=global_string_schema_name,
2480
+ table_name=UserAuthProvider.__tablename__,
2481
+ filters=FiltersV0(
2482
+ root={
2483
+ UserAuthProvider.user_id.name: FilterConditionsV0(eq=user_id),
2484
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
2485
+ eq=AuthProviderEnum.SELF.value
2486
+ ),
2487
+ }
2488
+ ),
2489
+ )["data"]["main"]
2490
+ )
2491
+ if len(local_list_response_user_auth_provider) != 1:
2492
+ output_content = get_api_output_in_standard_format(
2493
+ message=messages["INCORRECT_AUTH_PROVIDER"],
2494
+ log=f"user_id: {user_id} does not have {AuthProviderEnum.SELF.value} auth provider.",
2495
+ )
2496
+ raise HTTPException(
2497
+ status_code=status.HTTP_400_BAD_REQUEST,
2498
+ detail=output_content,
2499
+ )
1921
2500
  # check if user has recovery method enabled
1922
2501
  local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
1923
2502
  database_name=global_string_database_name,
@@ -2057,6 +2636,18 @@ async def reset_password_and_login_using_backup_code_v0(
2057
2636
  ).strftime("%Y-%m-%d %H:%M:%S.%f+00"),
2058
2637
  },
2059
2638
  )
2639
+ if logout_other_sessions:
2640
+ # delete all sessions for user
2641
+ global_object_square_database_helper.delete_rows_v0(
2642
+ database_name=global_string_database_name,
2643
+ schema_name=global_string_schema_name,
2644
+ table_name=UserSession.__tablename__,
2645
+ filters=FiltersV0(
2646
+ root={
2647
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
2648
+ }
2649
+ ),
2650
+ )
2060
2651
  # generate access token and refresh token
2061
2652
  local_dict_access_token_payload = {
2062
2653
  "app_id": app_id,
@@ -2098,7 +2689,7 @@ async def reset_password_and_login_using_backup_code_v0(
2098
2689
  return value
2099
2690
  """
2100
2691
  output_content = get_api_output_in_standard_format(
2101
- message=messages["GENERIC_CREATION_SUCCESSFUL"],
2692
+ message=messages["GENERIC_ACTION_SUCCESSFUL"],
2102
2693
  data={
2103
2694
  "main": {
2104
2695
  "user_id": user_id,
@@ -2134,7 +2725,6 @@ async def send_reset_password_email_v0(
2134
2725
  body: SendResetPasswordEmailV0,
2135
2726
  ):
2136
2727
  username = body.username
2137
- app_id = body.app_id
2138
2728
  try:
2139
2729
  """
2140
2730
  validation
@@ -2160,6 +2750,31 @@ async def send_reset_password_email_v0(
2160
2750
  detail=output_content,
2161
2751
  )
2162
2752
  user_id = local_list_authentication_user_response[0][User.user_id.name]
2753
+ # check if user has SELF auth provider
2754
+ local_list_response_user_auth_provider = (
2755
+ global_object_square_database_helper.get_rows_v0(
2756
+ database_name=global_string_database_name,
2757
+ schema_name=global_string_schema_name,
2758
+ table_name=UserAuthProvider.__tablename__,
2759
+ filters=FiltersV0(
2760
+ root={
2761
+ UserAuthProvider.user_id.name: FilterConditionsV0(eq=user_id),
2762
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
2763
+ eq=AuthProviderEnum.SELF.value
2764
+ ),
2765
+ }
2766
+ ),
2767
+ )["data"]["main"]
2768
+ )
2769
+ if len(local_list_response_user_auth_provider) != 1:
2770
+ output_content = get_api_output_in_standard_format(
2771
+ message=messages["INCORRECT_AUTH_PROVIDER"],
2772
+ log=f"user_id: {user_id} does not have {AuthProviderEnum.SELF.value} auth provider.",
2773
+ )
2774
+ raise HTTPException(
2775
+ status_code=status.HTTP_400_BAD_REQUEST,
2776
+ detail=output_content,
2777
+ )
2163
2778
  # check if user has recovery method enabled
2164
2779
  local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
2165
2780
  database_name=global_string_database_name,
@@ -2218,14 +2833,17 @@ async def send_reset_password_email_v0(
2218
2833
  """
2219
2834
  main process
2220
2835
  """
2221
- # create 6 digit verification code
2222
- verification_code = random.randint(100000, 999999)
2836
+ verification_code = random.randint(
2837
+ 10 ** (NUMBER_OF_DIGITS_IN_EMAIL_PASSWORD_RESET_CODE - 1),
2838
+ 10**NUMBER_OF_DIGITS_IN_EMAIL_PASSWORD_RESET_CODE - 1,
2839
+ )
2223
2840
  # hash the verification code
2224
2841
  hashed_verification_code = bcrypt.hashpw(
2225
2842
  str(verification_code).encode("utf-8"), bcrypt.gensalt()
2226
2843
  ).decode("utf-8")
2227
- # expire the verification code after 10 minutes
2228
- expires_at = datetime.now(timezone.utc) + timedelta(minutes=10)
2844
+ expires_at = datetime.now(timezone.utc) + timedelta(
2845
+ seconds=EXPIRY_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS
2846
+ )
2229
2847
  # add verification code to UserVerification code table
2230
2848
  global_object_square_database_helper.insert_rows_v0(
2231
2849
  database_name=global_string_database_name,
@@ -2261,7 +2879,7 @@ async def send_reset_password_email_v0(
2261
2879
  to_email=user_profile_data[UserProfile.user_profile_email.name],
2262
2880
  to_name=user_to_name,
2263
2881
  subject="Password Reset Verification Code",
2264
- body=f"Your Password Reset verification code is {verification_code}. It will expire in 10 minutes.",
2882
+ body=f"Your Password Reset verification code is {verification_code}. It will expire in {EXPIRY_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS/60} minutes.",
2265
2883
  api_key=MAIL_GUN_API_KEY,
2266
2884
  domain_name="thepmsquare.com",
2267
2885
  )
@@ -2312,3 +2930,322 @@ async def send_reset_password_email_v0(
2312
2930
  return JSONResponse(
2313
2931
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
2314
2932
  )
2933
+
2934
+
2935
+ @router.post("/reset_password_and_login_using_reset_email_code/v0")
2936
+ @global_object_square_logger.auto_logger()
2937
+ async def reset_password_and_login_using_reset_email_code_v0(
2938
+ body: ResetPasswordAndLoginUsingResetEmailCodeV0,
2939
+ ):
2940
+ reset_email_code = body.reset_email_code
2941
+ username = body.username
2942
+ new_password = body.new_password
2943
+ app_id = body.app_id
2944
+ logout_other_sessions = body.logout_other_sessions
2945
+ try:
2946
+ """
2947
+ validation
2948
+ """
2949
+ # validate username
2950
+ local_list_authentication_user_response = (
2951
+ global_object_square_database_helper.get_rows_v0(
2952
+ database_name=global_string_database_name,
2953
+ schema_name=global_string_schema_name,
2954
+ table_name=User.__tablename__,
2955
+ filters=FiltersV0(
2956
+ root={User.user_username.name: FilterConditionsV0(eq=username)}
2957
+ ),
2958
+ )["data"]["main"]
2959
+ )
2960
+ if len(local_list_authentication_user_response) != 1:
2961
+ output_content = get_api_output_in_standard_format(
2962
+ message=messages["INCORRECT_USERNAME"],
2963
+ log=f"incorrect username: {username}.",
2964
+ )
2965
+ raise HTTPException(
2966
+ status_code=status.HTTP_400_BAD_REQUEST,
2967
+ detail=output_content,
2968
+ )
2969
+ user_id = local_list_authentication_user_response[0][User.user_id.name]
2970
+ # check if user has SELF auth provider
2971
+ local_list_response_user_auth_provider = (
2972
+ global_object_square_database_helper.get_rows_v0(
2973
+ database_name=global_string_database_name,
2974
+ schema_name=global_string_schema_name,
2975
+ table_name=UserAuthProvider.__tablename__,
2976
+ filters=FiltersV0(
2977
+ root={
2978
+ UserAuthProvider.user_id.name: FilterConditionsV0(eq=user_id),
2979
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
2980
+ eq=AuthProviderEnum.SELF.value
2981
+ ),
2982
+ }
2983
+ ),
2984
+ )["data"]["main"]
2985
+ )
2986
+ if len(local_list_response_user_auth_provider) != 1:
2987
+ output_content = get_api_output_in_standard_format(
2988
+ message=messages["INCORRECT_AUTH_PROVIDER"],
2989
+ log=f"user_id: {user_id} does not have {AuthProviderEnum.SELF.value} auth provider.",
2990
+ )
2991
+ raise HTTPException(
2992
+ status_code=status.HTTP_400_BAD_REQUEST,
2993
+ detail=output_content,
2994
+ )
2995
+ # check if user has recovery method enabled
2996
+ local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
2997
+ database_name=global_string_database_name,
2998
+ schema_name=global_string_schema_name,
2999
+ table_name=UserRecoveryMethod.__tablename__,
3000
+ filters=FiltersV0(
3001
+ root={
3002
+ UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
3003
+ UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
3004
+ eq=RecoveryMethodEnum.EMAIL.value
3005
+ ),
3006
+ }
3007
+ ),
3008
+ )[
3009
+ "data"
3010
+ ][
3011
+ "main"
3012
+ ]
3013
+ if len(local_list_response_user_recovery_methods) != 1:
3014
+ output_content = get_api_output_in_standard_format(
3015
+ message=messages["RECOVERY_METHOD_NOT_ENABLED"],
3016
+ log=f"user_id: {user_id} does not have email recovery method enabled.",
3017
+ )
3018
+ raise HTTPException(
3019
+ status_code=status.HTTP_400_BAD_REQUEST,
3020
+ detail=output_content,
3021
+ )
3022
+ # check if user has email in profile
3023
+ user_profile_response = global_object_square_database_helper.get_rows_v0(
3024
+ database_name=global_string_database_name,
3025
+ schema_name=global_string_schema_name,
3026
+ table_name=UserProfile.__tablename__,
3027
+ filters=FiltersV0(
3028
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
3029
+ ),
3030
+ apply_filters=True,
3031
+ )
3032
+ user_profile_data = user_profile_response["data"]["main"][0]
3033
+ if not user_profile_data.get(UserProfile.user_profile_email.name):
3034
+ output_content = get_api_output_in_standard_format(
3035
+ message=messages["GENERIC_MISSING_REQUIRED_FIELD"],
3036
+ log="user seems to have email recovery method enabled, but does not have email in profile.",
3037
+ )
3038
+ raise HTTPException(
3039
+ status_code=status.HTTP_400_BAD_REQUEST,
3040
+ detail=output_content,
3041
+ )
3042
+ # check if email is verified.
3043
+ if not user_profile_data.get(UserProfile.user_profile_email_verified.name):
3044
+ output_content = get_api_output_in_standard_format(
3045
+ message=messages["EMAIL_NOT_VERIFIED"],
3046
+ log="email is not verified.",
3047
+ )
3048
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
3049
+ # validate if user is assigned to the app.
3050
+ # not checking [skipping] if the app exists, as it is not required for this endpoint.
3051
+ local_list_response_user_app = global_object_square_database_helper.get_rows_v0(
3052
+ database_name=global_string_database_name,
3053
+ schema_name=global_string_schema_name,
3054
+ table_name=UserApp.__tablename__,
3055
+ filters=FiltersV0(
3056
+ root={
3057
+ UserApp.user_id.name: FilterConditionsV0(eq=user_id),
3058
+ UserApp.app_id.name: FilterConditionsV0(eq=app_id),
3059
+ }
3060
+ ),
3061
+ )["data"]["main"]
3062
+ if len(local_list_response_user_app) == 0:
3063
+ output_content = get_api_output_in_standard_format(
3064
+ message=messages["GENERIC_400"],
3065
+ log=f"user_id: {user_id} is not assigned to app_id: {app_id}.",
3066
+ )
3067
+ raise HTTPException(
3068
+ status_code=status.HTTP_400_BAD_REQUEST,
3069
+ detail=output_content,
3070
+ )
3071
+ """
3072
+ main process
3073
+ """
3074
+ # validate email reset code
3075
+ local_list_response_user_verification_code = global_object_square_database_helper.get_rows_v0(
3076
+ database_name=global_string_database_name,
3077
+ schema_name=global_string_schema_name,
3078
+ table_name=UserVerificationCode.__tablename__,
3079
+ filters=FiltersV0(
3080
+ root={
3081
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
3082
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
3083
+ eq=VerificationCodeTypeEnum.EMAIL_RECOVERY.value
3084
+ ),
3085
+ UserVerificationCode.user_verification_code_expires_at.name: FilterConditionsV0(
3086
+ gte=datetime.now(timezone.utc).strftime(
3087
+ "%Y-%m-%d %H:%M:%S.%f+00"
3088
+ )
3089
+ ),
3090
+ UserVerificationCode.user_verification_code_used_at.name: FilterConditionsV0(
3091
+ is_null=True
3092
+ ),
3093
+ }
3094
+ ),
3095
+ columns=[UserVerificationCode.user_verification_code_hash.name],
3096
+ order_by=[
3097
+ "-" + UserVerificationCode.user_verification_code_created_at.name
3098
+ ],
3099
+ limit=1,
3100
+ )[
3101
+ "data"
3102
+ ][
3103
+ "main"
3104
+ ]
3105
+ if len(local_list_response_user_verification_code) != 1:
3106
+ output_content = get_api_output_in_standard_format(
3107
+ message=messages["INCORRECT_VERIFICATION_CODE"],
3108
+ log=f"incorrect reset_email_code: {reset_email_code} for user_id: {user_id}.",
3109
+ )
3110
+ raise HTTPException(
3111
+ status_code=status.HTTP_400_BAD_REQUEST,
3112
+ detail=output_content,
3113
+ )
3114
+ latest_hashed_verification_code = local_list_response_user_verification_code[0][
3115
+ UserVerificationCode.user_verification_code_hash.name
3116
+ ]
3117
+
3118
+ if not bcrypt.checkpw(
3119
+ reset_email_code.encode("utf-8"),
3120
+ latest_hashed_verification_code.encode("utf-8"),
3121
+ ):
3122
+ output_content = get_api_output_in_standard_format(
3123
+ message=messages["INCORRECT_VERIFICATION_CODE"],
3124
+ log=f"incorrect reset_email_code: {reset_email_code} for user_id: {user_id}.",
3125
+ )
3126
+ raise HTTPException(
3127
+ status_code=status.HTTP_400_BAD_REQUEST,
3128
+ detail=output_content,
3129
+ )
3130
+
3131
+ # hash the new password
3132
+ local_str_hashed_password = bcrypt.hashpw(
3133
+ new_password.encode("utf-8"), bcrypt.gensalt()
3134
+ ).decode("utf-8")
3135
+ # update the password
3136
+ global_object_square_database_helper.edit_rows_v0(
3137
+ database_name=global_string_database_name,
3138
+ schema_name=global_string_schema_name,
3139
+ table_name=UserCredential.__tablename__,
3140
+ filters=FiltersV0(
3141
+ root={
3142
+ UserCredential.user_id.name: FilterConditionsV0(eq=user_id),
3143
+ }
3144
+ ),
3145
+ data={
3146
+ UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
3147
+ },
3148
+ )
3149
+ # mark the email code as used
3150
+ global_object_square_database_helper.edit_rows_v0(
3151
+ database_name=global_string_database_name,
3152
+ schema_name=global_string_schema_name,
3153
+ table_name=UserVerificationCode.__tablename__,
3154
+ filters=FiltersV0(
3155
+ root={
3156
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
3157
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
3158
+ eq=VerificationCodeTypeEnum.EMAIL_RECOVERY.value
3159
+ ),
3160
+ UserVerificationCode.user_verification_code_hash.name: FilterConditionsV0(
3161
+ eq=latest_hashed_verification_code
3162
+ ),
3163
+ }
3164
+ ),
3165
+ data={
3166
+ UserVerificationCode.user_verification_code_used_at.name: datetime.now(
3167
+ timezone.utc
3168
+ ).strftime("%Y-%m-%d %H:%M:%S.%f+00"),
3169
+ },
3170
+ )
3171
+ if logout_other_sessions:
3172
+ # delete all sessions for user
3173
+ global_object_square_database_helper.delete_rows_v0(
3174
+ database_name=global_string_database_name,
3175
+ schema_name=global_string_schema_name,
3176
+ table_name=UserSession.__tablename__,
3177
+ filters=FiltersV0(
3178
+ root={
3179
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
3180
+ }
3181
+ ),
3182
+ )
3183
+ # generate access token and refresh token
3184
+ local_dict_access_token_payload = {
3185
+ "app_id": app_id,
3186
+ "user_id": user_id,
3187
+ "exp": datetime.now(timezone.utc)
3188
+ + timedelta(minutes=config_int_access_token_valid_minutes),
3189
+ }
3190
+ local_str_access_token = jwt.encode(
3191
+ local_dict_access_token_payload, config_str_secret_key_for_access_token
3192
+ )
3193
+ local_object_refresh_token_expiry_time = datetime.now(timezone.utc) + timedelta(
3194
+ minutes=config_int_refresh_token_valid_minutes
3195
+ )
3196
+ local_dict_refresh_token_payload = {
3197
+ "app_id": app_id,
3198
+ "user_id": user_id,
3199
+ "exp": local_object_refresh_token_expiry_time,
3200
+ }
3201
+ local_str_refresh_token = jwt.encode(
3202
+ local_dict_refresh_token_payload, config_str_secret_key_for_refresh_token
3203
+ )
3204
+ # insert the refresh token in the database
3205
+ global_object_square_database_helper.insert_rows_v0(
3206
+ database_name=global_string_database_name,
3207
+ schema_name=global_string_schema_name,
3208
+ table_name=UserSession.__tablename__,
3209
+ data=[
3210
+ {
3211
+ UserSession.user_id.name: user_id,
3212
+ UserSession.app_id.name: app_id,
3213
+ UserSession.user_session_refresh_token.name: local_str_refresh_token,
3214
+ UserSession.user_session_expiry_time.name: local_object_refresh_token_expiry_time.strftime(
3215
+ "%Y-%m-%d %H:%M:%S.%f+00"
3216
+ ),
3217
+ }
3218
+ ],
3219
+ )
3220
+ """
3221
+ return value
3222
+ """
3223
+ output_content = get_api_output_in_standard_format(
3224
+ message=messages["GENERIC_ACTION_SUCCESSFUL"],
3225
+ data={
3226
+ "main": {
3227
+ "user_id": user_id,
3228
+ "access_token": local_str_access_token,
3229
+ "refresh_token": local_str_refresh_token,
3230
+ }
3231
+ },
3232
+ )
3233
+
3234
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
3235
+ except HTTPException as http_exception:
3236
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
3237
+ return JSONResponse(
3238
+ status_code=http_exception.status_code, content=http_exception.detail
3239
+ )
3240
+ except Exception as e:
3241
+ """
3242
+ rollback logic
3243
+ """
3244
+ global_object_square_logger.logger.error(e, exc_info=True)
3245
+ output_content = get_api_output_in_standard_format(
3246
+ message=messages["GENERIC_500"],
3247
+ log=str(e),
3248
+ )
3249
+ return JSONResponse(
3250
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
3251
+ )