square-authentication 2.0.0__py3-none-any.whl → 4.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.
@@ -8,9 +8,10 @@ messages = {
8
8
  "USERNAME_ALREADY_EXISTS": "the username you entered is already taken. please choose a different one.",
9
9
  "INCORRECT_ACCESS_TOKEN": "the access token provided is invalid or expired.",
10
10
  "INCORRECT_REFRESH_TOKEN": "the refresh token provided is invalid or expired.",
11
- "GENERIC_READ_SUCCESSFUL": "data retrieved successfully.",
12
11
  "GENERIC_CREATION_SUCCESSFUL": "records created successfully.",
12
+ "GENERIC_READ_SUCCESSFUL": "data retrieved successfully.",
13
13
  "GENERIC_UPDATE_SUCCESSFUL": "your information has been updated successfully.",
14
+ "GENERIC_DELETE_SUCCESSFUL": "your records have been deleted successfully.",
14
15
  "GENERIC_400": "the request is invalid or cannot be processed.",
15
16
  "GENERIC_500": "an internal server error occurred. please try again later.",
16
17
  }
File without changes
@@ -0,0 +1,25 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class RegisterUsernameV0(BaseModel):
7
+ username: str
8
+ password: str
9
+ app_id: Optional[int] = None
10
+
11
+
12
+ class LoginUsernameV0(BaseModel):
13
+ username: str
14
+ password: str
15
+ app_id: int
16
+ assign_app_id_if_missing: bool = False
17
+
18
+
19
+ class DeleteUserV0(BaseModel):
20
+ password: str
21
+
22
+
23
+ class UpdatePasswordV0(BaseModel):
24
+ old_password: str
25
+ new_password: str
@@ -1,11 +1,11 @@
1
1
  from datetime import datetime, timedelta, timezone
2
2
  from typing import Annotated, List
3
- from uuid import UUID
4
3
 
5
4
  import bcrypt
6
5
  import jwt
7
6
  from fastapi import APIRouter, status, Header, HTTPException
8
7
  from fastapi.responses import JSONResponse
8
+ from requests import HTTPError
9
9
  from square_commons import get_api_output_in_standard_format
10
10
  from square_database.pydantic_models.pydantic_models import (
11
11
  FiltersV0,
@@ -36,6 +36,12 @@ from square_authentication.configuration import (
36
36
  config_str_square_database_protocol,
37
37
  )
38
38
  from square_authentication.messages import messages
39
+ from square_authentication.pydantic_models.core import (
40
+ RegisterUsernameV0,
41
+ LoginUsernameV0,
42
+ DeleteUserV0,
43
+ UpdatePasswordV0,
44
+ )
39
45
  from square_authentication.utils.token import get_jwt_payload
40
46
 
41
47
  router = APIRouter(
@@ -51,8 +57,17 @@ global_object_square_database_helper = SquareDatabaseHelper(
51
57
 
52
58
  @router.post("/register_username/v0")
53
59
  @global_object_square_logger.async_auto_logger
54
- async def register_username_v0(username: str, password: str):
60
+ async def register_username_v0(
61
+ body: RegisterUsernameV0,
62
+ ):
63
+ username = body.username
64
+ password = body.password
65
+ app_id = body.app_id
66
+
55
67
  local_str_user_id = None
68
+ local_str_access_token = None
69
+ local_str_refresh_token = None
70
+
56
71
  username = username.lower()
57
72
  try:
58
73
  """
@@ -117,13 +132,77 @@ async def register_username_v0(username: str, password: str):
117
132
  schema_name=global_string_schema_name,
118
133
  table_name=UserCredential.__tablename__,
119
134
  )
135
+ if app_id is not None:
136
+ # assign app to user
137
+ global_object_square_database_helper.insert_rows_v0(
138
+ database_name=global_string_database_name,
139
+ schema_name=global_string_schema_name,
140
+ table_name=UserApp.__tablename__,
141
+ data=[
142
+ {
143
+ UserApp.user_id.name: local_str_user_id,
144
+ UserApp.app_id.name: app_id,
145
+ }
146
+ ],
147
+ )
148
+
149
+ # return new access token and refresh token
150
+ # create access token
151
+ local_dict_access_token_payload = {
152
+ "app_id": app_id,
153
+ "user_id": local_str_user_id,
154
+ "exp": datetime.now(timezone.utc)
155
+ + timedelta(minutes=config_int_access_token_valid_minutes),
156
+ }
157
+ local_str_access_token = jwt.encode(
158
+ local_dict_access_token_payload,
159
+ config_str_secret_key_for_access_token,
160
+ )
120
161
 
162
+ # create refresh token
163
+ local_object_refresh_token_expiry_time = datetime.now(
164
+ timezone.utc
165
+ ) + timedelta(minutes=config_int_refresh_token_valid_minutes)
166
+
167
+ local_dict_refresh_token_payload = {
168
+ "app_id": app_id,
169
+ "user_id": local_str_user_id,
170
+ "exp": local_object_refresh_token_expiry_time,
171
+ }
172
+ local_str_refresh_token = jwt.encode(
173
+ local_dict_refresh_token_payload,
174
+ config_str_secret_key_for_refresh_token,
175
+ )
176
+ # entry in user session table
177
+ global_object_square_database_helper.insert_rows_v0(
178
+ data=[
179
+ {
180
+ UserSession.user_id.name: local_str_user_id,
181
+ UserSession.app_id.name: app_id,
182
+ UserSession.user_session_refresh_token.name: local_str_refresh_token,
183
+ UserSession.user_session_expiry_time.name: local_object_refresh_token_expiry_time.strftime(
184
+ "%Y-%m-%d %H:%M:%S.%f+00"
185
+ ),
186
+ }
187
+ ],
188
+ database_name=global_string_database_name,
189
+ schema_name=global_string_schema_name,
190
+ table_name=UserSession.__tablename__,
191
+ )
121
192
  """
122
193
  return value
123
194
  """
124
195
  output_content = get_api_output_in_standard_format(
125
196
  message=messages["REGISTRATION_SUCCESSFUL"],
126
- data={"main": {"user_id": local_str_user_id, "username": username}},
197
+ data={
198
+ "main": {
199
+ "user_id": local_str_user_id,
200
+ "username": username,
201
+ "app_id": app_id,
202
+ "access_token": local_str_access_token,
203
+ "refresh_token": local_str_refresh_token,
204
+ },
205
+ },
127
206
  )
128
207
  return JSONResponse(
129
208
  status_code=status.HTTP_201_CREATED,
@@ -156,32 +235,29 @@ async def register_username_v0(username: str, password: str):
156
235
  )
157
236
 
158
237
 
159
- @router.get("/get_user_app_ids/v0")
238
+ @router.get("/get_user_details/v0")
160
239
  @global_object_square_logger.async_auto_logger
161
- async def get_user_app_ids_v0(user_id: UUID):
240
+ async def get_user_details_v0(
241
+ access_token: Annotated[str, Header()],
242
+ ):
162
243
  try:
163
- local_string_user_id = str(user_id)
164
244
  """
165
245
  validation
166
246
  """
167
-
168
- local_list_response_user = global_object_square_database_helper.get_rows_v0(
169
- database_name=global_string_database_name,
170
- schema_name=global_string_schema_name,
171
- table_name=User.__tablename__,
172
- filters=FiltersV0(
173
- {User.user_id.name: FilterConditionsV0(eq=local_string_user_id)}
174
- ),
175
- )["data"]["main"]
176
- if len(local_list_response_user) != 1:
247
+ # validate access token
248
+ try:
249
+ local_dict_access_token_payload = get_jwt_payload(
250
+ access_token, config_str_secret_key_for_access_token
251
+ )
252
+ except Exception as error:
177
253
  output_content = get_api_output_in_standard_format(
178
- message=messages["INCORRECT_USER_ID"],
179
- log=f"invalid user_id: {local_string_user_id}",
254
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
180
255
  )
181
- raise HTTPException(
182
- status_code=status.HTTP_404_NOT_FOUND,
183
- detail=output_content,
256
+ return JSONResponse(
257
+ status_code=status.HTTP_400_BAD_REQUEST,
258
+ content=output_content,
184
259
  )
260
+ user_id = local_dict_access_token_payload["user_id"]
185
261
  """
186
262
  main process
187
263
  """
@@ -189,18 +265,61 @@ async def get_user_app_ids_v0(user_id: UUID):
189
265
  database_name=global_string_database_name,
190
266
  schema_name=global_string_schema_name,
191
267
  table_name=UserApp.__tablename__,
192
- filters=FiltersV0(
193
- {UserApp.user_id.name: FilterConditionsV0(eq=local_string_user_id)}
194
- ),
268
+ filters=FiltersV0({UserApp.user_id.name: FilterConditionsV0(eq=user_id)}),
195
269
  )["data"]["main"]
270
+ local_list_response_user_credentials = (
271
+ global_object_square_database_helper.get_rows_v0(
272
+ database_name=global_string_database_name,
273
+ schema_name=global_string_schema_name,
274
+ table_name=UserCredential.__tablename__,
275
+ filters=FiltersV0(
276
+ {UserCredential.user_id.name: FilterConditionsV0(eq=user_id)}
277
+ ),
278
+ )["data"]["main"]
279
+ )
280
+ local_list_response_user_sessions = (
281
+ global_object_square_database_helper.get_rows_v0(
282
+ database_name=global_string_database_name,
283
+ schema_name=global_string_schema_name,
284
+ table_name=UserSession.__tablename__,
285
+ filters=FiltersV0(
286
+ {
287
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
288
+ UserSession.user_session_expiry_time.name: FilterConditionsV0(
289
+ gte=datetime.now(timezone.utc).isoformat()
290
+ ),
291
+ }
292
+ ),
293
+ )["data"]["main"]
294
+ )
196
295
  """
197
296
  return value
198
297
  """
298
+ return_this = {
299
+ "user_id": user_id,
300
+ "credentials": {
301
+ "username": local_list_response_user_credentials[0][
302
+ UserCredential.user_credential_username.name
303
+ ],
304
+ },
305
+ "apps": [x[UserApp.app_id.name] for x in local_list_response_user_app],
306
+ "sessions": [
307
+ {
308
+ "app_id": x[UserApp.app_id.name],
309
+ "active_sessions": len(
310
+ [
311
+ y
312
+ for y in local_list_response_user_sessions
313
+ if y[UserSession.app_id.name] == x[UserApp.app_id.name]
314
+ ]
315
+ ),
316
+ }
317
+ for x in local_list_response_user_app
318
+ ],
319
+ }
199
320
  output_content = get_api_output_in_standard_format(
200
321
  message=messages["GENERIC_READ_SUCCESSFUL"],
201
- data={
202
- "main": [x[UserApp.app_id.name] for x in local_list_response_user_app]
203
- },
322
+ data={"main": return_this},
204
323
  )
205
324
  return JSONResponse(
206
325
  status_code=status.HTTP_200_OK,
@@ -228,15 +347,29 @@ async def get_user_app_ids_v0(user_id: UUID):
228
347
  @router.patch("/update_user_app_ids/v0")
229
348
  @global_object_square_logger.async_auto_logger
230
349
  async def update_user_app_ids_v0(
231
- user_id: UUID,
350
+ access_token: Annotated[str, Header()],
232
351
  app_ids_to_add: List[int],
233
352
  app_ids_to_remove: List[int],
234
353
  ):
235
354
  try:
236
- local_string_user_id = str(user_id)
355
+
237
356
  """
238
357
  validation
239
358
  """
359
+ # validate access token
360
+ try:
361
+ local_dict_access_token_payload = get_jwt_payload(
362
+ access_token, config_str_secret_key_for_access_token
363
+ )
364
+ except Exception as error:
365
+ output_content = get_api_output_in_standard_format(
366
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
367
+ )
368
+ return JSONResponse(
369
+ status_code=status.HTTP_400_BAD_REQUEST,
370
+ content=output_content,
371
+ )
372
+ user_id = local_dict_access_token_payload["user_id"]
240
373
 
241
374
  app_ids_to_add = list(set(app_ids_to_add))
242
375
  app_ids_to_remove = list(set(app_ids_to_remove))
@@ -252,27 +385,6 @@ async def update_user_app_ids_v0(
252
385
  status_code=status.HTTP_400_BAD_REQUEST,
253
386
  detail=output_content,
254
387
  )
255
- # validate access token
256
- # TBD
257
-
258
- # check if user id is in user table
259
- local_list_response_user = global_object_square_database_helper.get_rows_v0(
260
- database_name=global_string_database_name,
261
- schema_name=global_string_schema_name,
262
- table_name=User.__tablename__,
263
- filters=FiltersV0(
264
- {User.user_id.name: FilterConditionsV0(eq=local_string_user_id)}
265
- ),
266
- )["data"]["main"]
267
- if len(local_list_response_user) != 1:
268
- output_content = get_api_output_in_standard_format(
269
- message=messages["INCORRECT_USER_ID"],
270
- log=f"invalid user_id: {local_string_user_id}",
271
- )
272
- raise HTTPException(
273
- status_code=status.HTTP_404_NOT_FOUND,
274
- detail=output_content,
275
- )
276
388
 
277
389
  # check if all app_ids are valid
278
390
  local_list_all_app_ids = [*app_ids_to_add, *app_ids_to_remove]
@@ -305,13 +417,11 @@ async def update_user_app_ids_v0(
305
417
  database_name=global_string_database_name,
306
418
  schema_name=global_string_schema_name,
307
419
  table_name=UserApp.__tablename__,
308
- filters=FiltersV0(
309
- {UserApp.user_id.name: FilterConditionsV0(eq=local_string_user_id)}
310
- ),
420
+ filters=FiltersV0({UserApp.user_id.name: FilterConditionsV0(eq=user_id)}),
311
421
  )["data"]["main"]
312
422
  local_list_new_app_ids = [
313
423
  {
314
- UserApp.user_id.name: local_string_user_id,
424
+ UserApp.user_id.name: user_id,
315
425
  UserApp.app_id.name: x,
316
426
  }
317
427
  for x in app_ids_to_add
@@ -333,22 +443,19 @@ async def update_user_app_ids_v0(
333
443
  table_name=UserApp.__tablename__,
334
444
  filters=FiltersV0(
335
445
  {
336
- UserApp.user_id.name: FilterConditionsV0(
337
- eq=local_string_user_id
338
- ),
446
+ UserApp.user_id.name: FilterConditionsV0(eq=user_id),
339
447
  UserApp.app_id.name: FilterConditionsV0(eq=app_id),
340
448
  }
341
449
  ),
342
450
  )
451
+ # logout user from removed apps
343
452
  global_object_square_database_helper.delete_rows_v0(
344
453
  database_name=global_string_database_name,
345
454
  schema_name=global_string_schema_name,
346
455
  table_name=UserSession.__tablename__,
347
456
  filters=FiltersV0(
348
457
  {
349
- UserSession.user_id.name: FilterConditionsV0(
350
- eq=local_string_user_id
351
- ),
458
+ UserSession.user_id.name: FilterConditionsV0(eq=user_id),
352
459
  UserSession.app_id.name: FilterConditionsV0(eq=app_id),
353
460
  }
354
461
  ),
@@ -362,9 +469,7 @@ async def update_user_app_ids_v0(
362
469
  database_name=global_string_database_name,
363
470
  schema_name=global_string_schema_name,
364
471
  table_name=UserApp.__tablename__,
365
- filters=FiltersV0(
366
- {UserApp.user_id.name: FilterConditionsV0(eq=local_string_user_id)}
367
- ),
472
+ filters=FiltersV0({UserApp.user_id.name: FilterConditionsV0(eq=user_id)}),
368
473
  )["data"]["main"]
369
474
  output_content = get_api_output_in_standard_format(
370
475
  message=messages["GENERIC_UPDATE_SUCCESSFUL"],
@@ -395,9 +500,13 @@ async def update_user_app_ids_v0(
395
500
  )
396
501
 
397
502
 
398
- @router.get("/login_username/v0")
503
+ @router.post("/login_username/v0")
399
504
  @global_object_square_logger.async_auto_logger
400
- async def login_username_v0(username: str, password: str, app_id: int):
505
+ async def login_username_v0(body: LoginUsernameV0):
506
+ username = body.username
507
+ password = body.password
508
+ app_id = body.app_id
509
+ assign_app_id_if_missing = body.assign_app_id_if_missing
401
510
  username = username.lower()
402
511
  try:
403
512
  """
@@ -443,14 +552,36 @@ async def login_username_v0(username: str, password: str, app_id: int):
443
552
  }
444
553
  ),
445
554
  )["data"]["main"]
446
- if len(local_list_user_app_response) != 1:
447
- output_content = get_api_output_in_standard_format(
448
- message=messages["GENERIC_400"],
449
- log=f"user_id {local_str_user_id}({username}) not assigned to app {app_id}.",
450
- )
451
- return JSONResponse(
452
- status_code=status.HTTP_400_BAD_REQUEST, content=output_content
453
- )
555
+ if len(local_list_user_app_response) == 0:
556
+ if assign_app_id_if_missing:
557
+ try:
558
+ global_object_square_database_helper.insert_rows_v0(
559
+ database_name=global_string_database_name,
560
+ schema_name=global_string_schema_name,
561
+ table_name=UserApp.__tablename__,
562
+ data=[
563
+ {
564
+ UserApp.user_id.name: local_str_user_id,
565
+ UserApp.app_id.name: app_id,
566
+ }
567
+ ],
568
+ )
569
+ except HTTPError as he:
570
+ output_content = get_api_output_in_standard_format(
571
+ message=messages["GENERIC_400"],
572
+ log=str(he),
573
+ )
574
+ return JSONResponse(
575
+ status_code=he.response.status_code, content=output_content
576
+ )
577
+ else:
578
+ output_content = get_api_output_in_standard_format(
579
+ message=messages["GENERIC_400"],
580
+ log=f"user_id {local_str_user_id}({username}) not assigned to app {app_id}.",
581
+ )
582
+ return JSONResponse(
583
+ status_code=status.HTTP_400_BAD_REQUEST, content=output_content
584
+ )
454
585
 
455
586
  # validate password
456
587
  if not (
@@ -554,54 +685,14 @@ async def login_username_v0(username: str, password: str, app_id: int):
554
685
  @router.get("/generate_access_token/v0")
555
686
  @global_object_square_logger.async_auto_logger
556
687
  async def generate_access_token_v0(
557
- user_id: str, app_id: int, refresh_token: Annotated[str, Header()]
688
+ refresh_token: Annotated[str, Header()],
558
689
  ):
559
690
  try:
560
691
  """
561
692
  validation
562
693
  """
563
- # validate user_id
564
- local_list_user_response = global_object_square_database_helper.get_rows_v0(
565
- database_name=global_string_database_name,
566
- schema_name=global_string_schema_name,
567
- table_name=User.__tablename__,
568
- filters=FiltersV0({User.user_id.name: FilterConditionsV0(eq=user_id)}),
569
- )["data"]["main"]
570
-
571
- if len(local_list_user_response) != 1:
572
- output_content = get_api_output_in_standard_format(
573
- message=messages["INCORRECT_USER_ID"],
574
- log=f"incorrect user_id: {user_id}.",
575
- )
576
- return JSONResponse(
577
- status_code=status.HTTP_400_BAD_REQUEST,
578
- content=output_content,
579
- )
580
- # validate if app_id is assigned to user
581
- # this will also validate if app_id is valid
582
- local_dict_user = local_list_user_response[0]
583
- local_str_user_id = local_dict_user[User.user_id.name]
584
- local_list_user_app_response = global_object_square_database_helper.get_rows_v0(
585
- database_name=global_string_database_name,
586
- schema_name=global_string_schema_name,
587
- table_name=UserApp.__tablename__,
588
- filters=FiltersV0(
589
- {
590
- UserApp.user_id.name: FilterConditionsV0(eq=local_str_user_id),
591
- UserApp.app_id.name: FilterConditionsV0(eq=app_id),
592
- }
593
- ),
594
- )["data"]["main"]
595
- if len(local_list_user_app_response) != 1:
596
- output_content = get_api_output_in_standard_format(
597
- message=messages["GENERIC_400"],
598
- log=f"user_id {local_str_user_id} not assigned to app {app_id}.",
599
- )
600
- return JSONResponse(
601
- status_code=status.HTTP_400_BAD_REQUEST, content=output_content
602
- )
603
694
  # validate refresh token
604
- # validating if a session refresh token exists in the database for provided app id.
695
+ # validating if a session refresh token exists in the database.
605
696
  local_list_user_session_response = (
606
697
  global_object_square_database_helper.get_rows_v0(
607
698
  database_name=global_string_database_name,
@@ -609,11 +700,9 @@ async def generate_access_token_v0(
609
700
  table_name=UserSession.__tablename__,
610
701
  filters=FiltersV0(
611
702
  {
612
- UserSession.user_id.name: FilterConditionsV0(eq=user_id),
613
703
  UserSession.user_session_refresh_token.name: FilterConditionsV0(
614
704
  eq=refresh_token
615
705
  ),
616
- UserSession.app_id.name: FilterConditionsV0(eq=app_id),
617
706
  }
618
707
  ),
619
708
  )["data"]["main"]
@@ -622,7 +711,7 @@ async def generate_access_token_v0(
622
711
  if len(local_list_user_session_response) != 1:
623
712
  output_content = get_api_output_in_standard_format(
624
713
  message=messages["INCORRECT_REFRESH_TOKEN"],
625
- log=f"incorrect refresh token: {refresh_token} for user_id: {user_id} for app_id: {app_id}.",
714
+ log=f"incorrect refresh token: {refresh_token}.",
626
715
  )
627
716
  return JSONResponse(
628
717
  status_code=status.HTTP_400_BAD_REQUEST,
@@ -641,32 +730,13 @@ async def generate_access_token_v0(
641
730
  status_code=status.HTTP_400_BAD_REQUEST,
642
731
  content=output_content,
643
732
  )
644
-
645
- if local_dict_refresh_token_payload["user_id"] != user_id:
646
- output_content = get_api_output_in_standard_format(
647
- message=messages["INCORRECT_REFRESH_TOKEN"],
648
- log=f"refresh token and user_id mismatch.",
649
- )
650
- return JSONResponse(
651
- status_code=status.HTTP_400_BAD_REQUEST,
652
- content=output_content,
653
- )
654
- if local_dict_refresh_token_payload["app_id"] != app_id:
655
- output_content = get_api_output_in_standard_format(
656
- message=messages["INCORRECT_REFRESH_TOKEN"],
657
- log=f"refresh token and app_id mismatch.",
658
- )
659
- return JSONResponse(
660
- status_code=status.HTTP_400_BAD_REQUEST,
661
- content=output_content,
662
- )
663
733
  """
664
734
  main process
665
735
  """
666
736
  # create and send access token
667
737
  local_dict_access_token_payload = {
668
- "app_id": app_id,
669
- "user_id": user_id,
738
+ "app_id": local_dict_refresh_token_payload["app_id"],
739
+ "user_id": local_dict_refresh_token_payload["user_id"],
670
740
  "exp": datetime.now(timezone.utc)
671
741
  + timedelta(minutes=config_int_access_token_valid_minutes),
672
742
  }
@@ -705,15 +775,121 @@ async def generate_access_token_v0(
705
775
  @router.delete("/logout/v0")
706
776
  @global_object_square_logger.async_auto_logger
707
777
  async def logout_v0(
708
- user_id: str,
709
- app_id: int,
710
- access_token: Annotated[str, Header()],
711
778
  refresh_token: Annotated[str, Header()],
712
779
  ):
713
780
  try:
714
781
  """
715
782
  validation
716
783
  """
784
+ # validate refresh token
785
+ # validating if a session refresh token exists in the database.
786
+ local_list_user_session_response = (
787
+ global_object_square_database_helper.get_rows_v0(
788
+ database_name=global_string_database_name,
789
+ schema_name=global_string_schema_name,
790
+ table_name=UserSession.__tablename__,
791
+ filters=FiltersV0(
792
+ {
793
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
794
+ eq=refresh_token
795
+ ),
796
+ }
797
+ ),
798
+ )["data"]["main"]
799
+ )
800
+
801
+ if len(local_list_user_session_response) != 1:
802
+ output_content = get_api_output_in_standard_format(
803
+ message=messages["INCORRECT_REFRESH_TOKEN"],
804
+ log=f"incorrect refresh token: {refresh_token}.",
805
+ )
806
+ return JSONResponse(
807
+ status_code=status.HTTP_400_BAD_REQUEST,
808
+ content=output_content,
809
+ )
810
+ # validating if the refresh token is valid, active and of the same user.
811
+ try:
812
+ local_dict_refresh_token_payload = get_jwt_payload(
813
+ refresh_token, config_str_secret_key_for_refresh_token
814
+ )
815
+ except Exception as error:
816
+ output_content = get_api_output_in_standard_format(
817
+ message=messages["INCORRECT_REFRESH_TOKEN"],
818
+ log=str(error),
819
+ )
820
+ return JSONResponse(
821
+ status_code=status.HTTP_400_BAD_REQUEST,
822
+ content=output_content,
823
+ )
824
+ # ======================================================================================
825
+ # NOTE: if refresh token has expired no need to delete it during this call
826
+ # ======================================================================================
827
+ """
828
+ main process
829
+ """
830
+ # delete session for user
831
+ global_object_square_database_helper.delete_rows_v0(
832
+ database_name=global_string_database_name,
833
+ schema_name=global_string_schema_name,
834
+ table_name=UserSession.__tablename__,
835
+ filters=FiltersV0(
836
+ {
837
+ UserSession.user_session_refresh_token.name: FilterConditionsV0(
838
+ eq=refresh_token
839
+ ),
840
+ }
841
+ ),
842
+ )
843
+ """
844
+ return value
845
+ """
846
+ output_content = get_api_output_in_standard_format(
847
+ message=messages["LOGOUT_SUCCESSFUL"],
848
+ )
849
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
850
+ except HTTPException as http_exception:
851
+ return JSONResponse(
852
+ status_code=http_exception.status_code, content=http_exception.detail
853
+ )
854
+ except Exception as e:
855
+ """
856
+ rollback logic
857
+ """
858
+ global_object_square_logger.logger.error(e, exc_info=True)
859
+ output_content = get_api_output_in_standard_format(
860
+ message=messages["GENERIC_500"],
861
+ log=str(e),
862
+ )
863
+ return JSONResponse(
864
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
865
+ )
866
+
867
+
868
+ @router.patch("/update_username/v0")
869
+ @global_object_square_logger.async_auto_logger
870
+ async def update_username_v0(
871
+ new_username: str,
872
+ access_token: Annotated[str, Header()],
873
+ ):
874
+ try:
875
+ """
876
+ validation
877
+ """
878
+ # validate access token
879
+ try:
880
+ local_dict_access_token_payload = get_jwt_payload(
881
+ access_token, config_str_secret_key_for_access_token
882
+ )
883
+ except Exception as error:
884
+ output_content = get_api_output_in_standard_format(
885
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
886
+ )
887
+ return JSONResponse(
888
+ status_code=status.HTTP_400_BAD_REQUEST,
889
+ content=output_content,
890
+ )
891
+ user_id = local_dict_access_token_payload["user_id"]
892
+
717
893
  # validate user_id
718
894
  local_list_user_response = global_object_square_database_helper.get_rows_v0(
719
895
  database_name=global_string_database_name,
@@ -736,62 +912,192 @@ async def logout_v0(
736
912
  content=output_content,
737
913
  )
738
914
 
739
- # validate if app_id is assigned to user
740
- # this will also validate if app_id is valid
741
- local_dict_user = local_list_user_response[0]
742
- local_str_user_id = local_dict_user[User.user_id.name]
743
- local_list_user_app_response = global_object_square_database_helper.get_rows_v0(
915
+ # validate new username
916
+ local_list_user_credentials_response = global_object_square_database_helper.get_rows_v0(
744
917
  database_name=global_string_database_name,
745
918
  schema_name=global_string_schema_name,
746
- table_name=UserApp.__tablename__,
919
+ table_name=UserCredential.__tablename__,
747
920
  filters=FiltersV0(
748
921
  {
749
- UserApp.user_id.name: FilterConditionsV0(eq=local_str_user_id),
750
- UserApp.app_id.name: FilterConditionsV0(eq=app_id),
922
+ UserCredential.user_credential_username.name: FilterConditionsV0(
923
+ eq=new_username
924
+ ),
751
925
  }
752
926
  ),
753
- )["data"]["main"]
754
- if len(local_list_user_app_response) != 1:
927
+ )[
928
+ "data"
929
+ ][
930
+ "main"
931
+ ]
932
+ if len(local_list_user_credentials_response) != 0:
755
933
  output_content = get_api_output_in_standard_format(
756
- message=messages["GENERIC_400"],
757
- log=f"user_id {local_str_user_id} not assigned to app {app_id}.",
934
+ message=messages["USERNAME_ALREADY_EXISTS"],
935
+ log=f"{new_username} is taken.",
758
936
  )
759
937
  return JSONResponse(
760
- status_code=status.HTTP_400_BAD_REQUEST, content=output_content
938
+ status_code=status.HTTP_409_CONFLICT,
939
+ content=output_content,
761
940
  )
941
+ """
942
+ main process
943
+ """
944
+ # edit the username
945
+ global_object_square_database_helper.edit_rows_v0(
946
+ database_name=global_string_database_name,
947
+ schema_name=global_string_schema_name,
948
+ table_name=UserCredential.__tablename__,
949
+ filters=FiltersV0(
950
+ {
951
+ UserCredential.user_id.name: FilterConditionsV0(eq=user_id),
952
+ }
953
+ ),
954
+ data={
955
+ UserCredential.user_credential_username.name: new_username,
956
+ },
957
+ )
958
+ """
959
+ return value
960
+ """
961
+ output_content = get_api_output_in_standard_format(
962
+ data={"main": {"user_id": user_id, "username": new_username}},
963
+ message=messages["GENERIC_UPDATE_SUCCESSFUL"],
964
+ )
965
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
966
+ except HTTPException as http_exception:
967
+ return JSONResponse(
968
+ status_code=http_exception.status_code, content=http_exception.detail
969
+ )
970
+ except Exception as e:
971
+ """
972
+ rollback logic
973
+ """
974
+ global_object_square_logger.logger.error(e, exc_info=True)
975
+ output_content = get_api_output_in_standard_format(
976
+ message=messages["GENERIC_500"],
977
+ log=str(e),
978
+ )
979
+ return JSONResponse(
980
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
981
+ )
762
982
 
763
- # validate refresh token
764
- # validating if a session refresh token exists in the database.
765
- local_list_user_session_response = (
983
+
984
+ @router.delete("/delete_user/v0")
985
+ @global_object_square_logger.async_auto_logger
986
+ async def delete_user_v0(
987
+ body: DeleteUserV0,
988
+ access_token: Annotated[str, Header()],
989
+ ):
990
+ password = body.password
991
+ try:
992
+ """
993
+ validation
994
+ """
995
+ # validate access token
996
+ try:
997
+ local_dict_access_token_payload = get_jwt_payload(
998
+ access_token, config_str_secret_key_for_access_token
999
+ )
1000
+ except Exception as error:
1001
+ output_content = get_api_output_in_standard_format(
1002
+ message=messages["INCORRECT_ACCESS_TOKEN"], log=str(error)
1003
+ )
1004
+ return JSONResponse(
1005
+ status_code=status.HTTP_400_BAD_REQUEST,
1006
+ content=output_content,
1007
+ )
1008
+ user_id = local_dict_access_token_payload["user_id"]
1009
+
1010
+ # validate user_id
1011
+ local_list_authentication_user_response = (
766
1012
  global_object_square_database_helper.get_rows_v0(
767
1013
  database_name=global_string_database_name,
768
1014
  schema_name=global_string_schema_name,
769
- table_name=UserSession.__tablename__,
1015
+ table_name=UserCredential.__tablename__,
770
1016
  filters=FiltersV0(
771
- {
772
- UserSession.user_id.name: FilterConditionsV0(eq=user_id),
773
- UserSession.user_session_refresh_token.name: FilterConditionsV0(
774
- eq=refresh_token
775
- ),
776
- UserSession.app_id.name: FilterConditionsV0(eq=app_id),
777
- }
1017
+ {UserCredential.user_id.name: FilterConditionsV0(eq=user_id)}
778
1018
  ),
779
1019
  )["data"]["main"]
780
1020
  )
1021
+ if len(local_list_authentication_user_response) != 1:
1022
+ output_content = get_api_output_in_standard_format(
1023
+ message=messages["INCORRECT_USER_ID"],
1024
+ log=f"incorrect user_id: {user_id}.",
1025
+ )
1026
+ return JSONResponse(
1027
+ status_code=status.HTTP_400_BAD_REQUEST, content=output_content
1028
+ )
781
1029
 
782
- if len(local_list_user_session_response) != 1:
1030
+ # validate password
1031
+ local_dict_user = local_list_authentication_user_response[0]
1032
+ if not (
1033
+ bcrypt.checkpw(
1034
+ password.encode("utf-8"),
1035
+ local_dict_user[
1036
+ UserCredential.user_credential_hashed_password.name
1037
+ ].encode("utf-8"),
1038
+ )
1039
+ ):
783
1040
  output_content = get_api_output_in_standard_format(
784
- message=messages["INCORRECT_REFRESH_TOKEN"],
785
- log=f"incorrect refresh token: {refresh_token} for user_id: {user_id} for app_id: {app_id}.",
1041
+ message=messages["INCORRECT_PASSWORD"],
1042
+ log=f"incorrect password for user_id {user_id}.",
786
1043
  )
787
1044
  return JSONResponse(
788
1045
  status_code=status.HTTP_400_BAD_REQUEST,
789
1046
  content=output_content,
790
1047
  )
791
- # **not validating if the refresh token is valid, active, of the same user and of the provided app id.**
1048
+ """
1049
+ main process
1050
+ """
1051
+ # delete the user.
1052
+ global_object_square_database_helper.delete_rows_v0(
1053
+ database_name=global_string_database_name,
1054
+ schema_name=global_string_schema_name,
1055
+ table_name=User.__tablename__,
1056
+ filters=FiltersV0(
1057
+ {
1058
+ User.user_id.name: FilterConditionsV0(eq=user_id),
1059
+ }
1060
+ ),
1061
+ )
1062
+ """
1063
+ return value
1064
+ """
1065
+ output_content = get_api_output_in_standard_format(
1066
+ message=messages["GENERIC_DELETE_SUCCESSFUL"],
1067
+ log=f"user_id: {user_id} deleted successfully.",
1068
+ )
1069
+ return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
1070
+ except HTTPException as http_exception:
1071
+ return JSONResponse(
1072
+ status_code=http_exception.status_code, content=http_exception.detail
1073
+ )
1074
+ except Exception as e:
1075
+ """
1076
+ rollback logic
1077
+ """
1078
+ global_object_square_logger.logger.error(e, exc_info=True)
1079
+ output_content = get_api_output_in_standard_format(
1080
+ message=messages["GENERIC_500"],
1081
+ log=str(e),
1082
+ )
1083
+ return JSONResponse(
1084
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=output_content
1085
+ )
1086
+
792
1087
 
1088
+ @router.patch("/update_password/v0")
1089
+ @global_object_square_logger.async_auto_logger
1090
+ async def update_password_v0(
1091
+ body: UpdatePasswordV0,
1092
+ access_token: Annotated[str, Header()],
1093
+ ):
1094
+ old_password = body.old_password
1095
+ new_password = body.new_password
1096
+ try:
1097
+ """
1098
+ validation
1099
+ """
793
1100
  # validate access token
794
- # validating if the access token is valid, active, of the same user and of the provided app.
795
1101
  try:
796
1102
  local_dict_access_token_payload = get_jwt_payload(
797
1103
  access_token, config_str_secret_key_for_access_token
@@ -804,53 +1110,72 @@ async def logout_v0(
804
1110
  status_code=status.HTTP_400_BAD_REQUEST,
805
1111
  content=output_content,
806
1112
  )
807
- if local_dict_access_token_payload["user_id"] != user_id:
1113
+ user_id = local_dict_access_token_payload["user_id"]
1114
+
1115
+ # validate user_id
1116
+ local_list_authentication_user_response = (
1117
+ global_object_square_database_helper.get_rows_v0(
1118
+ database_name=global_string_database_name,
1119
+ schema_name=global_string_schema_name,
1120
+ table_name=UserCredential.__tablename__,
1121
+ filters=FiltersV0(
1122
+ {UserCredential.user_id.name: FilterConditionsV0(eq=user_id)}
1123
+ ),
1124
+ )["data"]["main"]
1125
+ )
1126
+ if len(local_list_authentication_user_response) != 1:
808
1127
  output_content = get_api_output_in_standard_format(
809
- message=messages["INCORRECT_ACCESS_TOKEN"],
810
- log=f"access token and user_id mismatch.",
1128
+ message=messages["INCORRECT_USER_ID"],
1129
+ log=f"incorrect user_id: {user_id}.",
811
1130
  )
812
1131
  return JSONResponse(
813
- status_code=status.HTTP_400_BAD_REQUEST,
814
- content=output_content,
1132
+ status_code=status.HTTP_400_BAD_REQUEST, content=output_content
815
1133
  )
816
- if local_dict_access_token_payload["app_id"] != app_id:
1134
+
1135
+ # validate password
1136
+ local_dict_user = local_list_authentication_user_response[0]
1137
+ if not (
1138
+ bcrypt.checkpw(
1139
+ old_password.encode("utf-8"),
1140
+ local_dict_user[
1141
+ UserCredential.user_credential_hashed_password.name
1142
+ ].encode("utf-8"),
1143
+ )
1144
+ ):
817
1145
  output_content = get_api_output_in_standard_format(
818
- message=messages["INCORRECT_ACCESS_TOKEN"],
819
- log=f"access token and app_id mismatch.",
1146
+ message=messages["INCORRECT_PASSWORD"],
1147
+ log=f"incorrect password for user_id {user_id}.",
820
1148
  )
821
1149
  return JSONResponse(
822
1150
  status_code=status.HTTP_400_BAD_REQUEST,
823
1151
  content=output_content,
824
1152
  )
825
- # ======================================================================================
826
-
827
- # NOTE: if both access token and refresh token have expired for a user,
828
- # it can be assumed that user session only needs to be removed from the front end.
829
-
830
- # ======================================================================================
831
1153
  """
832
1154
  main process
833
1155
  """
834
- # delete session for user
835
- global_object_square_database_helper.delete_rows_v0(
1156
+ # delete the user.
1157
+ local_str_hashed_password = bcrypt.hashpw(
1158
+ new_password.encode("utf-8"), bcrypt.gensalt()
1159
+ ).decode("utf-8")
1160
+ global_object_square_database_helper.edit_rows_v0(
836
1161
  database_name=global_string_database_name,
837
1162
  schema_name=global_string_schema_name,
838
- table_name=UserSession.__tablename__,
1163
+ table_name=UserCredential.__tablename__,
839
1164
  filters=FiltersV0(
840
1165
  {
841
- UserSession.user_id.name: FilterConditionsV0(eq=user_id),
842
- UserSession.user_session_refresh_token.name: FilterConditionsV0(
843
- eq=refresh_token
844
- ),
845
- UserSession.app_id.name: FilterConditionsV0(eq=app_id),
1166
+ UserCredential.user_id.name: FilterConditionsV0(eq=user_id),
846
1167
  }
847
1168
  ),
1169
+ data={
1170
+ UserCredential.user_credential_hashed_password.name: local_str_hashed_password,
1171
+ },
848
1172
  )
849
1173
  """
850
1174
  return value
851
1175
  """
852
1176
  output_content = get_api_output_in_standard_format(
853
- message=messages["LOGOUT_SUCCESSFUL"],
1177
+ message=messages["GENERIC_UPDATE_SUCCESSFUL"],
1178
+ log=f"password for user_id: {user_id} updated successfully.",
854
1179
  )
855
1180
  return JSONResponse(status_code=status.HTTP_200_OK, content=output_content)
856
1181
  except HTTPException as http_exception:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: square-authentication
3
- Version: 2.0.0
3
+ Version: 4.0.0
4
4
  Summary: authentication layer for my personal server.
5
5
  Home-page: https://github.com/thepmsquare/square_authentication
6
6
  Author: thePmSquare
@@ -43,6 +43,25 @@ pip install square_authentication
43
43
 
44
44
  ## changelog
45
45
 
46
+ ### v4.0.0
47
+
48
+ - /login_username/v0 is now POST method.
49
+ - new flag in /login_username/v0 assign_app_id_if_missing.
50
+ - bugfix: /get_user_details/v0 now only returns number of active sessions.
51
+
52
+ ### v3.0.0
53
+
54
+ - added new endpoints
55
+ - /update_username/v0
56
+ - /delete_user/v0
57
+ - /update_password/v0
58
+ - move data in password related endpoints to request body from params.
59
+ - /register_username/v0 now takes in app_id as optional parameter to assign user to that app and create session for it.
60
+ - /generate_access_token/v0 now only needs refresh token (removed validation).
61
+ - /logout/v0 now only needs refresh token (removed validation).
62
+ - /update_user_app_ids/v0 now only updates ids for self (user). added access token as input param and removed user_id.
63
+ - /get_user_app_ids/v0 is now /get_user_details/v0 with access token as the only input param.
64
+
46
65
  ### v2.0.0
47
66
 
48
67
  - authentication module needs to be used across applications so
@@ -1,15 +1,17 @@
1
1
  square_authentication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  square_authentication/configuration.py,sha256=i0uNtNSQd-n1rBxFM6jsIz1Wy3d090RcdTViSqHKc7Y,2973
3
3
  square_authentication/main.py,sha256=JK9KBmN73KL8EpKrXrjrwwf37bmC4AXrFHtfl2roYwQ,1636
4
- square_authentication/messages.py,sha256=NHJLnaB-qj69VJrMkmbMXDHJUnakQLht14ZBrw4dGgg,1094
4
+ square_authentication/messages.py,sha256=BA9KC0vW9UD1ZXT4VneVqVNLlgdbMdsAwAgxhJISLf4,1175
5
5
  square_authentication/data/config.ini,sha256=_740RvKpL5W2bUDGwZ7ePwuP-mAasr5cXXB81yq_Jv8,906
6
+ square_authentication/pydantic_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ square_authentication/pydantic_models/core.py,sha256=Hwzr-YJH6GVGLp4Z29iHItOEhiEvk65MjsttmCaDTe4,431
6
8
  square_authentication/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- square_authentication/routes/core.py,sha256=gwfNQcMvT5DdbZQBotLPwy1Actr7SBSgrimfHfMviYs,33617
9
+ square_authentication/routes/core.py,sha256=o9FeIGMdQbkBLZ0d9GdyEOJJkRMBsYRyUKOO9l5vWgA,44551
8
10
  square_authentication/routes/utility.py,sha256=Kx4S4tZ1GKsPoC8CoZ4fkLEebvr02KeFEPePtTHtpnQ,75
9
11
  square_authentication/utils/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
10
12
  square_authentication/utils/encryption.py,sha256=T6BShoUr_xeGpbfPgTK-GxTlXPwcjwU4c4KW7KPzrF8,1865
11
13
  square_authentication/utils/token.py,sha256=Y_arg5LegX-aprMj9YweUK8jjNZLGDjLUGgxbUA12w4,560
12
- square_authentication-2.0.0.dist-info/METADATA,sha256=BrxsipcwD1dQNTUeM7pXXtAOZMPXBO1i4n7tRRbXx7s,1966
13
- square_authentication-2.0.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
14
- square_authentication-2.0.0.dist-info/top_level.txt,sha256=wDssVJIl9KIEJPj5rR3rv4uRI7yCndMBrvHd_6BGXQA,22
15
- square_authentication-2.0.0.dist-info/RECORD,,
14
+ square_authentication-4.0.0.dist-info/METADATA,sha256=spjL_EnrXbwC-em5JgyrzlCq0HD0QHDzXEagx9DzLHM,2810
15
+ square_authentication-4.0.0.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
16
+ square_authentication-4.0.0.dist-info/top_level.txt,sha256=wDssVJIl9KIEJPj5rR3rv4uRI7yCndMBrvHd_6BGXQA,22
17
+ square_authentication-4.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.44.0)
2
+ Generator: bdist_wheel (0.45.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5