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