square-authentication 10.0.0__py3-none-any.whl → 10.0.2__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.
@@ -0,0 +1,3251 @@
1
+ import copy
2
+ import io
3
+ import mimetypes
4
+ import random
5
+ import re
6
+ import uuid
7
+ from datetime import datetime, timedelta, timezone
8
+
9
+ import bcrypt
10
+ import jwt
11
+ from fastapi import HTTPException, status
12
+ from fastapi.responses import JSONResponse
13
+ from google.auth.transport import requests as google_requests
14
+ from google.oauth2 import id_token
15
+ from requests import HTTPError
16
+ from square_commons import get_api_output_in_standard_format, send_email_using_mailgun
17
+ from square_commons.api_utils import make_request
18
+ from square_database_helper.pydantic_models import FilterConditionsV0, FiltersV0
19
+ from square_database_structure.square import global_string_database_name
20
+ from square_database_structure.square.authentication import global_string_schema_name
21
+ from square_database_structure.square.authentication.enums import (
22
+ RecoveryMethodEnum,
23
+ AuthProviderEnum,
24
+ VerificationCodeTypeEnum,
25
+ )
26
+ from square_database_structure.square.authentication.tables import (
27
+ User,
28
+ UserApp,
29
+ UserCredential,
30
+ UserSession,
31
+ UserProfile,
32
+ UserRecoveryMethod,
33
+ UserAuthProvider,
34
+ UserVerificationCode,
35
+ )
36
+ from square_database_structure.square.email import (
37
+ global_string_schema_name as email_schema_name,
38
+ )
39
+ from square_database_structure.square.email.enums import EmailTypeEnum, EmailStatusEnum
40
+ from square_database_structure.square.email.tables import EmailLog
41
+ from square_database_structure.square.public import (
42
+ global_string_schema_name as global_string_public_schema_name,
43
+ )
44
+ from square_database_structure.square.public.tables import App
45
+
46
+ from square_authentication.configuration import (
47
+ GOOGLE_AUTH_PLATFORM_CLIENT_ID,
48
+ )
49
+ from square_authentication.configuration import (
50
+ config_int_access_token_valid_minutes,
51
+ config_int_refresh_token_valid_minutes,
52
+ config_str_secret_key_for_access_token,
53
+ config_str_secret_key_for_refresh_token,
54
+ global_object_square_logger,
55
+ global_object_square_database_helper,
56
+ MAIL_GUN_API_KEY,
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,
61
+ RESEND_COOL_DOWN_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS,
62
+ )
63
+ from square_authentication.messages import messages
64
+ from square_authentication.pydantic_models.core import (
65
+ TokenType,
66
+ )
67
+ from square_authentication.utils.core import generate_default_username_for_google_users
68
+ from square_authentication.utils.token import get_jwt_payload
69
+
70
+
71
+ @global_object_square_logger.auto_logger()
72
+ def util_register_username_v0(username, password, app_id):
73
+ local_str_user_id = None
74
+ local_str_access_token = None
75
+ local_str_refresh_token = None
76
+ local_object_refresh_token_expiry_time = None
77
+ username = username.lower()
78
+ try:
79
+ """
80
+ validation
81
+ """
82
+ # validation for username
83
+ username_pattern = re.compile(r"^[a-z0-9._-]{2,20}$")
84
+ if not username_pattern.match(username):
85
+ output_content = get_api_output_in_standard_format(
86
+ message=messages["USERNAME_INVALID"],
87
+ log=f"username '{username}' is invalid. it must start and end with a letter, "
88
+ f"contain only lowercase letters, numbers, underscores, or hyphens, "
89
+ f"and not have consecutive separators.",
90
+ )
91
+ raise HTTPException(
92
+ status_code=status.HTTP_400_BAD_REQUEST,
93
+ detail=output_content,
94
+ )
95
+ local_list_response_user_creds = (
96
+ global_object_square_database_helper.get_rows_v0(
97
+ database_name=global_string_database_name,
98
+ schema_name=global_string_schema_name,
99
+ table_name=User.__tablename__,
100
+ filters=FiltersV0(
101
+ root={User.user_username.name: FilterConditionsV0(eq=username)}
102
+ ),
103
+ )["data"]["main"]
104
+ )
105
+ if len(local_list_response_user_creds) > 0:
106
+ output_content = get_api_output_in_standard_format(
107
+ message=messages["USERNAME_ALREADY_EXISTS"],
108
+ log=f"an account with the username {username} already exists.",
109
+ )
110
+ raise HTTPException(
111
+ status_code=status.HTTP_409_CONFLICT,
112
+ detail=output_content,
113
+ )
114
+
115
+ """
116
+ main process
117
+ """
118
+ # entry in user table
119
+ local_list_response_user = global_object_square_database_helper.insert_rows_v0(
120
+ data=[
121
+ {
122
+ User.user_username.name: username,
123
+ }
124
+ ],
125
+ database_name=global_string_database_name,
126
+ schema_name=global_string_schema_name,
127
+ table_name=User.__tablename__,
128
+ )["data"]["main"]
129
+ local_str_user_id = local_list_response_user[0][User.user_id.name]
130
+
131
+ # entry in user auth provider table
132
+ global_object_square_database_helper.insert_rows_v0(
133
+ data=[
134
+ {
135
+ UserAuthProvider.user_id.name: local_str_user_id,
136
+ UserAuthProvider.auth_provider.name: AuthProviderEnum.SELF.value,
137
+ }
138
+ ],
139
+ database_name=global_string_database_name,
140
+ schema_name=global_string_schema_name,
141
+ table_name=UserAuthProvider.__tablename__,
142
+ )
143
+
144
+ # entry in user profile table
145
+ global_object_square_database_helper.insert_rows_v0(
146
+ database_name=global_string_database_name,
147
+ schema_name=global_string_schema_name,
148
+ table_name=UserProfile.__tablename__,
149
+ data=[
150
+ {
151
+ UserProfile.user_id.name: local_str_user_id,
152
+ }
153
+ ],
154
+ )
155
+
156
+ # entry in credential table
157
+
158
+ # hash password
159
+ local_str_hashed_password = bcrypt.hashpw(
160
+ password.encode("utf-8"), bcrypt.gensalt()
161
+ ).decode("utf-8")
162
+
163
+ global_object_square_database_helper.insert_rows_v0(
164
+ data=[
165
+ {
166
+ UserCredential.user_id.name: local_str_user_id,
167
+ UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
168
+ }
169
+ ],
170
+ database_name=global_string_database_name,
171
+ schema_name=global_string_schema_name,
172
+ table_name=UserCredential.__tablename__,
173
+ )
174
+ if app_id is not None:
175
+ # assign app to user
176
+ global_object_square_database_helper.insert_rows_v0(
177
+ database_name=global_string_database_name,
178
+ schema_name=global_string_schema_name,
179
+ table_name=UserApp.__tablename__,
180
+ data=[
181
+ {
182
+ UserApp.user_id.name: local_str_user_id,
183
+ UserApp.app_id.name: app_id,
184
+ }
185
+ ],
186
+ )
187
+
188
+ # return new access token and refresh token
189
+ # create access token
190
+ local_dict_access_token_payload = {
191
+ "app_id": app_id,
192
+ "user_id": local_str_user_id,
193
+ "exp": datetime.now(timezone.utc)
194
+ + timedelta(minutes=config_int_access_token_valid_minutes),
195
+ }
196
+ local_str_access_token = jwt.encode(
197
+ local_dict_access_token_payload,
198
+ config_str_secret_key_for_access_token,
199
+ )
200
+
201
+ # create refresh token
202
+ local_object_refresh_token_expiry_time = datetime.now(
203
+ timezone.utc
204
+ ) + timedelta(minutes=config_int_refresh_token_valid_minutes)
205
+
206
+ local_dict_refresh_token_payload = {
207
+ "app_id": app_id,
208
+ "user_id": local_str_user_id,
209
+ "exp": local_object_refresh_token_expiry_time,
210
+ }
211
+ local_str_refresh_token = jwt.encode(
212
+ local_dict_refresh_token_payload,
213
+ config_str_secret_key_for_refresh_token,
214
+ )
215
+ # entry in user session table
216
+ global_object_square_database_helper.insert_rows_v0(
217
+ data=[
218
+ {
219
+ UserSession.user_id.name: local_str_user_id,
220
+ UserSession.app_id.name: app_id,
221
+ UserSession.user_session_refresh_token.name: local_str_refresh_token,
222
+ UserSession.user_session_expiry_time.name: local_object_refresh_token_expiry_time.strftime(
223
+ "%Y-%m-%d %H:%M:%S.%f+00"
224
+ ),
225
+ }
226
+ ],
227
+ database_name=global_string_database_name,
228
+ schema_name=global_string_schema_name,
229
+ table_name=UserSession.__tablename__,
230
+ )
231
+ """
232
+ return value
233
+ """
234
+ output_content = get_api_output_in_standard_format(
235
+ message=messages["REGISTRATION_SUCCESSFUL"],
236
+ data={
237
+ "main": {
238
+ "user_id": local_str_user_id,
239
+ "username": username,
240
+ "app_id": app_id,
241
+ "access_token": local_str_access_token,
242
+ "refresh_token": local_str_refresh_token,
243
+ "refresh_token_expiry_time": local_object_refresh_token_expiry_time.isoformat(),
244
+ },
245
+ },
246
+ )
247
+ return JSONResponse(
248
+ status_code=status.HTTP_201_CREATED,
249
+ content=output_content,
250
+ )
251
+ except HTTPException as http_exception:
252
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
253
+ return JSONResponse(
254
+ status_code=http_exception.status_code, content=http_exception.detail
255
+ )
256
+ except Exception as e:
257
+ global_object_square_logger.logger.error(e, exc_info=True)
258
+ """
259
+ rollback logic
260
+ """
261
+ if local_str_user_id:
262
+ global_object_square_database_helper.delete_rows_v0(
263
+ database_name=global_string_database_name,
264
+ schema_name=global_string_schema_name,
265
+ table_name=User.__tablename__,
266
+ filters=FiltersV0(
267
+ root={User.user_id.name: FilterConditionsV0(eq=local_str_user_id)}
268
+ ),
269
+ )
270
+ output_content = get_api_output_in_standard_format(
271
+ message=messages["GENERIC_500"],
272
+ log=str(e),
273
+ )
274
+ return JSONResponse(
275
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
276
+ )
277
+
278
+
279
+ @global_object_square_logger.auto_logger()
280
+ def util_register_login_google_v0(google_id, assign_app_id_if_missing, app_id):
281
+ was_new_user = False
282
+ try:
283
+ """
284
+ validation
285
+ """
286
+ # verify id token
287
+ try:
288
+ id_info = id_token.verify_oauth2_token(
289
+ google_id,
290
+ google_requests.Request(),
291
+ GOOGLE_AUTH_PLATFORM_CLIENT_ID,
292
+ )
293
+ except Exception:
294
+ output_content = get_api_output_in_standard_format(
295
+ message=messages["GENERIC_400"],
296
+ log="Google id is invalid.",
297
+ )
298
+ raise HTTPException(
299
+ status_code=status.HTTP_400_BAD_REQUEST,
300
+ detail=output_content,
301
+ )
302
+
303
+ # validate if email is verified
304
+ if id_info.get("email_verified") is not True:
305
+ output_content = get_api_output_in_standard_format(
306
+ message=messages["EMAIL_NOT_VERIFIED"],
307
+ log="Google account email is not verified.",
308
+ )
309
+ raise HTTPException(
310
+ status_code=status.HTTP_400_BAD_REQUEST,
311
+ detail=output_content,
312
+ )
313
+ """
314
+ processing
315
+ """
316
+ google_sub = id_info["sub"]
317
+ email = id_info.get("email")
318
+ given_name = id_info.get("given_name")
319
+ family_name = id_info.get("family_name")
320
+
321
+ profile_picture = id_info.get("picture")
322
+
323
+ # check if user exists
324
+ user_rows = global_object_square_database_helper.get_rows_v0(
325
+ database_name=global_string_database_name,
326
+ schema_name=global_string_schema_name,
327
+ table_name=UserAuthProvider.__tablename__,
328
+ filters=FiltersV0(
329
+ root={
330
+ UserAuthProvider.auth_provider_user_id.name: FilterConditionsV0(
331
+ eq=google_sub
332
+ ),
333
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
334
+ eq=AuthProviderEnum.GOOGLE.value
335
+ ),
336
+ }
337
+ ),
338
+ )["data"]["main"]
339
+
340
+ if user_rows:
341
+ # login
342
+
343
+ # validate if app_id is assigned to user
344
+ # this will also validate if app_id is valid
345
+ local_str_user_id = user_rows[0][User.user_id.name]
346
+ user_record = global_object_square_database_helper.get_rows_v0(
347
+ database_name=global_string_database_name,
348
+ schema_name=global_string_schema_name,
349
+ table_name=User.__tablename__,
350
+ filters=FiltersV0(
351
+ root={User.user_id.name: FilterConditionsV0(eq=local_str_user_id)}
352
+ ),
353
+ )["data"]["main"][0]
354
+ username = user_record[User.user_username.name]
355
+ local_list_user_app_response = (
356
+ global_object_square_database_helper.get_rows_v0(
357
+ database_name=global_string_database_name,
358
+ schema_name=global_string_schema_name,
359
+ table_name=UserApp.__tablename__,
360
+ filters=FiltersV0(
361
+ root={
362
+ UserApp.user_id.name: FilterConditionsV0(
363
+ eq=local_str_user_id
364
+ ),
365
+ UserApp.app_id.name: FilterConditionsV0(eq=app_id),
366
+ }
367
+ ),
368
+ )["data"]["main"]
369
+ )
370
+ if len(local_list_user_app_response) == 0:
371
+ if assign_app_id_if_missing:
372
+ global_object_square_database_helper.insert_rows_v0(
373
+ database_name=global_string_database_name,
374
+ schema_name=global_string_schema_name,
375
+ table_name=UserApp.__tablename__,
376
+ data=[
377
+ {
378
+ UserApp.user_id.name: local_str_user_id,
379
+ UserApp.app_id.name: app_id,
380
+ }
381
+ ],
382
+ )
383
+ else:
384
+ output_content = get_api_output_in_standard_format(
385
+ message=messages["GENERIC_400"],
386
+ log=f"user_id {local_str_user_id}({username}) not assigned to app {app_id}.",
387
+ )
388
+ raise HTTPException(
389
+ status_code=status.HTTP_400_BAD_REQUEST,
390
+ detail=output_content,
391
+ )
392
+ else:
393
+ # register
394
+
395
+ was_new_user = True
396
+ # check if account with same email address exists
397
+ profile_rows = global_object_square_database_helper.get_rows_v0(
398
+ database_name=global_string_database_name,
399
+ schema_name=global_string_schema_name,
400
+ table_name=UserProfile.__tablename__,
401
+ filters=FiltersV0(
402
+ root={
403
+ UserProfile.user_profile_email.name: FilterConditionsV0(
404
+ eq=email
405
+ )
406
+ }
407
+ ),
408
+ )["data"]["main"]
409
+ if len(profile_rows) > 0:
410
+ output_content = get_api_output_in_standard_format(
411
+ message=messages["ACCOUNT_WITH_EMAIL_ALREADY_EXISTS"],
412
+ log=f"An account with the email {email} already exists.",
413
+ )
414
+ raise HTTPException(
415
+ status_code=status.HTTP_409_CONFLICT,
416
+ detail=output_content,
417
+ )
418
+ # generate a default username
419
+ username = generate_default_username_for_google_users(
420
+ family_name=family_name, given_name=given_name
421
+ )
422
+ # create user
423
+ user_rows = global_object_square_database_helper.insert_rows_v0(
424
+ database_name=global_string_database_name,
425
+ schema_name=global_string_schema_name,
426
+ table_name=User.__tablename__,
427
+ data=[
428
+ {
429
+ User.user_username.name: username,
430
+ }
431
+ ],
432
+ )["data"]["main"]
433
+ local_str_user_id = user_rows[0][User.user_id.name]
434
+
435
+ # link to user_auth_provider
436
+ global_object_square_database_helper.insert_rows_v0(
437
+ database_name=global_string_database_name,
438
+ schema_name=global_string_schema_name,
439
+ table_name=UserAuthProvider.__tablename__,
440
+ data=[
441
+ {
442
+ UserAuthProvider.user_id.name: local_str_user_id,
443
+ UserAuthProvider.auth_provider.name: AuthProviderEnum.GOOGLE.value,
444
+ UserAuthProvider.auth_provider_user_id.name: google_sub,
445
+ }
446
+ ],
447
+ )
448
+ # getting profile picture
449
+ if profile_picture:
450
+ try:
451
+ profile_picture_response = make_request(
452
+ "GET", profile_picture, return_type="response"
453
+ )
454
+
455
+ # finding content type and filename
456
+ headers = profile_picture_response.headers
457
+ content_type = headers.get(
458
+ "Content-Type", "application/octet-stream"
459
+ )
460
+ content_disposition = headers.get("Content-Disposition", "")
461
+
462
+ if content_disposition:
463
+ match = re.search(r'filename="([^"]+)"', content_disposition)
464
+ if match:
465
+ filename = match.group(1)
466
+ else:
467
+ filename = None
468
+ else:
469
+ filename = None
470
+ if filename is None:
471
+ global_object_square_logger.logger.warning(
472
+ f"user_id {local_str_user_id}'s profile picture from Google missing filename; guessing extension from Content-Type: {content_type}."
473
+ )
474
+ ext = (
475
+ mimetypes.guess_extension(
476
+ content_type.split(";")[0].strip()
477
+ )
478
+ or ""
479
+ )
480
+ filename = f"profile_photo{ext}"
481
+ if not ext:
482
+ filename += ".bin"
483
+
484
+ # upload bytes to square_file_storage
485
+ file_upload_response = global_object_square_file_store_helper.upload_file_using_tuple_v0(
486
+ file=(
487
+ filename,
488
+ io.BytesIO(profile_picture_response.content),
489
+ content_type,
490
+ ),
491
+ system_relative_path="global/users/profile_photos",
492
+ )
493
+ user_profile_photo_storage_token = file_upload_response["data"][
494
+ "main"
495
+ ]
496
+ except HTTPError:
497
+ global_object_square_logger.logger.error(
498
+ f"Failed to fetch profile picture for user_id {local_str_user_id} from google account.",
499
+ exc_info=True,
500
+ )
501
+ user_profile_photo_storage_token = None
502
+ except Exception as e:
503
+ global_object_square_logger.logger.error(
504
+ f"Error while fetching profile picture for user_id {local_str_user_id} from google account: {str(e)}",
505
+ exc_info=True,
506
+ )
507
+ user_profile_photo_storage_token = None
508
+ else:
509
+ global_object_square_logger.logger.warning(
510
+ f"user_id {local_str_user_id} has no profile picture in google account."
511
+ )
512
+ user_profile_photo_storage_token = None
513
+ # create user profile
514
+ global_object_square_database_helper.insert_rows_v0(
515
+ database_name=global_string_database_name,
516
+ schema_name=global_string_schema_name,
517
+ table_name=UserProfile.__tablename__,
518
+ data=[
519
+ {
520
+ UserProfile.user_id.name: local_str_user_id,
521
+ UserProfile.user_profile_email.name: email,
522
+ UserProfile.user_profile_email_verified.name: datetime.now(
523
+ timezone.utc
524
+ ).strftime("%Y-%m-%d %H:%M:%S.%f+00"),
525
+ UserProfile.user_profile_first_name.name: given_name,
526
+ UserProfile.user_profile_last_name.name: family_name,
527
+ UserProfile.user_profile_photo_storage_token.name: user_profile_photo_storage_token,
528
+ }
529
+ ],
530
+ )
531
+
532
+ # assign app if provided
533
+ if app_id is not None:
534
+ global_object_square_database_helper.insert_rows_v0(
535
+ database_name=global_string_database_name,
536
+ schema_name=global_string_schema_name,
537
+ table_name=UserApp.__tablename__,
538
+ data=[
539
+ {
540
+ UserApp.user_id.name: local_str_user_id,
541
+ UserApp.app_id.name: app_id,
542
+ }
543
+ ],
544
+ )
545
+
546
+ # generate tokens
547
+ now = datetime.now(timezone.utc)
548
+ access_token_payload = {
549
+ "app_id": app_id,
550
+ "user_id": local_str_user_id,
551
+ "exp": now + timedelta(minutes=config_int_access_token_valid_minutes),
552
+ }
553
+ access_token_str = jwt.encode(
554
+ access_token_payload,
555
+ config_str_secret_key_for_access_token,
556
+ )
557
+
558
+ refresh_token_expiry = now + timedelta(
559
+ minutes=config_int_refresh_token_valid_minutes
560
+ )
561
+ refresh_token_payload = {
562
+ "app_id": app_id,
563
+ "user_id": local_str_user_id,
564
+ "exp": refresh_token_expiry,
565
+ }
566
+ refresh_token_str = jwt.encode(
567
+ refresh_token_payload,
568
+ config_str_secret_key_for_refresh_token,
569
+ )
570
+
571
+ # store refresh token
572
+ global_object_square_database_helper.insert_rows_v0(
573
+ database_name=global_string_database_name,
574
+ schema_name=global_string_schema_name,
575
+ table_name=UserSession.__tablename__,
576
+ data=[
577
+ {
578
+ UserSession.user_id.name: local_str_user_id,
579
+ UserSession.app_id.name: app_id,
580
+ UserSession.user_session_refresh_token.name: refresh_token_str,
581
+ UserSession.user_session_expiry_time.name: refresh_token_expiry.strftime(
582
+ "%Y-%m-%d %H:%M:%S.%f+00"
583
+ ),
584
+ }
585
+ ],
586
+ )
587
+ """
588
+ return value
589
+ """
590
+ if was_new_user:
591
+ message = messages["REGISTRATION_SUCCESSFUL"]
592
+ else:
593
+ message = messages["LOGIN_SUCCESSFUL"]
594
+ output_content = get_api_output_in_standard_format(
595
+ message=message,
596
+ data={
597
+ "main": {
598
+ "user_id": local_str_user_id,
599
+ "username": username,
600
+ "app_id": app_id,
601
+ "access_token": access_token_str,
602
+ "refresh_token": refresh_token_str,
603
+ "refresh_token_expiry_time": refresh_token_expiry.isoformat(),
604
+ "was_new_user": was_new_user,
605
+ },
606
+ },
607
+ )
608
+
609
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
610
+ except HTTPError as http_error:
611
+ global_object_square_logger.logger.error(http_error, exc_info=True)
612
+ return JSONResponse(
613
+ status_code=http_error.response.status_code,
614
+ content=http_error.response.text,
615
+ )
616
+ except HTTPException as http_exception:
617
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
618
+ return JSONResponse(
619
+ status_code=http_exception.status_code, content=http_exception.detail
620
+ )
621
+ except Exception as e:
622
+ """
623
+ rollback logic
624
+ """
625
+ global_object_square_logger.logger.error(e, exc_info=True)
626
+ output_content = get_api_output_in_standard_format(
627
+ message=messages["GENERIC_500"],
628
+ log=str(e),
629
+ )
630
+ return JSONResponse(
631
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
632
+ content=output_content,
633
+ )
634
+
635
+
636
+ @global_object_square_logger.auto_logger()
637
+ def util_get_user_details_v0(access_token):
638
+ try:
639
+ """
640
+ validation
641
+ """
642
+ # validate access token
643
+ try:
644
+ local_dict_access_token_payload = get_jwt_payload(
645
+ access_token, config_str_secret_key_for_access_token
646
+ )
647
+ except Exception as error:
648
+ output_content = get_api_output_in_standard_format(
649
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
650
+ )
651
+ raise HTTPException(
652
+ status_code=status.HTTP_400_BAD_REQUEST,
653
+ detail=output_content,
654
+ )
655
+ user_id = local_dict_access_token_payload["user_id"]
656
+ """
657
+ main process
658
+ """
659
+ local_list_user = global_object_square_database_helper.get_rows_v0(
660
+ database_name=global_string_database_name,
661
+ schema_name=global_string_schema_name,
662
+ table_name=User.__tablename__,
663
+ filters=FiltersV0(
664
+ root={
665
+ User.user_id.name: FilterConditionsV0(eq=user_id),
666
+ }
667
+ ),
668
+ )["data"]["main"]
669
+ local_list_app = global_object_square_database_helper.get_rows_v0(
670
+ database_name=global_string_database_name,
671
+ schema_name=global_string_public_schema_name,
672
+ table_name=App.__tablename__,
673
+ apply_filters=False,
674
+ filters=FiltersV0(root={}),
675
+ )["data"]["main"]
676
+ local_list_response_user_app = global_object_square_database_helper.get_rows_v0(
677
+ database_name=global_string_database_name,
678
+ schema_name=global_string_schema_name,
679
+ table_name=UserApp.__tablename__,
680
+ filters=FiltersV0(
681
+ root={UserApp.user_id.name: FilterConditionsV0(eq=user_id)}
682
+ ),
683
+ )["data"]["main"]
684
+ local_list_response_user_profile = (
685
+ global_object_square_database_helper.get_rows_v0(
686
+ database_name=global_string_database_name,
687
+ schema_name=global_string_schema_name,
688
+ table_name=UserProfile.__tablename__,
689
+ filters=FiltersV0(
690
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
691
+ ),
692
+ )["data"]["main"]
693
+ )
694
+ local_list_response_user_sessions = (
695
+ global_object_square_database_helper.get_rows_v0(
696
+ database_name=global_string_database_name,
697
+ schema_name=global_string_schema_name,
698
+ table_name=UserSession.__tablename__,
699
+ filters=FiltersV0(
700
+ root={
701
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
702
+ UserSession.user_session_expiry_time.name: FilterConditionsV0(
703
+ gte=datetime.now(timezone.utc).isoformat()
704
+ ),
705
+ }
706
+ ),
707
+ )["data"]["main"]
708
+ )
709
+ user_profile = copy.deepcopy(local_list_response_user_profile[0])
710
+ del user_profile[UserProfile.user_id.name]
711
+ """
712
+ return value
713
+ """
714
+ return_this = {
715
+ "user_id": user_id,
716
+ "username": local_list_user[0][User.user_username.name],
717
+ "profile": user_profile,
718
+ "apps": [
719
+ y[App.app_name.name]
720
+ for y in local_list_app
721
+ if y[App.app_id.name]
722
+ in [x[UserApp.app_id.name] for x in local_list_response_user_app]
723
+ ],
724
+ "sessions": [
725
+ {
726
+ "app_name": [
727
+ y[App.app_name.name]
728
+ for y in local_list_app
729
+ if y[App.app_id.name] == x[UserApp.app_id.name]
730
+ ][0],
731
+ "active_sessions": len(
732
+ [
733
+ y
734
+ for y in local_list_response_user_sessions
735
+ if y[UserSession.app_id.name] == x[UserApp.app_id.name]
736
+ ]
737
+ ),
738
+ }
739
+ for x in local_list_response_user_app
740
+ ],
741
+ }
742
+ output_content = get_api_output_in_standard_format(
743
+ message=messages["GENERIC_READ_SUCCESSFUL"],
744
+ data={"main": return_this},
745
+ )
746
+ return JSONResponse(
747
+ status_code=status.HTTP_200_OK,
748
+ content=output_content,
749
+ )
750
+ except HTTPException as http_exception:
751
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
752
+ return JSONResponse(
753
+ status_code=http_exception.status_code, content=http_exception.detail
754
+ )
755
+ except Exception as e:
756
+ """
757
+ rollback logic
758
+ """
759
+ global_object_square_logger.logger.error(e, exc_info=True)
760
+ output_content = get_api_output_in_standard_format(
761
+ message=messages["GENERIC_500"],
762
+ log=str(e),
763
+ )
764
+ return JSONResponse(
765
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
766
+ content=output_content,
767
+ )
768
+
769
+
770
+ @global_object_square_logger.auto_logger()
771
+ def util_update_user_app_ids_v0(access_token, app_ids_to_add, app_ids_to_remove):
772
+ try:
773
+
774
+ """
775
+ validation
776
+ """
777
+ # validate access token
778
+ try:
779
+ local_dict_access_token_payload = get_jwt_payload(
780
+ access_token, config_str_secret_key_for_access_token
781
+ )
782
+ except Exception as error:
783
+ output_content = get_api_output_in_standard_format(
784
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
785
+ )
786
+ raise HTTPException(
787
+ status_code=status.HTTP_400_BAD_REQUEST,
788
+ detail=output_content,
789
+ )
790
+ user_id = local_dict_access_token_payload["user_id"]
791
+
792
+ app_ids_to_add = list(set(app_ids_to_add))
793
+ app_ids_to_remove = list(set(app_ids_to_remove))
794
+
795
+ # check if app_ids_to_add and app_ids_to_remove don't have common ids.
796
+ local_list_common_app_ids = set(app_ids_to_add) & set(app_ids_to_remove)
797
+ if len(local_list_common_app_ids) > 0:
798
+ output_content = get_api_output_in_standard_format(
799
+ message=messages["GENERIC_400"],
800
+ log=f"invalid app_ids: {list(local_list_common_app_ids)}, present in both add list and remove list.",
801
+ )
802
+ raise HTTPException(
803
+ status_code=status.HTTP_400_BAD_REQUEST,
804
+ detail=output_content,
805
+ )
806
+
807
+ # check if all app_ids are valid
808
+ local_list_all_app_ids = [*app_ids_to_add, *app_ids_to_remove]
809
+ local_list_response_app = global_object_square_database_helper.get_rows_v0(
810
+ database_name=global_string_database_name,
811
+ schema_name=global_string_public_schema_name,
812
+ table_name=App.__tablename__,
813
+ apply_filters=False,
814
+ filters=FiltersV0(root={}),
815
+ )["data"]["main"]
816
+ local_list_invalid_ids = [
817
+ x
818
+ for x in local_list_all_app_ids
819
+ if x not in [y[App.app_id.name] for y in local_list_response_app]
820
+ ]
821
+ if len(local_list_invalid_ids) > 0:
822
+ output_content = get_api_output_in_standard_format(
823
+ message=messages["GENERIC_400"],
824
+ log=f"invalid app_ids: {local_list_invalid_ids}.",
825
+ )
826
+ raise HTTPException(
827
+ status_code=status.HTTP_400_BAD_REQUEST,
828
+ detail=output_content,
829
+ )
830
+ """
831
+ main process
832
+ """
833
+ # logic for adding new app_ids
834
+ local_list_response_user_app = global_object_square_database_helper.get_rows_v0(
835
+ database_name=global_string_database_name,
836
+ schema_name=global_string_schema_name,
837
+ table_name=UserApp.__tablename__,
838
+ filters=FiltersV0(
839
+ root={UserApp.user_id.name: FilterConditionsV0(eq=user_id)}
840
+ ),
841
+ )["data"]["main"]
842
+ local_list_new_app_ids = [
843
+ {
844
+ UserApp.user_id.name: user_id,
845
+ UserApp.app_id.name: x,
846
+ }
847
+ for x in app_ids_to_add
848
+ if x not in [y[UserApp.app_id.name] for y in local_list_response_user_app]
849
+ ]
850
+ if len(local_list_new_app_ids) > 0:
851
+ global_object_square_database_helper.insert_rows_v0(
852
+ database_name=global_string_database_name,
853
+ schema_name=global_string_schema_name,
854
+ table_name=UserApp.__tablename__,
855
+ data=local_list_new_app_ids,
856
+ )
857
+
858
+ # logic for removing app_ids
859
+ for app_id in app_ids_to_remove:
860
+ global_object_square_database_helper.delete_rows_v0(
861
+ database_name=global_string_database_name,
862
+ schema_name=global_string_schema_name,
863
+ table_name=UserApp.__tablename__,
864
+ filters=FiltersV0(
865
+ root={
866
+ UserApp.user_id.name: FilterConditionsV0(eq=user_id),
867
+ UserApp.app_id.name: FilterConditionsV0(eq=app_id),
868
+ }
869
+ ),
870
+ )
871
+ # logout user from removed apps
872
+ global_object_square_database_helper.delete_rows_v0(
873
+ database_name=global_string_database_name,
874
+ schema_name=global_string_schema_name,
875
+ table_name=UserSession.__tablename__,
876
+ filters=FiltersV0(
877
+ root={
878
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
879
+ UserSession.app_id.name: FilterConditionsV0(eq=app_id),
880
+ }
881
+ ),
882
+ )
883
+
884
+ """
885
+ return value
886
+ """
887
+ # get latest app ids
888
+ local_list_response_user_app = global_object_square_database_helper.get_rows_v0(
889
+ database_name=global_string_database_name,
890
+ schema_name=global_string_schema_name,
891
+ table_name=UserApp.__tablename__,
892
+ filters=FiltersV0(
893
+ root={UserApp.user_id.name: FilterConditionsV0(eq=user_id)}
894
+ ),
895
+ )["data"]["main"]
896
+ output_content = get_api_output_in_standard_format(
897
+ message=messages["GENERIC_UPDATE_SUCCESSFUL"],
898
+ data={
899
+ "main": [x[UserApp.app_id.name] for x in local_list_response_user_app]
900
+ },
901
+ )
902
+ return JSONResponse(
903
+ status_code=status.HTTP_200_OK,
904
+ content=output_content,
905
+ )
906
+ except HTTPException as http_exception:
907
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
908
+ return JSONResponse(
909
+ status_code=http_exception.status_code, content=http_exception.detail
910
+ )
911
+ except Exception as e:
912
+ """
913
+ rollback logic
914
+ """
915
+ global_object_square_logger.logger.error(e, exc_info=True)
916
+ output_content = get_api_output_in_standard_format(
917
+ message=messages["GENERIC_500"],
918
+ log=str(e),
919
+ )
920
+ return JSONResponse(
921
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
922
+ content=output_content,
923
+ )
924
+
925
+
926
+ @global_object_square_logger.auto_logger()
927
+ def util_login_username_v0(username, password, app_id, assign_app_id_if_missing):
928
+ try:
929
+ """
930
+ validation
931
+ """
932
+ # validation for username
933
+ # check if user with username exists
934
+ local_list_response_user = global_object_square_database_helper.get_rows_v0(
935
+ database_name=global_string_database_name,
936
+ schema_name=global_string_schema_name,
937
+ table_name=User.__tablename__,
938
+ filters=FiltersV0(
939
+ root={User.user_username.name: FilterConditionsV0(eq=username)}
940
+ ),
941
+ )["data"]["main"]
942
+ if len(local_list_response_user) != 1:
943
+ output_content = get_api_output_in_standard_format(
944
+ message=messages["INCORRECT_USERNAME"],
945
+ log=f"incorrect username {username}",
946
+ )
947
+ raise HTTPException(
948
+ status_code=status.HTTP_400_BAD_REQUEST,
949
+ detail=output_content,
950
+ )
951
+ # check if user has auth provider as SELF
952
+ local_list_user_auth_provider_response = (
953
+ global_object_square_database_helper.get_rows_v0(
954
+ database_name=global_string_database_name,
955
+ schema_name=global_string_schema_name,
956
+ table_name=UserAuthProvider.__tablename__,
957
+ filters=FiltersV0(
958
+ root={
959
+ UserAuthProvider.user_id.name: FilterConditionsV0(
960
+ eq=local_list_response_user[0][User.user_id.name]
961
+ ),
962
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
963
+ eq=AuthProviderEnum.SELF.value
964
+ ),
965
+ }
966
+ ),
967
+ )["data"]["main"]
968
+ )
969
+ if len(local_list_user_auth_provider_response) != 1:
970
+ output_content = get_api_output_in_standard_format(
971
+ message=messages["INCORRECT_AUTH_PROVIDER"],
972
+ log=f"{username} not linked with {AuthProviderEnum.SELF.value} auth provider.",
973
+ )
974
+ raise HTTPException(
975
+ status_code=status.HTTP_400_BAD_REQUEST,
976
+ detail=output_content,
977
+ )
978
+ # check if user has credentials (might not be set in case of errors in registration.)
979
+ local_list_authentication_user_response = (
980
+ global_object_square_database_helper.get_rows_v0(
981
+ database_name=global_string_database_name,
982
+ schema_name=global_string_schema_name,
983
+ table_name=UserCredential.__tablename__,
984
+ filters=FiltersV0(
985
+ root={
986
+ UserCredential.user_id.name: FilterConditionsV0(
987
+ eq=local_list_response_user[0][User.user_id.name]
988
+ )
989
+ }
990
+ ),
991
+ )["data"]["main"]
992
+ )
993
+ if len(local_list_authentication_user_response) != 1:
994
+ output_content = get_api_output_in_standard_format(
995
+ message=messages["MALFORMED_USER"],
996
+ log=f"username: {username} does not have credentials set.",
997
+ )
998
+ raise HTTPException(
999
+ status_code=status.HTTP_400_BAD_REQUEST,
1000
+ detail=output_content,
1001
+ )
1002
+ # validate if app_id is assigned to user
1003
+ # this will also validate if app_id is valid
1004
+ local_dict_user = local_list_authentication_user_response[0]
1005
+ local_str_user_id = local_dict_user[UserCredential.user_id.name]
1006
+ local_list_user_app_response = global_object_square_database_helper.get_rows_v0(
1007
+ database_name=global_string_database_name,
1008
+ schema_name=global_string_schema_name,
1009
+ table_name=UserApp.__tablename__,
1010
+ filters=FiltersV0(
1011
+ root={
1012
+ UserApp.user_id.name: FilterConditionsV0(eq=local_str_user_id),
1013
+ UserApp.app_id.name: FilterConditionsV0(eq=app_id),
1014
+ }
1015
+ ),
1016
+ )["data"]["main"]
1017
+ if len(local_list_user_app_response) == 0:
1018
+ if assign_app_id_if_missing:
1019
+ try:
1020
+ global_object_square_database_helper.insert_rows_v0(
1021
+ database_name=global_string_database_name,
1022
+ schema_name=global_string_schema_name,
1023
+ table_name=UserApp.__tablename__,
1024
+ data=[
1025
+ {
1026
+ UserApp.user_id.name: local_str_user_id,
1027
+ UserApp.app_id.name: app_id,
1028
+ }
1029
+ ],
1030
+ )
1031
+ except HTTPError as he:
1032
+ output_content = get_api_output_in_standard_format(
1033
+ message=messages["GENERIC_400"],
1034
+ log=str(he),
1035
+ )
1036
+ raise HTTPException(
1037
+ status_code=he.response.status_code, detail=output_content
1038
+ )
1039
+ else:
1040
+ output_content = get_api_output_in_standard_format(
1041
+ message=messages["GENERIC_400"],
1042
+ log=f"user_id {local_str_user_id}({username}) not assigned to app {app_id}.",
1043
+ )
1044
+ raise HTTPException(
1045
+ status_code=status.HTTP_400_BAD_REQUEST,
1046
+ detail=output_content,
1047
+ )
1048
+
1049
+ # validate password
1050
+ if not (
1051
+ bcrypt.checkpw(
1052
+ password.encode("utf-8"),
1053
+ local_dict_user[
1054
+ UserCredential.user_credential_hashed_password.name
1055
+ ].encode("utf-8"),
1056
+ )
1057
+ ):
1058
+ output_content = get_api_output_in_standard_format(
1059
+ message=messages["INCORRECT_PASSWORD"],
1060
+ log=f"incorrect password for user_id {local_str_user_id}({username}).",
1061
+ )
1062
+ raise HTTPException(
1063
+ status_code=status.HTTP_400_BAD_REQUEST,
1064
+ detail=output_content,
1065
+ )
1066
+ """
1067
+ main process
1068
+ """
1069
+ # return new access token and refresh token
1070
+
1071
+ # create access token
1072
+ local_dict_access_token_payload = {
1073
+ "app_id": app_id,
1074
+ "user_id": local_str_user_id,
1075
+ "exp": datetime.now(timezone.utc)
1076
+ + timedelta(minutes=config_int_access_token_valid_minutes),
1077
+ }
1078
+ local_str_access_token = jwt.encode(
1079
+ local_dict_access_token_payload,
1080
+ config_str_secret_key_for_access_token,
1081
+ )
1082
+
1083
+ # create refresh token
1084
+ local_object_refresh_token_expiry_time = datetime.now(timezone.utc) + timedelta(
1085
+ minutes=config_int_refresh_token_valid_minutes
1086
+ )
1087
+
1088
+ local_dict_refresh_token_payload = {
1089
+ "app_id": app_id,
1090
+ "user_id": local_str_user_id,
1091
+ "exp": local_object_refresh_token_expiry_time,
1092
+ }
1093
+ local_str_refresh_token = jwt.encode(
1094
+ local_dict_refresh_token_payload,
1095
+ config_str_secret_key_for_refresh_token,
1096
+ )
1097
+ # entry in user session table
1098
+ global_object_square_database_helper.insert_rows_v0(
1099
+ data=[
1100
+ {
1101
+ UserSession.user_id.name: local_str_user_id,
1102
+ UserSession.app_id.name: app_id,
1103
+ UserSession.user_session_refresh_token.name: local_str_refresh_token,
1104
+ UserSession.user_session_expiry_time.name: local_object_refresh_token_expiry_time.strftime(
1105
+ "%Y-%m-%d %H:%M:%S.%f+00"
1106
+ ),
1107
+ }
1108
+ ],
1109
+ database_name=global_string_database_name,
1110
+ schema_name=global_string_schema_name,
1111
+ table_name=UserSession.__tablename__,
1112
+ )
1113
+ """
1114
+ return value
1115
+ """
1116
+ output_content = get_api_output_in_standard_format(
1117
+ data={
1118
+ "main": {
1119
+ "user_id": local_str_user_id,
1120
+ "access_token": local_str_access_token,
1121
+ "refresh_token": local_str_refresh_token,
1122
+ "refresh_token_expiry_time": local_object_refresh_token_expiry_time.isoformat(),
1123
+ }
1124
+ },
1125
+ message=messages["LOGIN_SUCCESSFUL"],
1126
+ )
1127
+ return JSONResponse(
1128
+ status_code=status.HTTP_200_OK,
1129
+ content=output_content,
1130
+ )
1131
+ except HTTPException as http_exception:
1132
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
1133
+ return JSONResponse(
1134
+ status_code=http_exception.status_code, content=http_exception.detail
1135
+ )
1136
+ except Exception as e:
1137
+ """
1138
+ rollback logic
1139
+ """
1140
+ global_object_square_logger.logger.error(e, exc_info=True)
1141
+ output_content = get_api_output_in_standard_format(
1142
+ message=messages["GENERIC_500"],
1143
+ log=str(e),
1144
+ )
1145
+ return JSONResponse(
1146
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1147
+ )
1148
+
1149
+
1150
+ @global_object_square_logger.auto_logger()
1151
+ def util_generate_access_token_v0(refresh_token):
1152
+ try:
1153
+ """
1154
+ validation
1155
+ """
1156
+ # validate refresh token
1157
+ # validating if a session refresh token exists in the database.
1158
+ local_list_user_session_response = (
1159
+ global_object_square_database_helper.get_rows_v0(
1160
+ database_name=global_string_database_name,
1161
+ schema_name=global_string_schema_name,
1162
+ table_name=UserSession.__tablename__,
1163
+ filters=FiltersV0(
1164
+ root={
1165
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
1166
+ eq=refresh_token
1167
+ ),
1168
+ }
1169
+ ),
1170
+ )["data"]["main"]
1171
+ )
1172
+
1173
+ if len(local_list_user_session_response) != 1:
1174
+ output_content = get_api_output_in_standard_format(
1175
+ message=messages["INCORRECT_REFRESH_TOKEN"],
1176
+ log=f"incorrect refresh token: {refresh_token}.",
1177
+ )
1178
+ raise HTTPException(
1179
+ status_code=status.HTTP_400_BAD_REQUEST,
1180
+ detail=output_content,
1181
+ )
1182
+ # validating if the refresh token is valid, active and of the same user.
1183
+ try:
1184
+ local_dict_refresh_token_payload = get_jwt_payload(
1185
+ refresh_token, config_str_secret_key_for_refresh_token
1186
+ )
1187
+ except Exception as error:
1188
+ output_content = get_api_output_in_standard_format(
1189
+ message=messages["INCORRECT_REFRESH_TOKEN"], log=str(error)
1190
+ )
1191
+ raise HTTPException(
1192
+ status_code=status.HTTP_400_BAD_REQUEST,
1193
+ detail=output_content,
1194
+ )
1195
+ """
1196
+ main process
1197
+ """
1198
+ # create and send access token
1199
+ local_dict_access_token_payload = {
1200
+ "app_id": local_dict_refresh_token_payload["app_id"],
1201
+ "user_id": local_dict_refresh_token_payload["user_id"],
1202
+ "exp": datetime.now(timezone.utc)
1203
+ + timedelta(minutes=config_int_access_token_valid_minutes),
1204
+ }
1205
+ local_str_access_token = jwt.encode(
1206
+ local_dict_access_token_payload, config_str_secret_key_for_access_token
1207
+ )
1208
+ """
1209
+ return value
1210
+ """
1211
+ output_content = get_api_output_in_standard_format(
1212
+ data={"main": {"access_token": local_str_access_token}},
1213
+ message=messages["GENERIC_CREATION_SUCCESSFUL"],
1214
+ )
1215
+ return JSONResponse(
1216
+ status_code=status.HTTP_200_OK,
1217
+ content=output_content,
1218
+ )
1219
+ except HTTPException as http_exception:
1220
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
1221
+ return JSONResponse(
1222
+ status_code=http_exception.status_code, content=http_exception.detail
1223
+ )
1224
+ except Exception as e:
1225
+ """
1226
+ rollback logic
1227
+ """
1228
+ global_object_square_logger.logger.error(e, exc_info=True)
1229
+ output_content = get_api_output_in_standard_format(
1230
+ message=messages["GENERIC_500"],
1231
+ log=str(e),
1232
+ )
1233
+ return JSONResponse(
1234
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1235
+ )
1236
+
1237
+
1238
+ @global_object_square_logger.auto_logger()
1239
+ def util_logout_v0(refresh_token):
1240
+ try:
1241
+ """
1242
+ validation
1243
+ """
1244
+ # validate refresh token
1245
+ # validating if a session refresh token exists in the database.
1246
+ local_list_user_session_response = (
1247
+ global_object_square_database_helper.get_rows_v0(
1248
+ database_name=global_string_database_name,
1249
+ schema_name=global_string_schema_name,
1250
+ table_name=UserSession.__tablename__,
1251
+ filters=FiltersV0(
1252
+ root={
1253
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
1254
+ eq=refresh_token
1255
+ ),
1256
+ }
1257
+ ),
1258
+ )["data"]["main"]
1259
+ )
1260
+
1261
+ if len(local_list_user_session_response) != 1:
1262
+ output_content = get_api_output_in_standard_format(
1263
+ message=messages["INCORRECT_REFRESH_TOKEN"],
1264
+ log=f"incorrect refresh token: {refresh_token}.",
1265
+ )
1266
+ raise HTTPException(
1267
+ status_code=status.HTTP_400_BAD_REQUEST,
1268
+ detail=output_content,
1269
+ )
1270
+ # validating if the refresh token is valid, active and of the same user.
1271
+ try:
1272
+ _ = get_jwt_payload(refresh_token, config_str_secret_key_for_refresh_token)
1273
+ except Exception as error:
1274
+ output_content = get_api_output_in_standard_format(
1275
+ message=messages["INCORRECT_REFRESH_TOKEN"],
1276
+ log=str(error),
1277
+ )
1278
+ raise HTTPException(
1279
+ status_code=status.HTTP_400_BAD_REQUEST,
1280
+ detail=output_content,
1281
+ )
1282
+ # ======================================================================================
1283
+ # NOTE: if refresh token has expired no need to delete it during this call
1284
+ # ======================================================================================
1285
+ """
1286
+ main process
1287
+ """
1288
+ # delete session for user
1289
+ global_object_square_database_helper.delete_rows_v0(
1290
+ database_name=global_string_database_name,
1291
+ schema_name=global_string_schema_name,
1292
+ table_name=UserSession.__tablename__,
1293
+ filters=FiltersV0(
1294
+ root={
1295
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
1296
+ eq=refresh_token
1297
+ ),
1298
+ }
1299
+ ),
1300
+ )
1301
+ """
1302
+ return value
1303
+ """
1304
+ output_content = get_api_output_in_standard_format(
1305
+ message=messages["LOGOUT_SUCCESSFUL"],
1306
+ )
1307
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
1308
+ except HTTPException as http_exception:
1309
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
1310
+ return JSONResponse(
1311
+ status_code=http_exception.status_code, content=http_exception.detail
1312
+ )
1313
+ except Exception as e:
1314
+ """
1315
+ rollback logic
1316
+ """
1317
+ global_object_square_logger.logger.error(e, exc_info=True)
1318
+ output_content = get_api_output_in_standard_format(
1319
+ message=messages["GENERIC_500"],
1320
+ log=str(e),
1321
+ )
1322
+ return JSONResponse(
1323
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1324
+ )
1325
+
1326
+
1327
+ @global_object_square_logger.auto_logger()
1328
+ def util_logout_apps_v0(access_token, app_ids):
1329
+ try:
1330
+ """
1331
+ validation
1332
+ """
1333
+ try:
1334
+ local_dict_access_token_payload = get_jwt_payload(
1335
+ access_token, config_str_secret_key_for_access_token
1336
+ )
1337
+ except Exception as error:
1338
+ output_content = get_api_output_in_standard_format(
1339
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
1340
+ )
1341
+ raise HTTPException(
1342
+ status_code=status.HTTP_400_BAD_REQUEST,
1343
+ detail=output_content,
1344
+ )
1345
+ user_id = local_dict_access_token_payload["user_id"]
1346
+ # validate app_ids
1347
+ app_ids = list(set(app_ids))
1348
+ local_list_response_user_app = global_object_square_database_helper.get_rows_v0(
1349
+ database_name=global_string_database_name,
1350
+ schema_name=global_string_schema_name,
1351
+ table_name=UserApp.__tablename__,
1352
+ filters=FiltersV0(
1353
+ root={
1354
+ UserApp.user_id.name: FilterConditionsV0(eq=user_id),
1355
+ }
1356
+ ),
1357
+ columns=[UserApp.app_id.name],
1358
+ )["data"]["main"]
1359
+ local_list_user_app_ids = [
1360
+ x[UserApp.app_id.name] for x in local_list_response_user_app
1361
+ ]
1362
+ local_list_invalid_app_ids = [
1363
+ x for x in app_ids if x not in local_list_user_app_ids
1364
+ ]
1365
+ if len(local_list_invalid_app_ids) > 0:
1366
+ output_content = get_api_output_in_standard_format(
1367
+ message=messages["GENERIC_400"],
1368
+ log=f"invalid app_ids: {local_list_invalid_app_ids}.",
1369
+ )
1370
+ raise HTTPException(
1371
+ status_code=status.HTTP_400_BAD_REQUEST,
1372
+ detail=output_content,
1373
+ )
1374
+ """
1375
+ main process
1376
+ """
1377
+ # delete session for user
1378
+ global_object_square_database_helper.delete_rows_v0(
1379
+ database_name=global_string_database_name,
1380
+ schema_name=global_string_schema_name,
1381
+ table_name=UserSession.__tablename__,
1382
+ filters=FiltersV0(
1383
+ root={
1384
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
1385
+ UserSession.app_id.name: FilterConditionsV0(in_=app_ids),
1386
+ }
1387
+ ),
1388
+ )
1389
+ """
1390
+ return value
1391
+ """
1392
+ output_content = get_api_output_in_standard_format(
1393
+ message=messages["LOGOUT_SUCCESSFUL"],
1394
+ )
1395
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
1396
+ except HTTPException as http_exception:
1397
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
1398
+ return JSONResponse(
1399
+ status_code=http_exception.status_code, content=http_exception.detail
1400
+ )
1401
+ except Exception as e:
1402
+ """
1403
+ rollback logic
1404
+ """
1405
+ global_object_square_logger.logger.error(e, exc_info=True)
1406
+ output_content = get_api_output_in_standard_format(
1407
+ message=messages["GENERIC_500"],
1408
+ log=str(e),
1409
+ )
1410
+ return JSONResponse(
1411
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1412
+ )
1413
+
1414
+
1415
+ @global_object_square_logger.auto_logger()
1416
+ def util_logout_all_v0(access_token):
1417
+ try:
1418
+ """
1419
+ validation
1420
+ """
1421
+ try:
1422
+ local_dict_access_token_payload = get_jwt_payload(
1423
+ access_token, config_str_secret_key_for_access_token
1424
+ )
1425
+ except Exception as error:
1426
+ output_content = get_api_output_in_standard_format(
1427
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
1428
+ )
1429
+ raise HTTPException(
1430
+ status_code=status.HTTP_400_BAD_REQUEST,
1431
+ detail=output_content,
1432
+ )
1433
+ user_id = local_dict_access_token_payload["user_id"]
1434
+
1435
+ """
1436
+ main process
1437
+ """
1438
+ # delete session for user
1439
+ global_object_square_database_helper.delete_rows_v0(
1440
+ database_name=global_string_database_name,
1441
+ schema_name=global_string_schema_name,
1442
+ table_name=UserSession.__tablename__,
1443
+ filters=FiltersV0(
1444
+ root={
1445
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
1446
+ }
1447
+ ),
1448
+ )
1449
+ """
1450
+ return value
1451
+ """
1452
+ output_content = get_api_output_in_standard_format(
1453
+ message=messages["LOGOUT_SUCCESSFUL"],
1454
+ )
1455
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
1456
+ except HTTPException as http_exception:
1457
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
1458
+ return JSONResponse(
1459
+ status_code=http_exception.status_code, content=http_exception.detail
1460
+ )
1461
+ except Exception as e:
1462
+ """
1463
+ rollback logic
1464
+ """
1465
+ global_object_square_logger.logger.error(e, exc_info=True)
1466
+ output_content = get_api_output_in_standard_format(
1467
+ message=messages["GENERIC_500"],
1468
+ log=str(e),
1469
+ )
1470
+ return JSONResponse(
1471
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1472
+ )
1473
+
1474
+
1475
+ @global_object_square_logger.auto_logger()
1476
+ def util_update_username_v0(new_username, access_token):
1477
+ try:
1478
+ """
1479
+ validation
1480
+ """
1481
+ # validate access token
1482
+ try:
1483
+ local_dict_access_token_payload = get_jwt_payload(
1484
+ access_token, config_str_secret_key_for_access_token
1485
+ )
1486
+ except Exception as error:
1487
+ output_content = get_api_output_in_standard_format(
1488
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
1489
+ )
1490
+ raise HTTPException(
1491
+ status_code=status.HTTP_400_BAD_REQUEST,
1492
+ detail=output_content,
1493
+ )
1494
+ user_id = local_dict_access_token_payload["user_id"]
1495
+
1496
+ # validation for username
1497
+ new_username = new_username.lower()
1498
+ username_pattern = re.compile(r"^[a-z0-9._-]{2,20}$")
1499
+ if not username_pattern.match(new_username):
1500
+ output_content = get_api_output_in_standard_format(
1501
+ message=messages["USERNAME_INVALID"],
1502
+ log=f"username '{new_username}' is invalid. it must start and end with a letter, "
1503
+ f"contain only lowercase letters, numbers, underscores, or hyphens, "
1504
+ f"and not have consecutive separators.",
1505
+ )
1506
+ raise HTTPException(
1507
+ status_code=status.HTTP_400_BAD_REQUEST,
1508
+ detail=output_content,
1509
+ )
1510
+
1511
+ # validate user_id
1512
+ local_list_user_response = global_object_square_database_helper.get_rows_v0(
1513
+ database_name=global_string_database_name,
1514
+ schema_name=global_string_schema_name,
1515
+ table_name=User.__tablename__,
1516
+ filters=FiltersV0(
1517
+ root={
1518
+ User.user_id.name: FilterConditionsV0(eq=user_id),
1519
+ }
1520
+ ),
1521
+ )["data"]["main"]
1522
+
1523
+ if len(local_list_user_response) != 1:
1524
+ output_content = get_api_output_in_standard_format(
1525
+ message=messages["INCORRECT_USER_ID"],
1526
+ log=f"incorrect user_id: {user_id}.",
1527
+ )
1528
+ raise HTTPException(
1529
+ status_code=status.HTTP_400_BAD_REQUEST,
1530
+ detail=output_content,
1531
+ )
1532
+
1533
+ # validate new username
1534
+ local_list_user_credentials_response = (
1535
+ global_object_square_database_helper.get_rows_v0(
1536
+ database_name=global_string_database_name,
1537
+ schema_name=global_string_schema_name,
1538
+ table_name=User.__tablename__,
1539
+ filters=FiltersV0(
1540
+ root={
1541
+ User.user_username.name: FilterConditionsV0(eq=new_username),
1542
+ }
1543
+ ),
1544
+ )["data"]["main"]
1545
+ )
1546
+ if len(local_list_user_credentials_response) != 0:
1547
+ output_content = get_api_output_in_standard_format(
1548
+ message=messages["USERNAME_ALREADY_EXISTS"],
1549
+ log=f"{new_username} is taken.",
1550
+ )
1551
+ raise HTTPException(
1552
+ status_code=status.HTTP_400_BAD_REQUEST,
1553
+ detail=output_content,
1554
+ )
1555
+ """
1556
+ main process
1557
+ """
1558
+ # edit the username
1559
+ global_object_square_database_helper.edit_rows_v0(
1560
+ database_name=global_string_database_name,
1561
+ schema_name=global_string_schema_name,
1562
+ table_name=User.__tablename__,
1563
+ filters=FiltersV0(
1564
+ root={
1565
+ User.user_id.name: FilterConditionsV0(eq=user_id),
1566
+ }
1567
+ ),
1568
+ data={
1569
+ User.user_username.name: new_username,
1570
+ },
1571
+ )
1572
+ """
1573
+ return value
1574
+ """
1575
+ output_content = get_api_output_in_standard_format(
1576
+ data={"main": {"user_id": user_id, "username": new_username}},
1577
+ message=messages["GENERIC_UPDATE_SUCCESSFUL"],
1578
+ )
1579
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
1580
+ except HTTPException as http_exception:
1581
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
1582
+ return JSONResponse(
1583
+ status_code=http_exception.status_code, content=http_exception.detail
1584
+ )
1585
+ except Exception as e:
1586
+ """
1587
+ rollback logic
1588
+ """
1589
+ global_object_square_logger.logger.error(e, exc_info=True)
1590
+ output_content = get_api_output_in_standard_format(
1591
+ message=messages["GENERIC_500"],
1592
+ log=str(e),
1593
+ )
1594
+ return JSONResponse(
1595
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1596
+ )
1597
+
1598
+
1599
+ @global_object_square_logger.auto_logger()
1600
+ def util_delete_user_v0(access_token, password):
1601
+ try:
1602
+ """
1603
+ validation
1604
+ """
1605
+ # validate access token
1606
+ try:
1607
+ local_dict_access_token_payload = get_jwt_payload(
1608
+ access_token, config_str_secret_key_for_access_token
1609
+ )
1610
+ except Exception as error:
1611
+ output_content = get_api_output_in_standard_format(
1612
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
1613
+ )
1614
+ raise HTTPException(
1615
+ status_code=status.HTTP_400_BAD_REQUEST,
1616
+ detail=output_content,
1617
+ )
1618
+ user_id = local_dict_access_token_payload["user_id"]
1619
+
1620
+ # validate user_id
1621
+ local_list_authentication_user_response = (
1622
+ global_object_square_database_helper.get_rows_v0(
1623
+ database_name=global_string_database_name,
1624
+ schema_name=global_string_schema_name,
1625
+ table_name=UserCredential.__tablename__,
1626
+ filters=FiltersV0(
1627
+ root={UserCredential.user_id.name: FilterConditionsV0(eq=user_id)}
1628
+ ),
1629
+ )["data"]["main"]
1630
+ )
1631
+ if len(local_list_authentication_user_response) != 1:
1632
+ output_content = get_api_output_in_standard_format(
1633
+ message=messages["INCORRECT_USER_ID"],
1634
+ log=f"incorrect user_id: {user_id}.",
1635
+ )
1636
+ raise HTTPException(
1637
+ status_code=status.HTTP_400_BAD_REQUEST,
1638
+ detail=output_content,
1639
+ )
1640
+
1641
+ # validate password
1642
+ local_dict_user = local_list_authentication_user_response[0]
1643
+ if not (
1644
+ bcrypt.checkpw(
1645
+ password.encode("utf-8"),
1646
+ local_dict_user[
1647
+ UserCredential.user_credential_hashed_password.name
1648
+ ].encode("utf-8"),
1649
+ )
1650
+ ):
1651
+ output_content = get_api_output_in_standard_format(
1652
+ message=messages["INCORRECT_PASSWORD"],
1653
+ log=f"incorrect password for user_id {user_id}.",
1654
+ )
1655
+ raise HTTPException(
1656
+ status_code=status.HTTP_400_BAD_REQUEST,
1657
+ detail=output_content,
1658
+ )
1659
+ """
1660
+ main process
1661
+ """
1662
+ # fetch user profile photo storage token
1663
+ user_profile_storage_token = global_object_square_database_helper.get_rows_v0(
1664
+ database_name=global_string_database_name,
1665
+ schema_name=global_string_schema_name,
1666
+ table_name=UserProfile.__tablename__,
1667
+ filters=FiltersV0(
1668
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
1669
+ ),
1670
+ columns=[UserProfile.user_profile_photo_storage_token.name],
1671
+ )["data"]["main"][0][UserProfile.user_profile_photo_storage_token.name]
1672
+
1673
+ # delete the user.
1674
+ global_object_square_database_helper.delete_rows_v0(
1675
+ database_name=global_string_database_name,
1676
+ schema_name=global_string_schema_name,
1677
+ table_name=User.__tablename__,
1678
+ filters=FiltersV0(
1679
+ root={
1680
+ User.user_id.name: FilterConditionsV0(eq=user_id),
1681
+ }
1682
+ ),
1683
+ )
1684
+ # delete profile photo if exists
1685
+ if user_profile_storage_token:
1686
+ try:
1687
+ global_object_square_file_store_helper.delete_file_v0(
1688
+ list_file_storage_token=[user_profile_storage_token]
1689
+ )
1690
+ except HTTPError as he:
1691
+ global_object_square_logger.warning(
1692
+ f"Failed to delete user profile photo with storage token {user_profile_storage_token}. "
1693
+ f"Error: {he.response.text}",
1694
+ exc_info=True,
1695
+ )
1696
+ """
1697
+ return value
1698
+ """
1699
+ output_content = get_api_output_in_standard_format(
1700
+ message=messages["GENERIC_DELETE_SUCCESSFUL"],
1701
+ log=f"user_id: {user_id} deleted successfully.",
1702
+ )
1703
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
1704
+ except HTTPException as http_exception:
1705
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
1706
+ return JSONResponse(
1707
+ status_code=http_exception.status_code, content=http_exception.detail
1708
+ )
1709
+ except Exception as e:
1710
+ """
1711
+ rollback logic
1712
+ """
1713
+ global_object_square_logger.logger.error(e, exc_info=True)
1714
+ output_content = get_api_output_in_standard_format(
1715
+ message=messages["GENERIC_500"],
1716
+ log=str(e),
1717
+ )
1718
+ return JSONResponse(
1719
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1720
+ )
1721
+
1722
+
1723
+ @global_object_square_logger.auto_logger()
1724
+ def util_update_password_v0(
1725
+ access_token,
1726
+ old_password,
1727
+ new_password,
1728
+ logout_other_sessions,
1729
+ preserve_session_refresh_token,
1730
+ ):
1731
+ try:
1732
+ """
1733
+ validation
1734
+ """
1735
+ # validate access token
1736
+ try:
1737
+ local_dict_access_token_payload = get_jwt_payload(
1738
+ access_token, config_str_secret_key_for_access_token
1739
+ )
1740
+ except Exception as error:
1741
+ output_content = get_api_output_in_standard_format(
1742
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
1743
+ )
1744
+ raise HTTPException(
1745
+ status_code=status.HTTP_400_BAD_REQUEST,
1746
+ detail=output_content,
1747
+ )
1748
+ user_id = local_dict_access_token_payload["user_id"]
1749
+
1750
+ # validate user_id
1751
+ local_list_authentication_user_response = (
1752
+ global_object_square_database_helper.get_rows_v0(
1753
+ database_name=global_string_database_name,
1754
+ schema_name=global_string_schema_name,
1755
+ table_name=UserCredential.__tablename__,
1756
+ filters=FiltersV0(
1757
+ root={UserCredential.user_id.name: FilterConditionsV0(eq=user_id)}
1758
+ ),
1759
+ )["data"]["main"]
1760
+ )
1761
+ if len(local_list_authentication_user_response) != 1:
1762
+ output_content = get_api_output_in_standard_format(
1763
+ message=messages["INCORRECT_USER_ID"],
1764
+ log=f"incorrect user_id: {user_id}.",
1765
+ )
1766
+ raise HTTPException(
1767
+ status_code=status.HTTP_400_BAD_REQUEST,
1768
+ detail=output_content,
1769
+ )
1770
+ # check if user has SELF auth provider
1771
+ local_list_response_user_auth_provider = (
1772
+ global_object_square_database_helper.get_rows_v0(
1773
+ database_name=global_string_database_name,
1774
+ schema_name=global_string_schema_name,
1775
+ table_name=UserAuthProvider.__tablename__,
1776
+ filters=FiltersV0(
1777
+ root={
1778
+ UserAuthProvider.user_id.name: FilterConditionsV0(eq=user_id),
1779
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
1780
+ eq=AuthProviderEnum.SELF.value
1781
+ ),
1782
+ }
1783
+ ),
1784
+ )["data"]["main"]
1785
+ )
1786
+ if len(local_list_response_user_auth_provider) != 1:
1787
+ output_content = get_api_output_in_standard_format(
1788
+ message=messages["INCORRECT_AUTH_PROVIDER"],
1789
+ log=f"user_id: {user_id} does not have {AuthProviderEnum.SELF.value} auth provider.",
1790
+ )
1791
+ raise HTTPException(
1792
+ status_code=status.HTTP_400_BAD_REQUEST,
1793
+ detail=output_content,
1794
+ )
1795
+ # check if user has credentials (might not be set in case of errors in registration.)
1796
+ local_list_response_user = global_object_square_database_helper.get_rows_v0(
1797
+ database_name=global_string_database_name,
1798
+ schema_name=global_string_schema_name,
1799
+ table_name=User.__tablename__,
1800
+ filters=FiltersV0(root={User.user_id.name: FilterConditionsV0(eq=user_id)}),
1801
+ )["data"]["main"]
1802
+ if len(local_list_response_user) != 1:
1803
+ output_content = get_api_output_in_standard_format(
1804
+ message=messages["MALFORMED_USER"],
1805
+ log=f"user_id: {user_id} does not have credentials.",
1806
+ )
1807
+ raise HTTPException(
1808
+ status_code=status.HTTP_400_BAD_REQUEST,
1809
+ detail=output_content,
1810
+ )
1811
+ # validate password
1812
+ local_dict_user = local_list_authentication_user_response[0]
1813
+ if not (
1814
+ bcrypt.checkpw(
1815
+ old_password.encode("utf-8"),
1816
+ local_dict_user[
1817
+ UserCredential.user_credential_hashed_password.name
1818
+ ].encode("utf-8"),
1819
+ )
1820
+ ):
1821
+ output_content = get_api_output_in_standard_format(
1822
+ message=messages["INCORRECT_PASSWORD"],
1823
+ log=f"incorrect password for user_id {user_id}.",
1824
+ )
1825
+ raise HTTPException(
1826
+ status_code=status.HTTP_400_BAD_REQUEST,
1827
+ detail=output_content,
1828
+ )
1829
+ # check if provided refresh token is valid
1830
+ if preserve_session_refresh_token:
1831
+ local_dict_token_payload = get_jwt_payload(
1832
+ preserve_session_refresh_token, config_str_secret_key_for_refresh_token
1833
+ )
1834
+ local_list_response_user_session = global_object_square_database_helper.get_rows_v0(
1835
+ database_name=global_string_database_name,
1836
+ schema_name=global_string_schema_name,
1837
+ table_name=UserSession.__tablename__,
1838
+ filters=FiltersV0(
1839
+ root={
1840
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
1841
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
1842
+ eq=preserve_session_refresh_token
1843
+ ),
1844
+ }
1845
+ ),
1846
+ )[
1847
+ "data"
1848
+ ][
1849
+ "main"
1850
+ ]
1851
+ if len(local_list_response_user_session) != 1:
1852
+ output_content = get_api_output_in_standard_format(
1853
+ message=messages["INCORRECT_REFRESH_TOKEN"],
1854
+ log=f"incorrect refresh token: {preserve_session_refresh_token}.",
1855
+ )
1856
+ raise HTTPException(
1857
+ status_code=status.HTTP_400_BAD_REQUEST,
1858
+ detail=output_content,
1859
+ )
1860
+ """
1861
+ main process
1862
+ """
1863
+ # update the password
1864
+ local_str_hashed_password = bcrypt.hashpw(
1865
+ new_password.encode("utf-8"), bcrypt.gensalt()
1866
+ ).decode("utf-8")
1867
+ global_object_square_database_helper.edit_rows_v0(
1868
+ database_name=global_string_database_name,
1869
+ schema_name=global_string_schema_name,
1870
+ table_name=UserCredential.__tablename__,
1871
+ filters=FiltersV0(
1872
+ root={
1873
+ UserCredential.user_id.name: FilterConditionsV0(eq=user_id),
1874
+ }
1875
+ ),
1876
+ data={
1877
+ UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
1878
+ },
1879
+ )
1880
+ if logout_other_sessions:
1881
+ if preserve_session_refresh_token:
1882
+ # delete all sessions for user except the one with the given refresh token
1883
+ global_object_square_database_helper.delete_rows_v0(
1884
+ database_name=global_string_database_name,
1885
+ schema_name=global_string_schema_name,
1886
+ table_name=UserSession.__tablename__,
1887
+ filters=FiltersV0(
1888
+ root={
1889
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
1890
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
1891
+ ne=preserve_session_refresh_token
1892
+ ),
1893
+ }
1894
+ ),
1895
+ )
1896
+ else:
1897
+ # delete all sessions for user
1898
+ global_object_square_database_helper.delete_rows_v0(
1899
+ database_name=global_string_database_name,
1900
+ schema_name=global_string_schema_name,
1901
+ table_name=UserSession.__tablename__,
1902
+ filters=FiltersV0(
1903
+ root={
1904
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
1905
+ }
1906
+ ),
1907
+ )
1908
+ """
1909
+ return value
1910
+ """
1911
+ output_content = get_api_output_in_standard_format(
1912
+ message=messages["GENERIC_UPDATE_SUCCESSFUL"],
1913
+ log=f"password for user_id: {user_id} updated successfully.",
1914
+ )
1915
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
1916
+ except HTTPException as http_exception:
1917
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
1918
+ return JSONResponse(
1919
+ status_code=http_exception.status_code, content=http_exception.detail
1920
+ )
1921
+ except Exception as e:
1922
+ """
1923
+ rollback logic
1924
+ """
1925
+ global_object_square_logger.logger.error(e, exc_info=True)
1926
+ output_content = get_api_output_in_standard_format(
1927
+ message=messages["GENERIC_500"],
1928
+ log=str(e),
1929
+ )
1930
+ return JSONResponse(
1931
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1932
+ )
1933
+
1934
+
1935
+ @global_object_square_logger.auto_logger()
1936
+ def util_validate_and_get_payload_from_token_v0(app_id, token, token_type):
1937
+ try:
1938
+ """
1939
+ validation
1940
+ """
1941
+ # validate token
1942
+ try:
1943
+ local_dict_token_payload = None
1944
+ if token_type == TokenType.access_token:
1945
+ local_dict_token_payload = get_jwt_payload(
1946
+ token, config_str_secret_key_for_access_token
1947
+ )
1948
+ elif token_type == TokenType.refresh_token:
1949
+ local_dict_token_payload = get_jwt_payload(
1950
+ token, config_str_secret_key_for_refresh_token
1951
+ )
1952
+ local_list_response_user_session = global_object_square_database_helper.get_rows_v0(
1953
+ database_name=global_string_database_name,
1954
+ schema_name=global_string_schema_name,
1955
+ table_name=UserSession.__tablename__,
1956
+ filters=FiltersV0(
1957
+ root={
1958
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
1959
+ eq=token
1960
+ ),
1961
+ UserSession.user_id.name: FilterConditionsV0(
1962
+ eq=local_dict_token_payload["user_id"]
1963
+ ),
1964
+ }
1965
+ ),
1966
+ )[
1967
+ "data"
1968
+ ][
1969
+ "main"
1970
+ ]
1971
+ if len(local_list_response_user_session) != 1:
1972
+ output_content = get_api_output_in_standard_format(
1973
+ message=messages["INCORRECT_REFRESH_TOKEN"],
1974
+ log="refresh token valid but not present in database.",
1975
+ )
1976
+ raise HTTPException(
1977
+ status_code=status.HTTP_400_BAD_REQUEST,
1978
+ detail=output_content,
1979
+ )
1980
+ if local_dict_token_payload["app_id"] != app_id:
1981
+ output_content = get_api_output_in_standard_format(
1982
+ message=messages["GENERIC_400"],
1983
+ log=f"app_id: {app_id} does not match with token app_id: {local_dict_token_payload['app_id']}.",
1984
+ )
1985
+ raise HTTPException(
1986
+ status_code=status.HTTP_400_BAD_REQUEST,
1987
+ detail=output_content,
1988
+ )
1989
+ except HTTPException as http_exception:
1990
+ raise
1991
+ except Exception as error:
1992
+ output_content = None
1993
+ if token_type == TokenType.access_token:
1994
+ output_content = get_api_output_in_standard_format(
1995
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
1996
+ )
1997
+ elif token_type == TokenType.refresh_token:
1998
+ output_content = get_api_output_in_standard_format(
1999
+ message=messages["INCORRECT_REFRESH_TOKEN"], log=str(error)
2000
+ )
2001
+
2002
+ raise HTTPException(
2003
+ status_code=status.HTTP_400_BAD_REQUEST,
2004
+ detail=output_content,
2005
+ )
2006
+
2007
+ """
2008
+ main process
2009
+ """
2010
+ # pass
2011
+ """
2012
+ return value
2013
+ """
2014
+ output_content = get_api_output_in_standard_format(
2015
+ message=messages["GENERIC_READ_SUCCESSFUL"],
2016
+ data={"main": local_dict_token_payload},
2017
+ )
2018
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
2019
+ except HTTPException as http_exception:
2020
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
2021
+ return JSONResponse(
2022
+ status_code=http_exception.status_code, content=http_exception.detail
2023
+ )
2024
+ except Exception as e:
2025
+ """
2026
+ rollback logic
2027
+ """
2028
+ global_object_square_logger.logger.error(e, exc_info=True)
2029
+ output_content = get_api_output_in_standard_format(
2030
+ message=messages["GENERIC_500"],
2031
+ log=str(e),
2032
+ )
2033
+ return JSONResponse(
2034
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
2035
+ )
2036
+
2037
+
2038
+ @global_object_square_logger.auto_logger()
2039
+ def util_update_user_recovery_methods_v0(
2040
+ access_token, recovery_methods_to_add, recovery_methods_to_remove
2041
+ ):
2042
+ if recovery_methods_to_add is None:
2043
+ recovery_methods_to_add = []
2044
+ if recovery_methods_to_remove is None:
2045
+ recovery_methods_to_remove = []
2046
+ try:
2047
+
2048
+ """
2049
+ validation
2050
+ """
2051
+ # validate access token
2052
+ try:
2053
+ local_dict_access_token_payload = get_jwt_payload(
2054
+ access_token, config_str_secret_key_for_access_token
2055
+ )
2056
+ except Exception as error:
2057
+ output_content = get_api_output_in_standard_format(
2058
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
2059
+ )
2060
+ raise HTTPException(
2061
+ status_code=status.HTTP_400_BAD_REQUEST,
2062
+ detail=output_content,
2063
+ )
2064
+ user_id = local_dict_access_token_payload["user_id"]
2065
+
2066
+ recovery_methods_to_add = list(set(x.value for x in recovery_methods_to_add))
2067
+ recovery_methods_to_remove = list(
2068
+ set(x.value for x in recovery_methods_to_remove)
2069
+ )
2070
+
2071
+ # check if recovery_methods_to_add and recovery_methods_to_remove don't have common ids.
2072
+ local_list_common_recovery_methods = set(recovery_methods_to_add) & set(
2073
+ recovery_methods_to_remove
2074
+ )
2075
+ if len(local_list_common_recovery_methods) > 0:
2076
+ output_content = get_api_output_in_standard_format(
2077
+ message=messages["GENERIC_400"],
2078
+ log=f"invalid recovery_methods: {list(local_list_common_recovery_methods)}, present in both add list and remove list.",
2079
+ )
2080
+ raise HTTPException(
2081
+ status_code=status.HTTP_400_BAD_REQUEST,
2082
+ detail=output_content,
2083
+ )
2084
+ # check if user's email is verified in user profile.
2085
+ # maybe too harsh to reject the request entirely,
2086
+ # but for practical purposes this api call should be used for 1 recovery method at a time, so it's not too bad.
2087
+ if RecoveryMethodEnum.EMAIL.value in recovery_methods_to_add:
2088
+ local_list_response_user_profile = (
2089
+ global_object_square_database_helper.get_rows_v0(
2090
+ database_name=global_string_database_name,
2091
+ schema_name=global_string_schema_name,
2092
+ table_name=UserProfile.__tablename__,
2093
+ filters=FiltersV0(
2094
+ root={
2095
+ UserProfile.user_id.name: FilterConditionsV0(eq=user_id),
2096
+ }
2097
+ ),
2098
+ )["data"]["main"]
2099
+ )
2100
+ if len(local_list_response_user_profile) != 1:
2101
+ # maybe this should raise 500 as this error will not occur if code runs correctly.
2102
+ output_content = get_api_output_in_standard_format(
2103
+ message=messages["GENERIC_400"],
2104
+ log=f"user_id: {user_id} does not have a profile.",
2105
+ )
2106
+ raise HTTPException(
2107
+ status_code=status.HTTP_400_BAD_REQUEST,
2108
+ detail=output_content,
2109
+ )
2110
+ local_dict_user_profile = local_list_response_user_profile[0]
2111
+ if not local_dict_user_profile[
2112
+ UserProfile.user_profile_email_verified.name
2113
+ ]:
2114
+ output_content = get_api_output_in_standard_format(
2115
+ message=messages["EMAIL_NOT_VERIFIED"],
2116
+ log=f"user_id: {user_id} does not have email verified.",
2117
+ )
2118
+ raise HTTPException(
2119
+ status_code=status.HTTP_400_BAD_REQUEST,
2120
+ detail=output_content,
2121
+ )
2122
+ """
2123
+ main process
2124
+ """
2125
+ # logic for adding new recovery_methods
2126
+ local_list_response_user_recovery_methods = (
2127
+ global_object_square_database_helper.get_rows_v0(
2128
+ database_name=global_string_database_name,
2129
+ schema_name=global_string_schema_name,
2130
+ table_name=UserRecoveryMethod.__tablename__,
2131
+ filters=FiltersV0(
2132
+ root={
2133
+ UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id)
2134
+ }
2135
+ ),
2136
+ )["data"]["main"]
2137
+ )
2138
+ local_list_new_recovery_methods = [
2139
+ {
2140
+ UserRecoveryMethod.user_id.name: user_id,
2141
+ UserRecoveryMethod.user_recovery_method_name.name: x,
2142
+ }
2143
+ for x in recovery_methods_to_add
2144
+ if x
2145
+ not in [
2146
+ y[UserRecoveryMethod.user_recovery_method_name.name]
2147
+ for y in local_list_response_user_recovery_methods
2148
+ ]
2149
+ ]
2150
+ if len(local_list_new_recovery_methods) > 0:
2151
+ global_object_square_database_helper.insert_rows_v0(
2152
+ database_name=global_string_database_name,
2153
+ schema_name=global_string_schema_name,
2154
+ table_name=UserRecoveryMethod.__tablename__,
2155
+ data=local_list_new_recovery_methods,
2156
+ )
2157
+
2158
+ # logic for removing recovery_methods
2159
+ remove_old_backup_codes = (
2160
+ RecoveryMethodEnum.BACKUP_CODE.value in recovery_methods_to_remove
2161
+ )
2162
+ old_backup_code_hashes = None
2163
+ if remove_old_backup_codes:
2164
+ # delete existing backup codes if any
2165
+ old_backup_code_hashes = global_object_square_database_helper.get_rows_v0(
2166
+ database_name=global_string_database_name,
2167
+ schema_name=global_string_schema_name,
2168
+ table_name=UserVerificationCode.__tablename__,
2169
+ filters=FiltersV0(
2170
+ root={
2171
+ UserVerificationCode.user_id.name: FilterConditionsV0(
2172
+ eq=user_id
2173
+ ),
2174
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
2175
+ eq=VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value
2176
+ ),
2177
+ }
2178
+ ),
2179
+ columns=[UserVerificationCode.user_verification_code_hash.name],
2180
+ )["data"]["main"]
2181
+ global_object_square_database_helper.delete_rows_v0(
2182
+ database_name=global_string_database_name,
2183
+ schema_name=global_string_schema_name,
2184
+ table_name=UserRecoveryMethod.__tablename__,
2185
+ filters=FiltersV0(
2186
+ root={
2187
+ UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
2188
+ UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
2189
+ in_=recovery_methods_to_remove
2190
+ ),
2191
+ }
2192
+ ),
2193
+ )
2194
+ if remove_old_backup_codes and old_backup_code_hashes:
2195
+ global_object_square_database_helper.delete_rows_v0(
2196
+ database_name=global_string_database_name,
2197
+ schema_name=global_string_schema_name,
2198
+ table_name=UserVerificationCode.__tablename__,
2199
+ filters=FiltersV0(
2200
+ root={
2201
+ UserVerificationCode.user_verification_code_hash.name: FilterConditionsV0(
2202
+ in_=[
2203
+ x[UserVerificationCode.user_verification_code_hash.name]
2204
+ for x in old_backup_code_hashes
2205
+ ]
2206
+ ),
2207
+ }
2208
+ ),
2209
+ )
2210
+
2211
+ """
2212
+ return value
2213
+ """
2214
+ # get latest recovery_methods
2215
+ local_list_response_user_recovery_methods = (
2216
+ global_object_square_database_helper.get_rows_v0(
2217
+ database_name=global_string_database_name,
2218
+ schema_name=global_string_schema_name,
2219
+ table_name=UserRecoveryMethod.__tablename__,
2220
+ filters=FiltersV0(
2221
+ root={
2222
+ UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id)
2223
+ }
2224
+ ),
2225
+ )["data"]["main"]
2226
+ )
2227
+ output_content = get_api_output_in_standard_format(
2228
+ message=messages["GENERIC_UPDATE_SUCCESSFUL"],
2229
+ data={
2230
+ "main": [
2231
+ x[UserRecoveryMethod.user_recovery_method_name.name]
2232
+ for x in local_list_response_user_recovery_methods
2233
+ ]
2234
+ },
2235
+ )
2236
+ return JSONResponse(
2237
+ status_code=status.HTTP_200_OK,
2238
+ content=output_content,
2239
+ )
2240
+ except HTTPException as http_exception:
2241
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
2242
+ return JSONResponse(
2243
+ status_code=http_exception.status_code, content=http_exception.detail
2244
+ )
2245
+ except Exception as e:
2246
+ """
2247
+ rollback logic
2248
+ """
2249
+ global_object_square_logger.logger.error(e, exc_info=True)
2250
+ output_content = get_api_output_in_standard_format(
2251
+ message=messages["GENERIC_500"],
2252
+ log=str(e),
2253
+ )
2254
+ return JSONResponse(
2255
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
2256
+ content=output_content,
2257
+ )
2258
+
2259
+
2260
+ @global_object_square_logger.auto_logger()
2261
+ def util_generate_account_backup_codes_v0(access_token):
2262
+ try:
2263
+ """
2264
+ validation
2265
+ """
2266
+ try:
2267
+ local_dict_access_token_payload = get_jwt_payload(
2268
+ access_token, config_str_secret_key_for_access_token
2269
+ )
2270
+ except Exception as error:
2271
+ output_content = get_api_output_in_standard_format(
2272
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
2273
+ )
2274
+ raise HTTPException(
2275
+ status_code=status.HTTP_400_BAD_REQUEST,
2276
+ detail=output_content,
2277
+ )
2278
+ user_id = local_dict_access_token_payload["user_id"]
2279
+ # check if user has recovery method enabled
2280
+ local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
2281
+ database_name=global_string_database_name,
2282
+ schema_name=global_string_schema_name,
2283
+ table_name=UserRecoveryMethod.__tablename__,
2284
+ filters=FiltersV0(
2285
+ root={
2286
+ UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
2287
+ UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
2288
+ eq=RecoveryMethodEnum.BACKUP_CODE.value
2289
+ ),
2290
+ }
2291
+ ),
2292
+ )[
2293
+ "data"
2294
+ ][
2295
+ "main"
2296
+ ]
2297
+ if len(local_list_response_user_recovery_methods) != 1:
2298
+ output_content = get_api_output_in_standard_format(
2299
+ message=messages["RECOVERY_METHOD_NOT_ENABLED"],
2300
+ log=f"user_id: {user_id} does not have backup codes recovery method enabled.",
2301
+ )
2302
+ raise HTTPException(
2303
+ status_code=status.HTTP_400_BAD_REQUEST,
2304
+ detail=output_content,
2305
+ )
2306
+ """
2307
+ main process
2308
+ """
2309
+ # delete existing backup codes if any
2310
+ old_backup_code_hashes = global_object_square_database_helper.get_rows_v0(
2311
+ database_name=global_string_database_name,
2312
+ schema_name=global_string_schema_name,
2313
+ table_name=UserVerificationCode.__tablename__,
2314
+ filters=FiltersV0(
2315
+ root={
2316
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
2317
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
2318
+ eq=VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value
2319
+ ),
2320
+ }
2321
+ ),
2322
+ columns=[UserVerificationCode.user_verification_code_hash.name],
2323
+ )["data"]["main"]
2324
+
2325
+ # generate backup codes
2326
+ backup_codes = []
2327
+ db_data = []
2328
+
2329
+ for i in range(NUMBER_OF_RECOVERY_CODES):
2330
+ backup_code = str(uuid.uuid4())
2331
+ backup_codes.append(backup_code)
2332
+ # hash the backup code
2333
+ local_str_hashed_backup_code = bcrypt.hashpw(
2334
+ backup_code.encode("utf-8"), bcrypt.gensalt()
2335
+ ).decode("utf-8")
2336
+
2337
+ db_data.append(
2338
+ {
2339
+ UserVerificationCode.user_id.name: user_id,
2340
+ UserVerificationCode.user_verification_code_type.name: VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value,
2341
+ UserVerificationCode.user_verification_code_hash.name: local_str_hashed_backup_code,
2342
+ }
2343
+ )
2344
+ global_object_square_database_helper.insert_rows_v0(
2345
+ database_name=global_string_database_name,
2346
+ schema_name=global_string_schema_name,
2347
+ table_name=UserVerificationCode.__tablename__,
2348
+ data=db_data,
2349
+ )
2350
+ global_object_square_database_helper.delete_rows_v0(
2351
+ database_name=global_string_database_name,
2352
+ schema_name=global_string_schema_name,
2353
+ table_name=UserVerificationCode.__tablename__,
2354
+ filters=FiltersV0(
2355
+ root={
2356
+ UserVerificationCode.user_verification_code_hash.name: FilterConditionsV0(
2357
+ in_=[
2358
+ x[UserVerificationCode.user_verification_code_hash.name]
2359
+ for x in old_backup_code_hashes
2360
+ ]
2361
+ ),
2362
+ }
2363
+ ),
2364
+ )
2365
+ """
2366
+ return value
2367
+ """
2368
+ output_content = get_api_output_in_standard_format(
2369
+ message=messages["GENERIC_CREATION_SUCCESSFUL"],
2370
+ data={
2371
+ "main": {
2372
+ "user_id": user_id,
2373
+ "backup_codes": backup_codes,
2374
+ }
2375
+ },
2376
+ )
2377
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
2378
+ except HTTPException as http_exception:
2379
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
2380
+ return JSONResponse(
2381
+ status_code=http_exception.status_code, content=http_exception.detail
2382
+ )
2383
+ except Exception as e:
2384
+ """
2385
+ rollback logic
2386
+ """
2387
+ global_object_square_logger.logger.error(e, exc_info=True)
2388
+ output_content = get_api_output_in_standard_format(
2389
+ message=messages["GENERIC_500"],
2390
+ log=str(e),
2391
+ )
2392
+ return JSONResponse(
2393
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
2394
+ )
2395
+
2396
+
2397
+ @global_object_square_logger.auto_logger()
2398
+ def util_reset_password_and_login_using_backup_code_v0(
2399
+ backup_code, username, new_password, app_id, logout_other_sessions
2400
+ ):
2401
+ try:
2402
+ """
2403
+ validation
2404
+ """
2405
+ # validate username
2406
+ local_list_authentication_user_response = (
2407
+ global_object_square_database_helper.get_rows_v0(
2408
+ database_name=global_string_database_name,
2409
+ schema_name=global_string_schema_name,
2410
+ table_name=User.__tablename__,
2411
+ filters=FiltersV0(
2412
+ root={User.user_username.name: FilterConditionsV0(eq=username)}
2413
+ ),
2414
+ )["data"]["main"]
2415
+ )
2416
+ if len(local_list_authentication_user_response) != 1:
2417
+ output_content = get_api_output_in_standard_format(
2418
+ message=messages["INCORRECT_USERNAME"],
2419
+ log=f"incorrect username: {username}.",
2420
+ )
2421
+ raise HTTPException(
2422
+ status_code=status.HTTP_400_BAD_REQUEST,
2423
+ detail=output_content,
2424
+ )
2425
+ user_id = local_list_authentication_user_response[0][User.user_id.name]
2426
+ # check if user has SELF auth provider
2427
+ local_list_response_user_auth_provider = (
2428
+ global_object_square_database_helper.get_rows_v0(
2429
+ database_name=global_string_database_name,
2430
+ schema_name=global_string_schema_name,
2431
+ table_name=UserAuthProvider.__tablename__,
2432
+ filters=FiltersV0(
2433
+ root={
2434
+ UserAuthProvider.user_id.name: FilterConditionsV0(eq=user_id),
2435
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
2436
+ eq=AuthProviderEnum.SELF.value
2437
+ ),
2438
+ }
2439
+ ),
2440
+ )["data"]["main"]
2441
+ )
2442
+ if len(local_list_response_user_auth_provider) != 1:
2443
+ output_content = get_api_output_in_standard_format(
2444
+ message=messages["INCORRECT_AUTH_PROVIDER"],
2445
+ log=f"user_id: {user_id} does not have {AuthProviderEnum.SELF.value} auth provider.",
2446
+ )
2447
+ raise HTTPException(
2448
+ status_code=status.HTTP_400_BAD_REQUEST,
2449
+ detail=output_content,
2450
+ )
2451
+ # check if user has recovery method enabled
2452
+ local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
2453
+ database_name=global_string_database_name,
2454
+ schema_name=global_string_schema_name,
2455
+ table_name=UserRecoveryMethod.__tablename__,
2456
+ filters=FiltersV0(
2457
+ root={
2458
+ UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
2459
+ UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
2460
+ eq=RecoveryMethodEnum.BACKUP_CODE.value
2461
+ ),
2462
+ }
2463
+ ),
2464
+ )[
2465
+ "data"
2466
+ ][
2467
+ "main"
2468
+ ]
2469
+ if len(local_list_response_user_recovery_methods) != 1:
2470
+ output_content = get_api_output_in_standard_format(
2471
+ message=messages["RECOVERY_METHOD_NOT_ENABLED"],
2472
+ log=f"user_id: {user_id} does not have backup codes recovery method enabled.",
2473
+ )
2474
+ raise HTTPException(
2475
+ status_code=status.HTTP_400_BAD_REQUEST,
2476
+ detail=output_content,
2477
+ )
2478
+ # validate if user is assigned to the app.
2479
+ # not checking [skipping] if the app exists, as it is not required for this endpoint.
2480
+ local_list_response_user_app = global_object_square_database_helper.get_rows_v0(
2481
+ database_name=global_string_database_name,
2482
+ schema_name=global_string_schema_name,
2483
+ table_name=UserApp.__tablename__,
2484
+ filters=FiltersV0(
2485
+ root={
2486
+ UserApp.user_id.name: FilterConditionsV0(eq=user_id),
2487
+ UserApp.app_id.name: FilterConditionsV0(eq=app_id),
2488
+ }
2489
+ ),
2490
+ )["data"]["main"]
2491
+ if len(local_list_response_user_app) == 0:
2492
+ output_content = get_api_output_in_standard_format(
2493
+ message=messages["GENERIC_400"],
2494
+ log=f"user_id: {user_id} is not assigned to app_id: {app_id}.",
2495
+ )
2496
+ raise HTTPException(
2497
+ status_code=status.HTTP_400_BAD_REQUEST,
2498
+ detail=output_content,
2499
+ )
2500
+ """
2501
+ main process
2502
+ """
2503
+ # validate backup code
2504
+ local_list_response_user_verification_code = global_object_square_database_helper.get_rows_v0(
2505
+ database_name=global_string_database_name,
2506
+ schema_name=global_string_schema_name,
2507
+ table_name=UserVerificationCode.__tablename__,
2508
+ filters=FiltersV0(
2509
+ root={
2510
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
2511
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
2512
+ eq=VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value
2513
+ ),
2514
+ UserVerificationCode.user_verification_code_expires_at.name: FilterConditionsV0(
2515
+ is_null=True
2516
+ ),
2517
+ UserVerificationCode.user_verification_code_used_at.name: FilterConditionsV0(
2518
+ is_null=True
2519
+ ),
2520
+ }
2521
+ ),
2522
+ columns=[UserVerificationCode.user_verification_code_hash.name],
2523
+ )[
2524
+ "data"
2525
+ ][
2526
+ "main"
2527
+ ]
2528
+ # find the backup code in the list
2529
+ local_list_response_user_verification_code = [
2530
+ x
2531
+ for x in local_list_response_user_verification_code
2532
+ if bcrypt.checkpw(
2533
+ backup_code.encode("utf-8"),
2534
+ x[UserVerificationCode.user_verification_code_hash.name].encode(
2535
+ "utf-8"
2536
+ ),
2537
+ )
2538
+ ]
2539
+ if len(local_list_response_user_verification_code) != 1:
2540
+ output_content = get_api_output_in_standard_format(
2541
+ message=messages["INCORRECT_BACKUP_CODE"],
2542
+ log=f"incorrect backup code: {backup_code} for user_id: {user_id}.",
2543
+ )
2544
+ raise HTTPException(
2545
+ status_code=status.HTTP_400_BAD_REQUEST,
2546
+ detail=output_content,
2547
+ )
2548
+ # hash the new password
2549
+ local_str_hashed_password = bcrypt.hashpw(
2550
+ new_password.encode("utf-8"), bcrypt.gensalt()
2551
+ ).decode("utf-8")
2552
+ # update the password
2553
+ global_object_square_database_helper.edit_rows_v0(
2554
+ database_name=global_string_database_name,
2555
+ schema_name=global_string_schema_name,
2556
+ table_name=UserCredential.__tablename__,
2557
+ filters=FiltersV0(
2558
+ root={
2559
+ UserCredential.user_id.name: FilterConditionsV0(eq=user_id),
2560
+ }
2561
+ ),
2562
+ data={
2563
+ UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
2564
+ },
2565
+ )
2566
+ # mark the backup code as used
2567
+ global_object_square_database_helper.edit_rows_v0(
2568
+ database_name=global_string_database_name,
2569
+ schema_name=global_string_schema_name,
2570
+ table_name=UserVerificationCode.__tablename__,
2571
+ filters=FiltersV0(
2572
+ root={
2573
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
2574
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
2575
+ eq=VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value
2576
+ ),
2577
+ UserVerificationCode.user_verification_code_hash.name: FilterConditionsV0(
2578
+ eq=local_list_response_user_verification_code[0][
2579
+ UserVerificationCode.user_verification_code_hash.name
2580
+ ]
2581
+ ),
2582
+ }
2583
+ ),
2584
+ data={
2585
+ UserVerificationCode.user_verification_code_used_at.name: datetime.now(
2586
+ timezone.utc
2587
+ ).strftime("%Y-%m-%d %H:%M:%S.%f+00"),
2588
+ },
2589
+ )
2590
+ if logout_other_sessions:
2591
+ # delete all sessions for user
2592
+ global_object_square_database_helper.delete_rows_v0(
2593
+ database_name=global_string_database_name,
2594
+ schema_name=global_string_schema_name,
2595
+ table_name=UserSession.__tablename__,
2596
+ filters=FiltersV0(
2597
+ root={
2598
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
2599
+ }
2600
+ ),
2601
+ )
2602
+ # generate access token and refresh token
2603
+ local_dict_access_token_payload = {
2604
+ "app_id": app_id,
2605
+ "user_id": user_id,
2606
+ "exp": datetime.now(timezone.utc)
2607
+ + timedelta(minutes=config_int_access_token_valid_minutes),
2608
+ }
2609
+ local_str_access_token = jwt.encode(
2610
+ local_dict_access_token_payload, config_str_secret_key_for_access_token
2611
+ )
2612
+ local_object_refresh_token_expiry_time = datetime.now(timezone.utc) + timedelta(
2613
+ minutes=config_int_refresh_token_valid_minutes
2614
+ )
2615
+ local_dict_refresh_token_payload = {
2616
+ "app_id": app_id,
2617
+ "user_id": user_id,
2618
+ "exp": local_object_refresh_token_expiry_time,
2619
+ }
2620
+ local_str_refresh_token = jwt.encode(
2621
+ local_dict_refresh_token_payload, config_str_secret_key_for_refresh_token
2622
+ )
2623
+ # insert the refresh token in the database
2624
+ global_object_square_database_helper.insert_rows_v0(
2625
+ database_name=global_string_database_name,
2626
+ schema_name=global_string_schema_name,
2627
+ table_name=UserSession.__tablename__,
2628
+ data=[
2629
+ {
2630
+ UserSession.user_id.name: user_id,
2631
+ UserSession.app_id.name: app_id,
2632
+ UserSession.user_session_refresh_token.name: local_str_refresh_token,
2633
+ UserSession.user_session_expiry_time.name: local_object_refresh_token_expiry_time.strftime(
2634
+ "%Y-%m-%d %H:%M:%S.%f+00"
2635
+ ),
2636
+ }
2637
+ ],
2638
+ )
2639
+ """
2640
+ return value
2641
+ """
2642
+ output_content = get_api_output_in_standard_format(
2643
+ message=messages["GENERIC_ACTION_SUCCESSFUL"],
2644
+ data={
2645
+ "main": {
2646
+ "user_id": user_id,
2647
+ "access_token": local_str_access_token,
2648
+ "refresh_token": local_str_refresh_token,
2649
+ "refresh_token_expiry_time": local_object_refresh_token_expiry_time.isoformat(),
2650
+ }
2651
+ },
2652
+ )
2653
+
2654
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
2655
+ except HTTPException as http_exception:
2656
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
2657
+ return JSONResponse(
2658
+ status_code=http_exception.status_code, content=http_exception.detail
2659
+ )
2660
+ except Exception as e:
2661
+ """
2662
+ rollback logic
2663
+ """
2664
+ global_object_square_logger.logger.error(e, exc_info=True)
2665
+ output_content = get_api_output_in_standard_format(
2666
+ message=messages["GENERIC_500"],
2667
+ log=str(e),
2668
+ )
2669
+ return JSONResponse(
2670
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
2671
+ )
2672
+
2673
+
2674
+ @global_object_square_logger.auto_logger()
2675
+ def util_send_reset_password_email_v0(
2676
+ username,
2677
+ ):
2678
+ try:
2679
+ """
2680
+ validation
2681
+ """
2682
+ # validate username
2683
+ local_list_authentication_user_response = (
2684
+ global_object_square_database_helper.get_rows_v0(
2685
+ database_name=global_string_database_name,
2686
+ schema_name=global_string_schema_name,
2687
+ table_name=User.__tablename__,
2688
+ filters=FiltersV0(
2689
+ root={User.user_username.name: FilterConditionsV0(eq=username)}
2690
+ ),
2691
+ )["data"]["main"]
2692
+ )
2693
+ if len(local_list_authentication_user_response) != 1:
2694
+ output_content = get_api_output_in_standard_format(
2695
+ message=messages["INCORRECT_USERNAME"],
2696
+ log=f"incorrect username: {username}.",
2697
+ )
2698
+ raise HTTPException(
2699
+ status_code=status.HTTP_400_BAD_REQUEST,
2700
+ detail=output_content,
2701
+ )
2702
+ user_id = local_list_authentication_user_response[0][User.user_id.name]
2703
+ # check if user has SELF auth provider
2704
+ local_list_response_user_auth_provider = (
2705
+ global_object_square_database_helper.get_rows_v0(
2706
+ database_name=global_string_database_name,
2707
+ schema_name=global_string_schema_name,
2708
+ table_name=UserAuthProvider.__tablename__,
2709
+ filters=FiltersV0(
2710
+ root={
2711
+ UserAuthProvider.user_id.name: FilterConditionsV0(eq=user_id),
2712
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
2713
+ eq=AuthProviderEnum.SELF.value
2714
+ ),
2715
+ }
2716
+ ),
2717
+ )["data"]["main"]
2718
+ )
2719
+ if len(local_list_response_user_auth_provider) != 1:
2720
+ output_content = get_api_output_in_standard_format(
2721
+ message=messages["INCORRECT_AUTH_PROVIDER"],
2722
+ log=f"user_id: {user_id} does not have {AuthProviderEnum.SELF.value} auth provider.",
2723
+ )
2724
+ raise HTTPException(
2725
+ status_code=status.HTTP_400_BAD_REQUEST,
2726
+ detail=output_content,
2727
+ )
2728
+ # check if user has recovery method enabled
2729
+ local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
2730
+ database_name=global_string_database_name,
2731
+ schema_name=global_string_schema_name,
2732
+ table_name=UserRecoveryMethod.__tablename__,
2733
+ filters=FiltersV0(
2734
+ root={
2735
+ UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
2736
+ UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
2737
+ eq=RecoveryMethodEnum.EMAIL.value
2738
+ ),
2739
+ }
2740
+ ),
2741
+ )[
2742
+ "data"
2743
+ ][
2744
+ "main"
2745
+ ]
2746
+ if len(local_list_response_user_recovery_methods) != 1:
2747
+ output_content = get_api_output_in_standard_format(
2748
+ message=messages["RECOVERY_METHOD_NOT_ENABLED"],
2749
+ log=f"user_id: {user_id} does not have email recovery method enabled.",
2750
+ )
2751
+ raise HTTPException(
2752
+ status_code=status.HTTP_400_BAD_REQUEST,
2753
+ detail=output_content,
2754
+ )
2755
+ # validate if user has email in profile
2756
+ user_profile_response = global_object_square_database_helper.get_rows_v0(
2757
+ database_name=global_string_database_name,
2758
+ schema_name=global_string_schema_name,
2759
+ table_name=UserProfile.__tablename__,
2760
+ filters=FiltersV0(
2761
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
2762
+ ),
2763
+ apply_filters=True,
2764
+ )
2765
+ user_profile_data = user_profile_response["data"]["main"][0]
2766
+ if not user_profile_data.get(UserProfile.user_profile_email.name):
2767
+ output_content = get_api_output_in_standard_format(
2768
+ message=messages["GENERIC_MISSING_REQUIRED_FIELD"],
2769
+ log="email is required to send verification email.",
2770
+ )
2771
+ raise HTTPException(
2772
+ status_code=status.HTTP_400_BAD_REQUEST,
2773
+ detail=output_content,
2774
+ )
2775
+ # check if email is not verified
2776
+ if not user_profile_data.get(UserProfile.user_profile_email_verified.name):
2777
+ output_content = get_api_output_in_standard_format(
2778
+ message=messages["EMAIL_NOT_VERIFIED"],
2779
+ log="email is not verified.",
2780
+ )
2781
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
2782
+ # check if reset password code already exists
2783
+ local_list_response_user_verification_code = global_object_square_database_helper.get_rows_v0(
2784
+ database_name=global_string_database_name,
2785
+ schema_name=global_string_schema_name,
2786
+ table_name=UserVerificationCode.__tablename__,
2787
+ filters=FiltersV0(
2788
+ root={
2789
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
2790
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
2791
+ eq=VerificationCodeTypeEnum.EMAIL_RECOVERY.value
2792
+ ),
2793
+ UserVerificationCode.user_verification_code_used_at.name: FilterConditionsV0(
2794
+ is_null=True
2795
+ ),
2796
+ UserVerificationCode.user_verification_code_expires_at.name: FilterConditionsV0(
2797
+ gte=datetime.now(timezone.utc).strftime(
2798
+ "%Y-%m-%d %H:%M:%S.%f+00"
2799
+ )
2800
+ ),
2801
+ }
2802
+ ),
2803
+ order_by=[
2804
+ "-" + UserVerificationCode.user_verification_code_created_at.name
2805
+ ],
2806
+ limit=1,
2807
+ apply_filters=True,
2808
+ )[
2809
+ "data"
2810
+ ][
2811
+ "main"
2812
+ ]
2813
+ if len(local_list_response_user_verification_code) > 0:
2814
+ existing_verification_code_data = (
2815
+ local_list_response_user_verification_code[0]
2816
+ )
2817
+ if (
2818
+ datetime.now(timezone.utc)
2819
+ - datetime.fromisoformat(
2820
+ existing_verification_code_data[
2821
+ UserVerificationCode.user_verification_code_created_at.name
2822
+ ]
2823
+ )
2824
+ ).total_seconds() < RESEND_COOL_DOWN_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS:
2825
+ output_content = get_api_output_in_standard_format(
2826
+ message=messages["GENERIC_400"],
2827
+ log="verification code already exists and was sent within the cooldown period.",
2828
+ )
2829
+ raise HTTPException(
2830
+ status_code=status.HTTP_400_BAD_REQUEST,
2831
+ detail=output_content,
2832
+ )
2833
+ """
2834
+ main process
2835
+ """
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
+ )
2840
+ # hash the verification code
2841
+ hashed_verification_code = bcrypt.hashpw(
2842
+ str(verification_code).encode("utf-8"), bcrypt.gensalt()
2843
+ ).decode("utf-8")
2844
+ expires_at = datetime.now(timezone.utc) + timedelta(
2845
+ seconds=EXPIRY_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS
2846
+ )
2847
+ # add verification code to UserVerification code table
2848
+ global_object_square_database_helper.insert_rows_v0(
2849
+ database_name=global_string_database_name,
2850
+ schema_name=global_string_schema_name,
2851
+ table_name=UserVerificationCode.__tablename__,
2852
+ data=[
2853
+ {
2854
+ UserVerificationCode.user_id.name: user_id,
2855
+ UserVerificationCode.user_verification_code_type.name: VerificationCodeTypeEnum.EMAIL_RECOVERY.value,
2856
+ UserVerificationCode.user_verification_code_hash.name: hashed_verification_code,
2857
+ UserVerificationCode.user_verification_code_expires_at.name: expires_at.strftime(
2858
+ "%Y-%m-%d %H:%M:%S.%f+00"
2859
+ ),
2860
+ }
2861
+ ],
2862
+ )
2863
+ # send verification email
2864
+ if (
2865
+ user_profile_data[UserProfile.user_profile_first_name.name]
2866
+ and user_profile_data[UserProfile.user_profile_last_name.name]
2867
+ ):
2868
+ user_to_name = f"{user_profile_data[UserProfile.user_profile_first_name.name]} {user_profile_data[UserProfile.user_profile_last_name.name]}"
2869
+ elif user_profile_data[UserProfile.user_profile_first_name.name]:
2870
+ user_to_name = user_profile_data[UserProfile.user_profile_first_name.name]
2871
+ elif user_profile_data[UserProfile.user_profile_last_name.name]:
2872
+ user_to_name = user_profile_data[UserProfile.user_profile_last_name.name]
2873
+ else:
2874
+ user_to_name = ""
2875
+
2876
+ mailgun_response = send_email_using_mailgun(
2877
+ from_email="auth@thepmsquare.com",
2878
+ from_name="square_authentication",
2879
+ to_email=user_profile_data[UserProfile.user_profile_email.name],
2880
+ to_name=user_to_name,
2881
+ subject="Password Reset Verification Code",
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.",
2883
+ api_key=MAIL_GUN_API_KEY,
2884
+ domain_name="thepmsquare.com",
2885
+ )
2886
+ # add log for email sending
2887
+ global_object_square_database_helper.insert_rows_v0(
2888
+ database_name=global_string_database_name,
2889
+ schema_name=email_schema_name,
2890
+ table_name=EmailLog.__tablename__,
2891
+ data=[
2892
+ {
2893
+ EmailLog.user_id.name: user_id,
2894
+ EmailLog.recipient_email.name: user_profile_data[
2895
+ UserProfile.user_profile_email.name
2896
+ ],
2897
+ EmailLog.email_type.name: EmailTypeEnum.VERIFY_EMAIL.value,
2898
+ EmailLog.status.name: EmailStatusEnum.SENT.value,
2899
+ EmailLog.third_party_message_id.name: mailgun_response.get("id"),
2900
+ }
2901
+ ],
2902
+ )
2903
+ """
2904
+ return value
2905
+ """
2906
+ cooldown_reset_at = datetime.now(timezone.utc) + timedelta(
2907
+ seconds=EXPIRY_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS,
2908
+ )
2909
+
2910
+ output_content = get_api_output_in_standard_format(
2911
+ data={
2912
+ "expires_at": expires_at.isoformat(),
2913
+ "cooldown_reset_at": cooldown_reset_at.isoformat(),
2914
+ },
2915
+ message=messages["GENERIC_ACTION_SUCCESSFUL"],
2916
+ )
2917
+ return JSONResponse(
2918
+ status_code=status.HTTP_200_OK,
2919
+ content=output_content,
2920
+ )
2921
+ except HTTPException as http_exception:
2922
+ global_object_square_logger.logger.error(http_exception, exc_info=True)
2923
+ return JSONResponse(
2924
+ status_code=http_exception.status_code, content=http_exception.detail
2925
+ )
2926
+ except Exception as e:
2927
+ """
2928
+ rollback logic
2929
+ """
2930
+ global_object_square_logger.logger.error(e, exc_info=True)
2931
+ output_content = get_api_output_in_standard_format(
2932
+ message=messages["GENERIC_500"],
2933
+ log=str(e),
2934
+ )
2935
+ return JSONResponse(
2936
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
2937
+ )
2938
+
2939
+
2940
+ @global_object_square_logger.auto_logger()
2941
+ def util_reset_password_and_login_using_reset_email_code_v0(
2942
+ reset_email_code, username, new_password, app_id, logout_other_sessions
2943
+ ):
2944
+ try:
2945
+ """
2946
+ validation
2947
+ """
2948
+ # validate username
2949
+ local_list_authentication_user_response = (
2950
+ global_object_square_database_helper.get_rows_v0(
2951
+ database_name=global_string_database_name,
2952
+ schema_name=global_string_schema_name,
2953
+ table_name=User.__tablename__,
2954
+ filters=FiltersV0(
2955
+ root={User.user_username.name: FilterConditionsV0(eq=username)}
2956
+ ),
2957
+ )["data"]["main"]
2958
+ )
2959
+ if len(local_list_authentication_user_response) != 1:
2960
+ output_content = get_api_output_in_standard_format(
2961
+ message=messages["INCORRECT_USERNAME"],
2962
+ log=f"incorrect username: {username}.",
2963
+ )
2964
+ raise HTTPException(
2965
+ status_code=status.HTTP_400_BAD_REQUEST,
2966
+ detail=output_content,
2967
+ )
2968
+ user_id = local_list_authentication_user_response[0][User.user_id.name]
2969
+ # check if user has SELF auth provider
2970
+ local_list_response_user_auth_provider = (
2971
+ global_object_square_database_helper.get_rows_v0(
2972
+ database_name=global_string_database_name,
2973
+ schema_name=global_string_schema_name,
2974
+ table_name=UserAuthProvider.__tablename__,
2975
+ filters=FiltersV0(
2976
+ root={
2977
+ UserAuthProvider.user_id.name: FilterConditionsV0(eq=user_id),
2978
+ UserAuthProvider.auth_provider.name: FilterConditionsV0(
2979
+ eq=AuthProviderEnum.SELF.value
2980
+ ),
2981
+ }
2982
+ ),
2983
+ )["data"]["main"]
2984
+ )
2985
+ if len(local_list_response_user_auth_provider) != 1:
2986
+ output_content = get_api_output_in_standard_format(
2987
+ message=messages["INCORRECT_AUTH_PROVIDER"],
2988
+ log=f"user_id: {user_id} does not have {AuthProviderEnum.SELF.value} auth provider.",
2989
+ )
2990
+ raise HTTPException(
2991
+ status_code=status.HTTP_400_BAD_REQUEST,
2992
+ detail=output_content,
2993
+ )
2994
+ # check if user has recovery method enabled
2995
+ local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
2996
+ database_name=global_string_database_name,
2997
+ schema_name=global_string_schema_name,
2998
+ table_name=UserRecoveryMethod.__tablename__,
2999
+ filters=FiltersV0(
3000
+ root={
3001
+ UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
3002
+ UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
3003
+ eq=RecoveryMethodEnum.EMAIL.value
3004
+ ),
3005
+ }
3006
+ ),
3007
+ )[
3008
+ "data"
3009
+ ][
3010
+ "main"
3011
+ ]
3012
+ if len(local_list_response_user_recovery_methods) != 1:
3013
+ output_content = get_api_output_in_standard_format(
3014
+ message=messages["RECOVERY_METHOD_NOT_ENABLED"],
3015
+ log=f"user_id: {user_id} does not have email recovery method enabled.",
3016
+ )
3017
+ raise HTTPException(
3018
+ status_code=status.HTTP_400_BAD_REQUEST,
3019
+ detail=output_content,
3020
+ )
3021
+ # check if user has email in profile
3022
+ user_profile_response = global_object_square_database_helper.get_rows_v0(
3023
+ database_name=global_string_database_name,
3024
+ schema_name=global_string_schema_name,
3025
+ table_name=UserProfile.__tablename__,
3026
+ filters=FiltersV0(
3027
+ root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
3028
+ ),
3029
+ apply_filters=True,
3030
+ )
3031
+ user_profile_data = user_profile_response["data"]["main"][0]
3032
+ if not user_profile_data.get(UserProfile.user_profile_email.name):
3033
+ output_content = get_api_output_in_standard_format(
3034
+ message=messages["GENERIC_MISSING_REQUIRED_FIELD"],
3035
+ log="user seems to have email recovery method enabled, but does not have email in profile.",
3036
+ )
3037
+ raise HTTPException(
3038
+ status_code=status.HTTP_400_BAD_REQUEST,
3039
+ detail=output_content,
3040
+ )
3041
+ # check if email is verified.
3042
+ if not user_profile_data.get(UserProfile.user_profile_email_verified.name):
3043
+ output_content = get_api_output_in_standard_format(
3044
+ message=messages["EMAIL_NOT_VERIFIED"],
3045
+ log="email is not verified.",
3046
+ )
3047
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
3048
+ # validate if user is assigned to the app.
3049
+ # not checking [skipping] if the app exists, as it is not required for this endpoint.
3050
+ local_list_response_user_app = global_object_square_database_helper.get_rows_v0(
3051
+ database_name=global_string_database_name,
3052
+ schema_name=global_string_schema_name,
3053
+ table_name=UserApp.__tablename__,
3054
+ filters=FiltersV0(
3055
+ root={
3056
+ UserApp.user_id.name: FilterConditionsV0(eq=user_id),
3057
+ UserApp.app_id.name: FilterConditionsV0(eq=app_id),
3058
+ }
3059
+ ),
3060
+ )["data"]["main"]
3061
+ if len(local_list_response_user_app) == 0:
3062
+ output_content = get_api_output_in_standard_format(
3063
+ message=messages["GENERIC_400"],
3064
+ log=f"user_id: {user_id} is not assigned to app_id: {app_id}.",
3065
+ )
3066
+ raise HTTPException(
3067
+ status_code=status.HTTP_400_BAD_REQUEST,
3068
+ detail=output_content,
3069
+ )
3070
+ """
3071
+ main process
3072
+ """
3073
+ # validate email reset code
3074
+ local_list_response_user_verification_code = global_object_square_database_helper.get_rows_v0(
3075
+ database_name=global_string_database_name,
3076
+ schema_name=global_string_schema_name,
3077
+ table_name=UserVerificationCode.__tablename__,
3078
+ filters=FiltersV0(
3079
+ root={
3080
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
3081
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
3082
+ eq=VerificationCodeTypeEnum.EMAIL_RECOVERY.value
3083
+ ),
3084
+ UserVerificationCode.user_verification_code_expires_at.name: FilterConditionsV0(
3085
+ gte=datetime.now(timezone.utc).strftime(
3086
+ "%Y-%m-%d %H:%M:%S.%f+00"
3087
+ )
3088
+ ),
3089
+ UserVerificationCode.user_verification_code_used_at.name: FilterConditionsV0(
3090
+ is_null=True
3091
+ ),
3092
+ }
3093
+ ),
3094
+ columns=[UserVerificationCode.user_verification_code_hash.name],
3095
+ order_by=[
3096
+ "-" + UserVerificationCode.user_verification_code_created_at.name
3097
+ ],
3098
+ limit=1,
3099
+ )[
3100
+ "data"
3101
+ ][
3102
+ "main"
3103
+ ]
3104
+ if len(local_list_response_user_verification_code) != 1:
3105
+ output_content = get_api_output_in_standard_format(
3106
+ message=messages["INCORRECT_VERIFICATION_CODE"],
3107
+ log=f"incorrect reset_email_code: {reset_email_code} for user_id: {user_id}.",
3108
+ )
3109
+ raise HTTPException(
3110
+ status_code=status.HTTP_400_BAD_REQUEST,
3111
+ detail=output_content,
3112
+ )
3113
+ latest_hashed_verification_code = local_list_response_user_verification_code[0][
3114
+ UserVerificationCode.user_verification_code_hash.name
3115
+ ]
3116
+
3117
+ if not bcrypt.checkpw(
3118
+ reset_email_code.encode("utf-8"),
3119
+ latest_hashed_verification_code.encode("utf-8"),
3120
+ ):
3121
+ output_content = get_api_output_in_standard_format(
3122
+ message=messages["INCORRECT_VERIFICATION_CODE"],
3123
+ log=f"incorrect reset_email_code: {reset_email_code} for user_id: {user_id}.",
3124
+ )
3125
+ raise HTTPException(
3126
+ status_code=status.HTTP_400_BAD_REQUEST,
3127
+ detail=output_content,
3128
+ )
3129
+
3130
+ # hash the new password
3131
+ local_str_hashed_password = bcrypt.hashpw(
3132
+ new_password.encode("utf-8"), bcrypt.gensalt()
3133
+ ).decode("utf-8")
3134
+ # update the password
3135
+ global_object_square_database_helper.edit_rows_v0(
3136
+ database_name=global_string_database_name,
3137
+ schema_name=global_string_schema_name,
3138
+ table_name=UserCredential.__tablename__,
3139
+ filters=FiltersV0(
3140
+ root={
3141
+ UserCredential.user_id.name: FilterConditionsV0(eq=user_id),
3142
+ }
3143
+ ),
3144
+ data={
3145
+ UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
3146
+ },
3147
+ )
3148
+ # mark the email code as used
3149
+ global_object_square_database_helper.edit_rows_v0(
3150
+ database_name=global_string_database_name,
3151
+ schema_name=global_string_schema_name,
3152
+ table_name=UserVerificationCode.__tablename__,
3153
+ filters=FiltersV0(
3154
+ root={
3155
+ UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
3156
+ UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
3157
+ eq=VerificationCodeTypeEnum.EMAIL_RECOVERY.value
3158
+ ),
3159
+ UserVerificationCode.user_verification_code_hash.name: FilterConditionsV0(
3160
+ eq=latest_hashed_verification_code
3161
+ ),
3162
+ }
3163
+ ),
3164
+ data={
3165
+ UserVerificationCode.user_verification_code_used_at.name: datetime.now(
3166
+ timezone.utc
3167
+ ).strftime("%Y-%m-%d %H:%M:%S.%f+00"),
3168
+ },
3169
+ )
3170
+ if logout_other_sessions:
3171
+ # delete all sessions for user
3172
+ global_object_square_database_helper.delete_rows_v0(
3173
+ database_name=global_string_database_name,
3174
+ schema_name=global_string_schema_name,
3175
+ table_name=UserSession.__tablename__,
3176
+ filters=FiltersV0(
3177
+ root={
3178
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
3179
+ }
3180
+ ),
3181
+ )
3182
+ # generate access token and refresh token
3183
+ local_dict_access_token_payload = {
3184
+ "app_id": app_id,
3185
+ "user_id": user_id,
3186
+ "exp": datetime.now(timezone.utc)
3187
+ + timedelta(minutes=config_int_access_token_valid_minutes),
3188
+ }
3189
+ local_str_access_token = jwt.encode(
3190
+ local_dict_access_token_payload, config_str_secret_key_for_access_token
3191
+ )
3192
+ local_object_refresh_token_expiry_time = datetime.now(timezone.utc) + timedelta(
3193
+ minutes=config_int_refresh_token_valid_minutes
3194
+ )
3195
+ local_dict_refresh_token_payload = {
3196
+ "app_id": app_id,
3197
+ "user_id": user_id,
3198
+ "exp": local_object_refresh_token_expiry_time,
3199
+ }
3200
+ local_str_refresh_token = jwt.encode(
3201
+ local_dict_refresh_token_payload, config_str_secret_key_for_refresh_token
3202
+ )
3203
+ # insert the refresh token in the database
3204
+ global_object_square_database_helper.insert_rows_v0(
3205
+ database_name=global_string_database_name,
3206
+ schema_name=global_string_schema_name,
3207
+ table_name=UserSession.__tablename__,
3208
+ data=[
3209
+ {
3210
+ UserSession.user_id.name: user_id,
3211
+ UserSession.app_id.name: app_id,
3212
+ UserSession.user_session_refresh_token.name: local_str_refresh_token,
3213
+ UserSession.user_session_expiry_time.name: local_object_refresh_token_expiry_time.strftime(
3214
+ "%Y-%m-%d %H:%M:%S.%f+00"
3215
+ ),
3216
+ }
3217
+ ],
3218
+ )
3219
+ """
3220
+ return value
3221
+ """
3222
+ output_content = get_api_output_in_standard_format(
3223
+ message=messages["GENERIC_ACTION_SUCCESSFUL"],
3224
+ data={
3225
+ "main": {
3226
+ "user_id": user_id,
3227
+ "access_token": local_str_access_token,
3228
+ "refresh_token": local_str_refresh_token,
3229
+ "refresh_token_expiry_time": local_object_refresh_token_expiry_time.isoformat(),
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
+ )