square-authentication 6.2.2__py3-none-any.whl → 8.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- square_authentication/configuration.py +37 -0
- square_authentication/data/config.sample.ini +14 -1
- square_authentication/data/config.testing.sample.ini +14 -1
- square_authentication/messages.py +12 -0
- square_authentication/pydantic_models/core.py +28 -0
- square_authentication/pydantic_models/profile.py +5 -0
- square_authentication/routes/core.py +1611 -46
- square_authentication/routes/profile.py +453 -4
- square_authentication/utils/core.py +37 -0
- {square_authentication-6.2.2.dist-info → square_authentication-8.0.0.dist-info}/METADATA +56 -2
- square_authentication-8.0.0.dist-info/RECORD +21 -0
- {square_authentication-6.2.2.dist-info → square_authentication-8.0.0.dist-info}/WHEEL +1 -1
- square_authentication-6.2.2.dist-info/RECORD +0 -19
- {square_authentication-6.2.2.dist-info → square_authentication-8.0.0.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,9 @@
|
|
1
1
|
import copy
|
2
|
+
import io
|
3
|
+
import mimetypes
|
4
|
+
import random
|
2
5
|
import re
|
6
|
+
import uuid
|
3
7
|
from datetime import datetime, timedelta, timezone
|
4
8
|
from typing import Annotated, List
|
5
9
|
|
@@ -8,12 +12,19 @@ import jwt
|
|
8
12
|
from fastapi import APIRouter, Header, HTTPException, status
|
9
13
|
from fastapi.params import Query
|
10
14
|
from fastapi.responses import JSONResponse
|
15
|
+
from google.auth.transport import requests as google_requests
|
16
|
+
from google.oauth2 import id_token
|
11
17
|
from requests import HTTPError
|
12
|
-
from square_commons import get_api_output_in_standard_format
|
18
|
+
from square_commons import get_api_output_in_standard_format, send_email_using_mailgun
|
19
|
+
from square_commons.api_utils import make_request
|
13
20
|
from square_database_helper.pydantic_models import FilterConditionsV0, FiltersV0
|
14
21
|
from square_database_structure.square import global_string_database_name
|
15
22
|
from square_database_structure.square.authentication import global_string_schema_name
|
16
|
-
from square_database_structure.square.authentication.enums import
|
23
|
+
from square_database_structure.square.authentication.enums import (
|
24
|
+
RecoveryMethodEnum,
|
25
|
+
AuthProviderEnum,
|
26
|
+
VerificationCodeTypeEnum,
|
27
|
+
)
|
17
28
|
from square_database_structure.square.authentication.tables import (
|
18
29
|
User,
|
19
30
|
UserApp,
|
@@ -21,7 +32,14 @@ from square_database_structure.square.authentication.tables import (
|
|
21
32
|
UserSession,
|
22
33
|
UserProfile,
|
23
34
|
UserRecoveryMethod,
|
35
|
+
UserAuthProvider,
|
36
|
+
UserVerificationCode,
|
37
|
+
)
|
38
|
+
from square_database_structure.square.email import (
|
39
|
+
global_string_schema_name as email_schema_name,
|
24
40
|
)
|
41
|
+
from square_database_structure.square.email.enums import EmailTypeEnum, EmailStatusEnum
|
42
|
+
from square_database_structure.square.email.tables import EmailLog
|
25
43
|
from square_database_structure.square.public import (
|
26
44
|
global_string_schema_name as global_string_public_schema_name,
|
27
45
|
)
|
@@ -34,6 +52,12 @@ from square_authentication.configuration import (
|
|
34
52
|
config_str_secret_key_for_refresh_token,
|
35
53
|
global_object_square_logger,
|
36
54
|
global_object_square_database_helper,
|
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,
|
37
61
|
)
|
38
62
|
from square_authentication.messages import messages
|
39
63
|
from square_authentication.pydantic_models.core import (
|
@@ -43,7 +67,12 @@ from square_authentication.pydantic_models.core import (
|
|
43
67
|
RegisterUsernameV0,
|
44
68
|
TokenType,
|
45
69
|
UpdatePasswordV0,
|
70
|
+
ResetPasswordAndLoginUsingBackupCodeV0,
|
71
|
+
SendResetPasswordEmailV0,
|
72
|
+
ResetPasswordAndLoginUsingResetEmailCodeV0,
|
73
|
+
RegisterLoginGoogleV0,
|
46
74
|
)
|
75
|
+
from square_authentication.utils.core import generate_default_username_for_google_users
|
47
76
|
from square_authentication.utils.token import get_jwt_payload
|
48
77
|
|
49
78
|
router = APIRouter(
|
@@ -86,13 +115,9 @@ async def register_username_v0(
|
|
86
115
|
global_object_square_database_helper.get_rows_v0(
|
87
116
|
database_name=global_string_database_name,
|
88
117
|
schema_name=global_string_schema_name,
|
89
|
-
table_name=
|
118
|
+
table_name=User.__tablename__,
|
90
119
|
filters=FiltersV0(
|
91
|
-
root={
|
92
|
-
UserProfile.user_profile_username.name: FilterConditionsV0(
|
93
|
-
eq=username
|
94
|
-
)
|
95
|
-
}
|
120
|
+
root={User.user_username.name: FilterConditionsV0(eq=username)}
|
96
121
|
),
|
97
122
|
)["data"]["main"]
|
98
123
|
)
|
@@ -111,13 +136,42 @@ async def register_username_v0(
|
|
111
136
|
"""
|
112
137
|
# entry in user table
|
113
138
|
local_list_response_user = global_object_square_database_helper.insert_rows_v0(
|
114
|
-
data=[
|
139
|
+
data=[
|
140
|
+
{
|
141
|
+
User.user_username.name: username,
|
142
|
+
}
|
143
|
+
],
|
115
144
|
database_name=global_string_database_name,
|
116
145
|
schema_name=global_string_schema_name,
|
117
146
|
table_name=User.__tablename__,
|
118
147
|
)["data"]["main"]
|
119
148
|
local_str_user_id = local_list_response_user[0][User.user_id.name]
|
120
149
|
|
150
|
+
# entry in user auth provider table
|
151
|
+
global_object_square_database_helper.insert_rows_v0(
|
152
|
+
data=[
|
153
|
+
{
|
154
|
+
UserAuthProvider.user_id.name: local_str_user_id,
|
155
|
+
UserAuthProvider.auth_provider.name: AuthProviderEnum.SELF.value,
|
156
|
+
}
|
157
|
+
],
|
158
|
+
database_name=global_string_database_name,
|
159
|
+
schema_name=global_string_schema_name,
|
160
|
+
table_name=UserAuthProvider.__tablename__,
|
161
|
+
)
|
162
|
+
|
163
|
+
# entry in user profile table
|
164
|
+
global_object_square_database_helper.insert_rows_v0(
|
165
|
+
database_name=global_string_database_name,
|
166
|
+
schema_name=global_string_schema_name,
|
167
|
+
table_name=UserProfile.__tablename__,
|
168
|
+
data=[
|
169
|
+
{
|
170
|
+
UserProfile.user_id.name: local_str_user_id,
|
171
|
+
}
|
172
|
+
],
|
173
|
+
)
|
174
|
+
|
121
175
|
# entry in credential table
|
122
176
|
|
123
177
|
# hash password
|
@@ -136,17 +190,6 @@ async def register_username_v0(
|
|
136
190
|
schema_name=global_string_schema_name,
|
137
191
|
table_name=UserCredential.__tablename__,
|
138
192
|
)
|
139
|
-
global_object_square_database_helper.insert_rows_v0(
|
140
|
-
data=[
|
141
|
-
{
|
142
|
-
UserProfile.user_id.name: local_str_user_id,
|
143
|
-
UserProfile.user_profile_username.name: username,
|
144
|
-
}
|
145
|
-
],
|
146
|
-
database_name=global_string_database_name,
|
147
|
-
schema_name=global_string_schema_name,
|
148
|
-
table_name=UserProfile.__tablename__,
|
149
|
-
)
|
150
193
|
if app_id is not None:
|
151
194
|
# assign app to user
|
152
195
|
global_object_square_database_helper.insert_rows_v0(
|
@@ -252,6 +295,355 @@ async def register_username_v0(
|
|
252
295
|
)
|
253
296
|
|
254
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
|
+
|
255
647
|
@router.get("/get_user_details/v0")
|
256
648
|
@global_object_square_logger.auto_logger()
|
257
649
|
async def get_user_details_v0(
|
@@ -552,29 +944,52 @@ async def login_username_v0(body: LoginUsernameV0):
|
|
552
944
|
validation
|
553
945
|
"""
|
554
946
|
# validation for username
|
555
|
-
|
947
|
+
# check if user with username exists
|
948
|
+
local_list_response_user = global_object_square_database_helper.get_rows_v0(
|
949
|
+
database_name=global_string_database_name,
|
950
|
+
schema_name=global_string_schema_name,
|
951
|
+
table_name=User.__tablename__,
|
952
|
+
filters=FiltersV0(
|
953
|
+
root={User.user_username.name: FilterConditionsV0(eq=username)}
|
954
|
+
),
|
955
|
+
)["data"]["main"]
|
956
|
+
if len(local_list_response_user) != 1:
|
957
|
+
output_content = get_api_output_in_standard_format(
|
958
|
+
message=messages["INCORRECT_USERNAME"],
|
959
|
+
log=f"incorrect username {username}",
|
960
|
+
)
|
961
|
+
raise HTTPException(
|
962
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
963
|
+
detail=output_content,
|
964
|
+
)
|
965
|
+
# check if user has auth provider as SELF
|
966
|
+
local_list_user_auth_provider_response = (
|
556
967
|
global_object_square_database_helper.get_rows_v0(
|
557
968
|
database_name=global_string_database_name,
|
558
969
|
schema_name=global_string_schema_name,
|
559
|
-
table_name=
|
970
|
+
table_name=UserAuthProvider.__tablename__,
|
560
971
|
filters=FiltersV0(
|
561
972
|
root={
|
562
|
-
|
563
|
-
eq=
|
564
|
-
)
|
973
|
+
UserAuthProvider.user_id.name: FilterConditionsV0(
|
974
|
+
eq=local_list_response_user[0][User.user_id.name]
|
975
|
+
),
|
976
|
+
UserAuthProvider.auth_provider.name: FilterConditionsV0(
|
977
|
+
eq=AuthProviderEnum.SELF.value
|
978
|
+
),
|
565
979
|
}
|
566
980
|
),
|
567
981
|
)["data"]["main"]
|
568
982
|
)
|
569
|
-
if len(
|
983
|
+
if len(local_list_user_auth_provider_response) != 1:
|
570
984
|
output_content = get_api_output_in_standard_format(
|
571
|
-
message=messages["
|
572
|
-
log=f"
|
985
|
+
message=messages["INCORRECT_AUTH_PROVIDER"],
|
986
|
+
log=f"{username} not linked with {AuthProviderEnum.SELF.value} auth provider.",
|
573
987
|
)
|
574
988
|
raise HTTPException(
|
575
989
|
status_code=status.HTTP_400_BAD_REQUEST,
|
576
990
|
detail=output_content,
|
577
991
|
)
|
992
|
+
# check if user has credentials (might not be set in case of errors in registration.)
|
578
993
|
local_list_authentication_user_response = (
|
579
994
|
global_object_square_database_helper.get_rows_v0(
|
580
995
|
database_name=global_string_database_name,
|
@@ -583,9 +998,7 @@ async def login_username_v0(body: LoginUsernameV0):
|
|
583
998
|
filters=FiltersV0(
|
584
999
|
root={
|
585
1000
|
UserCredential.user_id.name: FilterConditionsV0(
|
586
|
-
eq=
|
587
|
-
UserProfile.user_id.name
|
588
|
-
]
|
1001
|
+
eq=local_list_response_user[0][User.user_id.name]
|
589
1002
|
)
|
590
1003
|
}
|
591
1004
|
),
|
@@ -593,8 +1006,8 @@ async def login_username_v0(body: LoginUsernameV0):
|
|
593
1006
|
)
|
594
1007
|
if len(local_list_authentication_user_response) != 1:
|
595
1008
|
output_content = get_api_output_in_standard_format(
|
596
|
-
message=messages["
|
597
|
-
log=f"
|
1009
|
+
message=messages["MALFORMED_USER"],
|
1010
|
+
log=f"username: {username} does not have credentials set.",
|
598
1011
|
)
|
599
1012
|
raise HTTPException(
|
600
1013
|
status_code=status.HTTP_400_BAD_REQUEST,
|
@@ -1155,12 +1568,10 @@ async def update_username_v0(
|
|
1155
1568
|
global_object_square_database_helper.get_rows_v0(
|
1156
1569
|
database_name=global_string_database_name,
|
1157
1570
|
schema_name=global_string_schema_name,
|
1158
|
-
table_name=
|
1571
|
+
table_name=User.__tablename__,
|
1159
1572
|
filters=FiltersV0(
|
1160
1573
|
root={
|
1161
|
-
|
1162
|
-
eq=new_username
|
1163
|
-
),
|
1574
|
+
User.user_username.name: FilterConditionsV0(eq=new_username),
|
1164
1575
|
}
|
1165
1576
|
),
|
1166
1577
|
)["data"]["main"]
|
@@ -1181,14 +1592,14 @@ async def update_username_v0(
|
|
1181
1592
|
global_object_square_database_helper.edit_rows_v0(
|
1182
1593
|
database_name=global_string_database_name,
|
1183
1594
|
schema_name=global_string_schema_name,
|
1184
|
-
table_name=
|
1595
|
+
table_name=User.__tablename__,
|
1185
1596
|
filters=FiltersV0(
|
1186
1597
|
root={
|
1187
|
-
|
1598
|
+
User.user_id.name: FilterConditionsV0(eq=user_id),
|
1188
1599
|
}
|
1189
1600
|
),
|
1190
1601
|
data={
|
1191
|
-
|
1602
|
+
User.user_username.name: new_username,
|
1192
1603
|
},
|
1193
1604
|
)
|
1194
1605
|
"""
|
@@ -1218,7 +1629,7 @@ async def update_username_v0(
|
|
1218
1629
|
)
|
1219
1630
|
|
1220
1631
|
|
1221
|
-
@router.
|
1632
|
+
@router.post("/delete_user/v0")
|
1222
1633
|
@global_object_square_logger.auto_logger()
|
1223
1634
|
async def delete_user_v0(
|
1224
1635
|
body: DeleteUserV0,
|
@@ -1286,8 +1697,19 @@ async def delete_user_v0(
|
|
1286
1697
|
"""
|
1287
1698
|
main process
|
1288
1699
|
"""
|
1289
|
-
#
|
1290
|
-
global_object_square_database_helper.
|
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
|
+
|
1711
|
+
# delete the user.
|
1712
|
+
global_object_square_database_helper.delete_rows_v0(
|
1291
1713
|
database_name=global_string_database_name,
|
1292
1714
|
schema_name=global_string_schema_name,
|
1293
1715
|
table_name=User.__tablename__,
|
@@ -1297,6 +1719,18 @@ async def delete_user_v0(
|
|
1297
1719
|
}
|
1298
1720
|
),
|
1299
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
|
+
)
|
1300
1734
|
"""
|
1301
1735
|
return value
|
1302
1736
|
"""
|
@@ -1332,6 +1766,8 @@ async def update_password_v0(
|
|
1332
1766
|
):
|
1333
1767
|
old_password = body.old_password
|
1334
1768
|
new_password = body.new_password
|
1769
|
+
logout_other_sessions = body.logout_other_sessions
|
1770
|
+
preserve_session_refresh_token = body.preserve_session_refresh_token
|
1335
1771
|
try:
|
1336
1772
|
"""
|
1337
1773
|
validation
|
@@ -1371,7 +1807,47 @@ async def update_password_v0(
|
|
1371
1807
|
status_code=status.HTTP_400_BAD_REQUEST,
|
1372
1808
|
detail=output_content,
|
1373
1809
|
)
|
1374
|
-
|
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
|
+
)
|
1375
1851
|
# validate password
|
1376
1852
|
local_dict_user = local_list_authentication_user_response[0]
|
1377
1853
|
if not (
|
@@ -1390,10 +1866,41 @@ async def update_password_v0(
|
|
1390
1866
|
status_code=status.HTTP_400_BAD_REQUEST,
|
1391
1867
|
detail=output_content,
|
1392
1868
|
)
|
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
|
+
)
|
1393
1900
|
"""
|
1394
1901
|
main process
|
1395
1902
|
"""
|
1396
|
-
#
|
1903
|
+
# update the password
|
1397
1904
|
local_str_hashed_password = bcrypt.hashpw(
|
1398
1905
|
new_password.encode("utf-8"), bcrypt.gensalt()
|
1399
1906
|
).decode("utf-8")
|
@@ -1410,6 +1917,34 @@ async def update_password_v0(
|
|
1410
1917
|
UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
|
1411
1918
|
},
|
1412
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
|
+
)
|
1413
1948
|
"""
|
1414
1949
|
return value
|
1415
1950
|
"""
|
@@ -1581,7 +2116,44 @@ async def update_user_recovery_methods_v0(
|
|
1581
2116
|
status_code=status.HTTP_400_BAD_REQUEST,
|
1582
2117
|
detail=output_content,
|
1583
2118
|
)
|
1584
|
-
|
2119
|
+
# check if user's email is verified in user profile.
|
2120
|
+
# maybe too harsh to reject the request entirely,
|
2121
|
+
# but for practical purposes this api call should be used for 1 recovery method at a time, so it's not too bad.
|
2122
|
+
if RecoveryMethodEnum.EMAIL.value in recovery_methods_to_add:
|
2123
|
+
local_list_response_user_profile = (
|
2124
|
+
global_object_square_database_helper.get_rows_v0(
|
2125
|
+
database_name=global_string_database_name,
|
2126
|
+
schema_name=global_string_schema_name,
|
2127
|
+
table_name=UserProfile.__tablename__,
|
2128
|
+
filters=FiltersV0(
|
2129
|
+
root={
|
2130
|
+
UserProfile.user_id.name: FilterConditionsV0(eq=user_id),
|
2131
|
+
}
|
2132
|
+
),
|
2133
|
+
)["data"]["main"]
|
2134
|
+
)
|
2135
|
+
if len(local_list_response_user_profile) != 1:
|
2136
|
+
# maybe this should raise 500 as this error will not occur if code runs correctly.
|
2137
|
+
output_content = get_api_output_in_standard_format(
|
2138
|
+
message=messages["GENERIC_400"],
|
2139
|
+
log=f"user_id: {user_id} does not have a profile.",
|
2140
|
+
)
|
2141
|
+
raise HTTPException(
|
2142
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2143
|
+
detail=output_content,
|
2144
|
+
)
|
2145
|
+
local_dict_user_profile = local_list_response_user_profile[0]
|
2146
|
+
if not local_dict_user_profile[
|
2147
|
+
UserProfile.user_profile_email_verified.name
|
2148
|
+
]:
|
2149
|
+
output_content = get_api_output_in_standard_format(
|
2150
|
+
message=messages["EMAIL_NOT_VERIFIED"],
|
2151
|
+
log=f"user_id: {user_id} does not have email verified.",
|
2152
|
+
)
|
2153
|
+
raise HTTPException(
|
2154
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2155
|
+
detail=output_content,
|
2156
|
+
)
|
1585
2157
|
"""
|
1586
2158
|
main process
|
1587
2159
|
"""
|
@@ -1619,6 +2191,28 @@ async def update_user_recovery_methods_v0(
|
|
1619
2191
|
)
|
1620
2192
|
|
1621
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"]
|
1622
2216
|
global_object_square_database_helper.delete_rows_v0(
|
1623
2217
|
database_name=global_string_database_name,
|
1624
2218
|
schema_name=global_string_schema_name,
|
@@ -1632,6 +2226,22 @@ async def update_user_recovery_methods_v0(
|
|
1632
2226
|
}
|
1633
2227
|
),
|
1634
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
|
+
)
|
1635
2245
|
|
1636
2246
|
"""
|
1637
2247
|
return value
|
@@ -1680,3 +2290,958 @@ async def update_user_recovery_methods_v0(
|
|
1680
2290
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
1681
2291
|
content=output_content,
|
1682
2292
|
)
|
2293
|
+
|
2294
|
+
|
2295
|
+
@router.post("/generate_account_backup_codes/v0")
|
2296
|
+
@global_object_square_logger.auto_logger()
|
2297
|
+
async def generate_account_backup_codes_v0(
|
2298
|
+
access_token: Annotated[str, Header()],
|
2299
|
+
):
|
2300
|
+
|
2301
|
+
try:
|
2302
|
+
"""
|
2303
|
+
validation
|
2304
|
+
"""
|
2305
|
+
try:
|
2306
|
+
local_dict_access_token_payload = get_jwt_payload(
|
2307
|
+
access_token, config_str_secret_key_for_access_token
|
2308
|
+
)
|
2309
|
+
except Exception as error:
|
2310
|
+
output_content = get_api_output_in_standard_format(
|
2311
|
+
message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
|
2312
|
+
)
|
2313
|
+
raise HTTPException(
|
2314
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2315
|
+
detail=output_content,
|
2316
|
+
)
|
2317
|
+
user_id = local_dict_access_token_payload["user_id"]
|
2318
|
+
# check if user has recovery method enabled
|
2319
|
+
local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
|
2320
|
+
database_name=global_string_database_name,
|
2321
|
+
schema_name=global_string_schema_name,
|
2322
|
+
table_name=UserRecoveryMethod.__tablename__,
|
2323
|
+
filters=FiltersV0(
|
2324
|
+
root={
|
2325
|
+
UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
|
2326
|
+
UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
|
2327
|
+
eq=RecoveryMethodEnum.BACKUP_CODE.value
|
2328
|
+
),
|
2329
|
+
}
|
2330
|
+
),
|
2331
|
+
)[
|
2332
|
+
"data"
|
2333
|
+
][
|
2334
|
+
"main"
|
2335
|
+
]
|
2336
|
+
if len(local_list_response_user_recovery_methods) != 1:
|
2337
|
+
output_content = get_api_output_in_standard_format(
|
2338
|
+
message=messages["RECOVERY_METHOD_NOT_ENABLED"],
|
2339
|
+
log=f"user_id: {user_id} does not have backup codes recovery method enabled.",
|
2340
|
+
)
|
2341
|
+
raise HTTPException(
|
2342
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2343
|
+
detail=output_content,
|
2344
|
+
)
|
2345
|
+
"""
|
2346
|
+
main process
|
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
|
+
|
2364
|
+
# generate backup codes
|
2365
|
+
backup_codes = []
|
2366
|
+
db_data = []
|
2367
|
+
|
2368
|
+
for i in range(NUMBER_OF_RECOVERY_CODES):
|
2369
|
+
backup_code = str(uuid.uuid4())
|
2370
|
+
backup_codes.append(backup_code)
|
2371
|
+
# hash the backup code
|
2372
|
+
local_str_hashed_backup_code = bcrypt.hashpw(
|
2373
|
+
backup_code.encode("utf-8"), bcrypt.gensalt()
|
2374
|
+
).decode("utf-8")
|
2375
|
+
|
2376
|
+
db_data.append(
|
2377
|
+
{
|
2378
|
+
UserVerificationCode.user_id.name: user_id,
|
2379
|
+
UserVerificationCode.user_verification_code_type.name: VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value,
|
2380
|
+
UserVerificationCode.user_verification_code_hash.name: local_str_hashed_backup_code,
|
2381
|
+
}
|
2382
|
+
)
|
2383
|
+
global_object_square_database_helper.insert_rows_v0(
|
2384
|
+
database_name=global_string_database_name,
|
2385
|
+
schema_name=global_string_schema_name,
|
2386
|
+
table_name=UserVerificationCode.__tablename__,
|
2387
|
+
data=db_data,
|
2388
|
+
)
|
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
|
+
)
|
2404
|
+
"""
|
2405
|
+
return value
|
2406
|
+
"""
|
2407
|
+
output_content = get_api_output_in_standard_format(
|
2408
|
+
message=messages["GENERIC_CREATION_SUCCESSFUL"],
|
2409
|
+
data={
|
2410
|
+
"main": {
|
2411
|
+
"user_id": user_id,
|
2412
|
+
"backup_codes": backup_codes,
|
2413
|
+
}
|
2414
|
+
},
|
2415
|
+
)
|
2416
|
+
return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
|
2417
|
+
except HTTPException as http_exception:
|
2418
|
+
global_object_square_logger.logger.error(http_exception, exc_info=True)
|
2419
|
+
return JSONResponse(
|
2420
|
+
status_code=http_exception.status_code, content=http_exception.detail
|
2421
|
+
)
|
2422
|
+
except Exception as e:
|
2423
|
+
"""
|
2424
|
+
rollback logic
|
2425
|
+
"""
|
2426
|
+
global_object_square_logger.logger.error(e, exc_info=True)
|
2427
|
+
output_content = get_api_output_in_standard_format(
|
2428
|
+
message=messages["GENERIC_500"],
|
2429
|
+
log=str(e),
|
2430
|
+
)
|
2431
|
+
return JSONResponse(
|
2432
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
|
2433
|
+
)
|
2434
|
+
|
2435
|
+
|
2436
|
+
@router.post("/reset_password_and_login_using_backup_code/v0")
|
2437
|
+
@global_object_square_logger.auto_logger()
|
2438
|
+
async def reset_password_and_login_using_backup_code_v0(
|
2439
|
+
body: ResetPasswordAndLoginUsingBackupCodeV0,
|
2440
|
+
):
|
2441
|
+
backup_code = body.backup_code
|
2442
|
+
username = body.username
|
2443
|
+
new_password = body.new_password
|
2444
|
+
app_id = body.app_id
|
2445
|
+
logout_other_sessions = body.logout_other_sessions
|
2446
|
+
try:
|
2447
|
+
"""
|
2448
|
+
validation
|
2449
|
+
"""
|
2450
|
+
# validate username
|
2451
|
+
local_list_authentication_user_response = (
|
2452
|
+
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=User.__tablename__,
|
2456
|
+
filters=FiltersV0(
|
2457
|
+
root={User.user_username.name: FilterConditionsV0(eq=username)}
|
2458
|
+
),
|
2459
|
+
)["data"]["main"]
|
2460
|
+
)
|
2461
|
+
if len(local_list_authentication_user_response) != 1:
|
2462
|
+
output_content = get_api_output_in_standard_format(
|
2463
|
+
message=messages["INCORRECT_USERNAME"],
|
2464
|
+
log=f"incorrect username: {username}.",
|
2465
|
+
)
|
2466
|
+
raise HTTPException(
|
2467
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2468
|
+
detail=output_content,
|
2469
|
+
)
|
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
|
+
)
|
2496
|
+
# check if user has recovery method enabled
|
2497
|
+
local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
|
2498
|
+
database_name=global_string_database_name,
|
2499
|
+
schema_name=global_string_schema_name,
|
2500
|
+
table_name=UserRecoveryMethod.__tablename__,
|
2501
|
+
filters=FiltersV0(
|
2502
|
+
root={
|
2503
|
+
UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
|
2504
|
+
UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
|
2505
|
+
eq=RecoveryMethodEnum.BACKUP_CODE.value
|
2506
|
+
),
|
2507
|
+
}
|
2508
|
+
),
|
2509
|
+
)[
|
2510
|
+
"data"
|
2511
|
+
][
|
2512
|
+
"main"
|
2513
|
+
]
|
2514
|
+
if len(local_list_response_user_recovery_methods) != 1:
|
2515
|
+
output_content = get_api_output_in_standard_format(
|
2516
|
+
message=messages["RECOVERY_METHOD_NOT_ENABLED"],
|
2517
|
+
log=f"user_id: {user_id} does not have backup codes recovery method enabled.",
|
2518
|
+
)
|
2519
|
+
raise HTTPException(
|
2520
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2521
|
+
detail=output_content,
|
2522
|
+
)
|
2523
|
+
# validate if user is assigned to the app.
|
2524
|
+
# not checking [skipping] if the app exists, as it is not required for this endpoint.
|
2525
|
+
local_list_response_user_app = global_object_square_database_helper.get_rows_v0(
|
2526
|
+
database_name=global_string_database_name,
|
2527
|
+
schema_name=global_string_schema_name,
|
2528
|
+
table_name=UserApp.__tablename__,
|
2529
|
+
filters=FiltersV0(
|
2530
|
+
root={
|
2531
|
+
UserApp.user_id.name: FilterConditionsV0(eq=user_id),
|
2532
|
+
UserApp.app_id.name: FilterConditionsV0(eq=app_id),
|
2533
|
+
}
|
2534
|
+
),
|
2535
|
+
)["data"]["main"]
|
2536
|
+
if len(local_list_response_user_app) == 0:
|
2537
|
+
output_content = get_api_output_in_standard_format(
|
2538
|
+
message=messages["GENERIC_400"],
|
2539
|
+
log=f"user_id: {user_id} is not assigned to app_id: {app_id}.",
|
2540
|
+
)
|
2541
|
+
raise HTTPException(
|
2542
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2543
|
+
detail=output_content,
|
2544
|
+
)
|
2545
|
+
"""
|
2546
|
+
main process
|
2547
|
+
"""
|
2548
|
+
# validate backup code
|
2549
|
+
local_list_response_user_verification_code = global_object_square_database_helper.get_rows_v0(
|
2550
|
+
database_name=global_string_database_name,
|
2551
|
+
schema_name=global_string_schema_name,
|
2552
|
+
table_name=UserVerificationCode.__tablename__,
|
2553
|
+
filters=FiltersV0(
|
2554
|
+
root={
|
2555
|
+
UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
|
2556
|
+
UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
|
2557
|
+
eq=VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value
|
2558
|
+
),
|
2559
|
+
UserVerificationCode.user_verification_code_expires_at.name: FilterConditionsV0(
|
2560
|
+
is_null=True
|
2561
|
+
),
|
2562
|
+
UserVerificationCode.user_verification_code_used_at.name: FilterConditionsV0(
|
2563
|
+
is_null=True
|
2564
|
+
),
|
2565
|
+
}
|
2566
|
+
),
|
2567
|
+
columns=[UserVerificationCode.user_verification_code_hash.name],
|
2568
|
+
)[
|
2569
|
+
"data"
|
2570
|
+
][
|
2571
|
+
"main"
|
2572
|
+
]
|
2573
|
+
# find the backup code in the list
|
2574
|
+
local_list_response_user_verification_code = [
|
2575
|
+
x
|
2576
|
+
for x in local_list_response_user_verification_code
|
2577
|
+
if bcrypt.checkpw(
|
2578
|
+
backup_code.encode("utf-8"),
|
2579
|
+
x[UserVerificationCode.user_verification_code_hash.name].encode(
|
2580
|
+
"utf-8"
|
2581
|
+
),
|
2582
|
+
)
|
2583
|
+
]
|
2584
|
+
if len(local_list_response_user_verification_code) != 1:
|
2585
|
+
output_content = get_api_output_in_standard_format(
|
2586
|
+
message=messages["INCORRECT_BACKUP_CODE"],
|
2587
|
+
log=f"incorrect backup code: {backup_code} for user_id: {user_id}.",
|
2588
|
+
)
|
2589
|
+
raise HTTPException(
|
2590
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2591
|
+
detail=output_content,
|
2592
|
+
)
|
2593
|
+
# hash the new password
|
2594
|
+
local_str_hashed_password = bcrypt.hashpw(
|
2595
|
+
new_password.encode("utf-8"), bcrypt.gensalt()
|
2596
|
+
).decode("utf-8")
|
2597
|
+
# update the password
|
2598
|
+
global_object_square_database_helper.edit_rows_v0(
|
2599
|
+
database_name=global_string_database_name,
|
2600
|
+
schema_name=global_string_schema_name,
|
2601
|
+
table_name=UserCredential.__tablename__,
|
2602
|
+
filters=FiltersV0(
|
2603
|
+
root={
|
2604
|
+
UserCredential.user_id.name: FilterConditionsV0(eq=user_id),
|
2605
|
+
}
|
2606
|
+
),
|
2607
|
+
data={
|
2608
|
+
UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
|
2609
|
+
},
|
2610
|
+
)
|
2611
|
+
# mark the backup code as used
|
2612
|
+
global_object_square_database_helper.edit_rows_v0(
|
2613
|
+
database_name=global_string_database_name,
|
2614
|
+
schema_name=global_string_schema_name,
|
2615
|
+
table_name=UserVerificationCode.__tablename__,
|
2616
|
+
filters=FiltersV0(
|
2617
|
+
root={
|
2618
|
+
UserVerificationCode.user_id.name: FilterConditionsV0(eq=user_id),
|
2619
|
+
UserVerificationCode.user_verification_code_type.name: FilterConditionsV0(
|
2620
|
+
eq=VerificationCodeTypeEnum.BACKUP_CODE_RECOVERY.value
|
2621
|
+
),
|
2622
|
+
UserVerificationCode.user_verification_code_hash.name: FilterConditionsV0(
|
2623
|
+
eq=local_list_response_user_verification_code[0][
|
2624
|
+
UserVerificationCode.user_verification_code_hash.name
|
2625
|
+
]
|
2626
|
+
),
|
2627
|
+
}
|
2628
|
+
),
|
2629
|
+
data={
|
2630
|
+
UserVerificationCode.user_verification_code_used_at.name: datetime.now(
|
2631
|
+
timezone.utc
|
2632
|
+
).strftime("%Y-%m-%d %H:%M:%S.%f+00"),
|
2633
|
+
},
|
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
|
+
)
|
2647
|
+
# generate access token and refresh token
|
2648
|
+
local_dict_access_token_payload = {
|
2649
|
+
"app_id": app_id,
|
2650
|
+
"user_id": user_id,
|
2651
|
+
"exp": datetime.now(timezone.utc)
|
2652
|
+
+ timedelta(minutes=config_int_access_token_valid_minutes),
|
2653
|
+
}
|
2654
|
+
local_str_access_token = jwt.encode(
|
2655
|
+
local_dict_access_token_payload, config_str_secret_key_for_access_token
|
2656
|
+
)
|
2657
|
+
local_object_refresh_token_expiry_time = datetime.now(timezone.utc) + timedelta(
|
2658
|
+
minutes=config_int_refresh_token_valid_minutes
|
2659
|
+
)
|
2660
|
+
local_dict_refresh_token_payload = {
|
2661
|
+
"app_id": app_id,
|
2662
|
+
"user_id": user_id,
|
2663
|
+
"exp": local_object_refresh_token_expiry_time,
|
2664
|
+
}
|
2665
|
+
local_str_refresh_token = jwt.encode(
|
2666
|
+
local_dict_refresh_token_payload, config_str_secret_key_for_refresh_token
|
2667
|
+
)
|
2668
|
+
# insert the refresh token in the database
|
2669
|
+
global_object_square_database_helper.insert_rows_v0(
|
2670
|
+
database_name=global_string_database_name,
|
2671
|
+
schema_name=global_string_schema_name,
|
2672
|
+
table_name=UserSession.__tablename__,
|
2673
|
+
data=[
|
2674
|
+
{
|
2675
|
+
UserSession.user_id.name: user_id,
|
2676
|
+
UserSession.app_id.name: app_id,
|
2677
|
+
UserSession.user_session_refresh_token.name: local_str_refresh_token,
|
2678
|
+
UserSession.user_session_expiry_time.name: local_object_refresh_token_expiry_time.strftime(
|
2679
|
+
"%Y-%m-%d %H:%M:%S.%f+00"
|
2680
|
+
),
|
2681
|
+
}
|
2682
|
+
],
|
2683
|
+
)
|
2684
|
+
"""
|
2685
|
+
return value
|
2686
|
+
"""
|
2687
|
+
output_content = get_api_output_in_standard_format(
|
2688
|
+
message=messages["GENERIC_ACTION_SUCCESSFUL"],
|
2689
|
+
data={
|
2690
|
+
"main": {
|
2691
|
+
"user_id": user_id,
|
2692
|
+
"access_token": local_str_access_token,
|
2693
|
+
"refresh_token": local_str_refresh_token,
|
2694
|
+
}
|
2695
|
+
},
|
2696
|
+
)
|
2697
|
+
|
2698
|
+
return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
|
2699
|
+
except HTTPException as http_exception:
|
2700
|
+
global_object_square_logger.logger.error(http_exception, exc_info=True)
|
2701
|
+
return JSONResponse(
|
2702
|
+
status_code=http_exception.status_code, content=http_exception.detail
|
2703
|
+
)
|
2704
|
+
except Exception as e:
|
2705
|
+
"""
|
2706
|
+
rollback logic
|
2707
|
+
"""
|
2708
|
+
global_object_square_logger.logger.error(e, exc_info=True)
|
2709
|
+
output_content = get_api_output_in_standard_format(
|
2710
|
+
message=messages["GENERIC_500"],
|
2711
|
+
log=str(e),
|
2712
|
+
)
|
2713
|
+
return JSONResponse(
|
2714
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
|
2715
|
+
)
|
2716
|
+
|
2717
|
+
|
2718
|
+
@router.post("/send_reset_password_email/v0")
|
2719
|
+
@global_object_square_logger.auto_logger()
|
2720
|
+
async def send_reset_password_email_v0(
|
2721
|
+
body: SendResetPasswordEmailV0,
|
2722
|
+
):
|
2723
|
+
username = body.username
|
2724
|
+
try:
|
2725
|
+
"""
|
2726
|
+
validation
|
2727
|
+
"""
|
2728
|
+
# validate username
|
2729
|
+
local_list_authentication_user_response = (
|
2730
|
+
global_object_square_database_helper.get_rows_v0(
|
2731
|
+
database_name=global_string_database_name,
|
2732
|
+
schema_name=global_string_schema_name,
|
2733
|
+
table_name=User.__tablename__,
|
2734
|
+
filters=FiltersV0(
|
2735
|
+
root={User.user_username.name: FilterConditionsV0(eq=username)}
|
2736
|
+
),
|
2737
|
+
)["data"]["main"]
|
2738
|
+
)
|
2739
|
+
if len(local_list_authentication_user_response) != 1:
|
2740
|
+
output_content = get_api_output_in_standard_format(
|
2741
|
+
message=messages["INCORRECT_USERNAME"],
|
2742
|
+
log=f"incorrect username: {username}.",
|
2743
|
+
)
|
2744
|
+
raise HTTPException(
|
2745
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2746
|
+
detail=output_content,
|
2747
|
+
)
|
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
|
+
)
|
2774
|
+
# check if user has recovery method enabled
|
2775
|
+
local_list_response_user_recovery_methods = global_object_square_database_helper.get_rows_v0(
|
2776
|
+
database_name=global_string_database_name,
|
2777
|
+
schema_name=global_string_schema_name,
|
2778
|
+
table_name=UserRecoveryMethod.__tablename__,
|
2779
|
+
filters=FiltersV0(
|
2780
|
+
root={
|
2781
|
+
UserRecoveryMethod.user_id.name: FilterConditionsV0(eq=user_id),
|
2782
|
+
UserRecoveryMethod.user_recovery_method_name.name: FilterConditionsV0(
|
2783
|
+
eq=RecoveryMethodEnum.EMAIL.value
|
2784
|
+
),
|
2785
|
+
}
|
2786
|
+
),
|
2787
|
+
)[
|
2788
|
+
"data"
|
2789
|
+
][
|
2790
|
+
"main"
|
2791
|
+
]
|
2792
|
+
if len(local_list_response_user_recovery_methods) != 1:
|
2793
|
+
output_content = get_api_output_in_standard_format(
|
2794
|
+
message=messages["RECOVERY_METHOD_NOT_ENABLED"],
|
2795
|
+
log=f"user_id: {user_id} does not have email recovery method enabled.",
|
2796
|
+
)
|
2797
|
+
raise HTTPException(
|
2798
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2799
|
+
detail=output_content,
|
2800
|
+
)
|
2801
|
+
# validate if user has email in profile
|
2802
|
+
user_profile_response = global_object_square_database_helper.get_rows_v0(
|
2803
|
+
database_name=global_string_database_name,
|
2804
|
+
schema_name=global_string_schema_name,
|
2805
|
+
table_name=UserProfile.__tablename__,
|
2806
|
+
filters=FiltersV0(
|
2807
|
+
root={UserProfile.user_id.name: FilterConditionsV0(eq=user_id)}
|
2808
|
+
),
|
2809
|
+
apply_filters=True,
|
2810
|
+
)
|
2811
|
+
user_profile_data = user_profile_response["data"]["main"][0]
|
2812
|
+
if not user_profile_data.get(UserProfile.user_profile_email.name):
|
2813
|
+
output_content = get_api_output_in_standard_format(
|
2814
|
+
message=messages["GENERIC_MISSING_REQUIRED_FIELD"],
|
2815
|
+
log="email is required to send verification email.",
|
2816
|
+
)
|
2817
|
+
raise HTTPException(
|
2818
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
2819
|
+
detail=output_content,
|
2820
|
+
)
|
2821
|
+
# check if email is not verified
|
2822
|
+
if not user_profile_data.get(UserProfile.user_profile_email_verified.name):
|
2823
|
+
output_content = get_api_output_in_standard_format(
|
2824
|
+
message=messages["EMAIL_NOT_VERIFIED"],
|
2825
|
+
log="email is not verified.",
|
2826
|
+
)
|
2827
|
+
return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
|
2828
|
+
|
2829
|
+
"""
|
2830
|
+
main process
|
2831
|
+
"""
|
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
|
+
)
|
2836
|
+
# hash the verification code
|
2837
|
+
hashed_verification_code = bcrypt.hashpw(
|
2838
|
+
str(verification_code).encode("utf-8"), bcrypt.gensalt()
|
2839
|
+
).decode("utf-8")
|
2840
|
+
expires_at = datetime.now(timezone.utc) + timedelta(
|
2841
|
+
seconds=EXPIRY_TIME_FOR_EMAIL_PASSWORD_RESET_CODE_IN_SECONDS
|
2842
|
+
)
|
2843
|
+
# add verification code to UserVerification code table
|
2844
|
+
global_object_square_database_helper.insert_rows_v0(
|
2845
|
+
database_name=global_string_database_name,
|
2846
|
+
schema_name=global_string_schema_name,
|
2847
|
+
table_name=UserVerificationCode.__tablename__,
|
2848
|
+
data=[
|
2849
|
+
{
|
2850
|
+
UserVerificationCode.user_id.name: user_id,
|
2851
|
+
UserVerificationCode.user_verification_code_type.name: VerificationCodeTypeEnum.EMAIL_RECOVERY.value,
|
2852
|
+
UserVerificationCode.user_verification_code_hash.name: hashed_verification_code,
|
2853
|
+
UserVerificationCode.user_verification_code_expires_at.name: expires_at.strftime(
|
2854
|
+
"%Y-%m-%d %H:%M:%S.%f+00"
|
2855
|
+
),
|
2856
|
+
}
|
2857
|
+
],
|
2858
|
+
)
|
2859
|
+
# send verification email
|
2860
|
+
if (
|
2861
|
+
user_profile_data[UserProfile.user_profile_first_name.name]
|
2862
|
+
and user_profile_data[UserProfile.user_profile_last_name.name]
|
2863
|
+
):
|
2864
|
+
user_to_name = f"{user_profile_data[UserProfile.user_profile_first_name.name]} {user_profile_data[UserProfile.user_profile_last_name.name]}"
|
2865
|
+
elif user_profile_data[UserProfile.user_profile_first_name.name]:
|
2866
|
+
user_to_name = user_profile_data[UserProfile.user_profile_first_name.name]
|
2867
|
+
elif user_profile_data[UserProfile.user_profile_last_name.name]:
|
2868
|
+
user_to_name = user_profile_data[UserProfile.user_profile_last_name.name]
|
2869
|
+
else:
|
2870
|
+
user_to_name = ""
|
2871
|
+
|
2872
|
+
mailgun_response = send_email_using_mailgun(
|
2873
|
+
from_email="auth@thepmsquare.com",
|
2874
|
+
from_name="square_authentication",
|
2875
|
+
to_email=user_profile_data[UserProfile.user_profile_email.name],
|
2876
|
+
to_name=user_to_name,
|
2877
|
+
subject="Password Reset Verification Code",
|
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.",
|
2879
|
+
api_key=MAIL_GUN_API_KEY,
|
2880
|
+
domain_name="thepmsquare.com",
|
2881
|
+
)
|
2882
|
+
# add log for email sending
|
2883
|
+
global_object_square_database_helper.insert_rows_v0(
|
2884
|
+
database_name=global_string_database_name,
|
2885
|
+
schema_name=email_schema_name,
|
2886
|
+
table_name=EmailLog.__tablename__,
|
2887
|
+
data=[
|
2888
|
+
{
|
2889
|
+
EmailLog.user_id.name: user_id,
|
2890
|
+
EmailLog.recipient_email.name: user_profile_data[
|
2891
|
+
UserProfile.user_profile_email.name
|
2892
|
+
],
|
2893
|
+
EmailLog.email_type.name: EmailTypeEnum.VERIFY_EMAIL.value,
|
2894
|
+
EmailLog.status.name: EmailStatusEnum.SENT.value,
|
2895
|
+
EmailLog.third_party_message_id.name: mailgun_response.get("id"),
|
2896
|
+
}
|
2897
|
+
],
|
2898
|
+
)
|
2899
|
+
"""
|
2900
|
+
return value
|
2901
|
+
"""
|
2902
|
+
output_content = get_api_output_in_standard_format(
|
2903
|
+
data={
|
2904
|
+
"expires_at": expires_at.isoformat(),
|
2905
|
+
},
|
2906
|
+
message=messages["GENERIC_ACTION_SUCCESSFUL"],
|
2907
|
+
)
|
2908
|
+
return JSONResponse(
|
2909
|
+
status_code=status.HTTP_200_OK,
|
2910
|
+
content=output_content,
|
2911
|
+
)
|
2912
|
+
except HTTPException as http_exception:
|
2913
|
+
global_object_square_logger.logger.error(http_exception, exc_info=True)
|
2914
|
+
return JSONResponse(
|
2915
|
+
status_code=http_exception.status_code, content=http_exception.detail
|
2916
|
+
)
|
2917
|
+
except Exception as e:
|
2918
|
+
"""
|
2919
|
+
rollback logic
|
2920
|
+
"""
|
2921
|
+
global_object_square_logger.logger.error(e, exc_info=True)
|
2922
|
+
output_content = get_api_output_in_standard_format(
|
2923
|
+
message=messages["GENERIC_500"],
|
2924
|
+
log=str(e),
|
2925
|
+
)
|
2926
|
+
return JSONResponse(
|
2927
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
|
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
|
+
)
|