1
1
import contextlib
2
2
import json
3
3
import uuid
4
+ from enum import Enum
4
5
from typing import Any , Dict , List , Mapping , Optional , Tuple , TypedDict , Union
5
6
from urllib .parse import urlencode
6
7
13
14
HTTPXClient = Union [httpx .Client , httpx .AsyncClient ]
14
15
15
16
17
+ class FiefACR (str , Enum ):
18
+ """
19
+ List of defined Authentication Context Class Reference.
20
+ """
21
+
22
+ LEVEL_ZERO = "0"
23
+ """Level 0. No authentication was performed, a previous session was used."""
24
+ LEVEL_ONE = "1"
25
+ """Level 1. Password authentication was performed."""
26
+
27
+
16
28
class FiefTokenResponse (TypedDict ):
17
29
"""
18
30
Typed dictionary containing the tokens and related information returned by Fief after a successful authentication.
@@ -40,6 +52,7 @@ class FiefAccessTokenInfo(TypedDict):
40
52
{
41
53
"id": "aeeb8bfa-e8f4-4724-9427-c3d5af66190e",
42
54
"scope": ["openid", "required_scope"],
55
+ "acr": "1",
43
56
"permissions": ["castles:read", "castles:create", "castles:update", "castles:delete"],
44
57
"access_token": "ACCESS_TOKEN",
45
58
}
@@ -50,6 +63,8 @@ class FiefAccessTokenInfo(TypedDict):
50
63
"""ID of the user."""
51
64
scope : List [str ]
52
65
"""List of granted scopes for this access token."""
66
+ acr : FiefACR
67
+ """Level of Authentication Context class Reference."""
53
68
permissions : List [str ]
54
69
"""List of [granted permissions](https://docs.fief.dev/getting-started/access-control/) for this user."""
55
70
access_token : str
@@ -259,6 +274,7 @@ def _validate_access_token(
259
274
return {
260
275
"id" : uuid .UUID (claims ["sub" ]),
261
276
"scope" : access_token_scope ,
277
+ "acr" : claims ["acr" ],
262
278
"permissions" : permissions ,
263
279
"access_token" : access_token ,
264
280
}
@@ -367,6 +383,51 @@ def _get_update_profile_request(
367
383
json = data ,
368
384
)
369
385
386
+ def _get_change_password_request (
387
+ self ,
388
+ client : HTTPXClient ,
389
+ * ,
390
+ endpoint : str ,
391
+ access_token : str ,
392
+ new_password : str ,
393
+ ) -> httpx .Request :
394
+ return client .build_request (
395
+ "PATCH" ,
396
+ endpoint ,
397
+ headers = {"Authorization" : f"Bearer { access_token } " },
398
+ json = {"password" : new_password },
399
+ )
400
+
401
+ def _get_email_change_request (
402
+ self ,
403
+ client : HTTPXClient ,
404
+ * ,
405
+ endpoint : str ,
406
+ access_token : str ,
407
+ email : str ,
408
+ ) -> httpx .Request :
409
+ return client .build_request (
410
+ "PATCH" ,
411
+ endpoint ,
412
+ headers = {"Authorization" : f"Bearer { access_token } " },
413
+ json = {"email" : email },
414
+ )
415
+
416
+ def _get_email_verify_request (
417
+ self ,
418
+ client : HTTPXClient ,
419
+ * ,
420
+ endpoint : str ,
421
+ access_token : str ,
422
+ code : str ,
423
+ ) -> httpx .Request :
424
+ return client .build_request (
425
+ "PATCH" ,
426
+ endpoint ,
427
+ headers = {"Authorization" : f"Bearer { access_token } " },
428
+ json = {"code" : code },
429
+ )
430
+
370
431
def _handle_request_error (self , response : httpx .Response ):
371
432
if response .is_error :
372
433
raise FiefRequestError (response .status_code , response .text )
@@ -594,34 +655,115 @@ def update_profile(self, access_token: str, data: Dict[str, Any]) -> FiefUserInf
594
655
:param access_token: A valid access token.
595
656
:param data: A dictionary containing the data to update.
596
657
597
- **Example: Update email address**
658
+ **Example: Update user field**
659
+
660
+ To update [user field](https://docs.fief.dev/getting-started/user-fields/) values, you need to nest them into a `fields` dictionary, indexed by their slug.
598
661
599
662
```py
600
- userinfo = fief.update_profile("ACCESS_TOKEN", { "email ": "[email protected] " })
663
+ userinfo = fief.update_profile("ACCESS_TOKEN", { "fields ": { "first_name": "Anne" } })
601
664
```
665
+ """
666
+ update_profile_endpoint = f"{ self .base_url } /api/profile"
602
667
603
- **Example: Update password**
668
+ with self ._get_httpx_client () as client :
669
+ request = self ._get_update_profile_request (
670
+ client ,
671
+ endpoint = update_profile_endpoint ,
672
+ access_token = access_token ,
673
+ data = data ,
674
+ )
675
+ response = client .send (request )
676
+
677
+ self ._handle_request_error (response )
678
+
679
+ return response .json ()
680
+
681
+ def change_password (self , access_token : str , new_password : str ) -> FiefUserInfo :
682
+ """
683
+ Change the user password with the Fief API using a valid access token.
684
+
685
+ **An access token with an ACR of at least level 1 is required.**
686
+
687
+ :param access_token: A valid access token.
688
+ :param new_password: The new password.
689
+
690
+ **Example**
604
691
605
692
```py
606
- userinfo = fief.update_profile ("ACCESS_TOKEN", { "password": " herminetincture" } )
693
+ userinfo = fief.change_password ("ACCESS_TOKEN", " herminetincture")
607
694
```
695
+ """
696
+ change_password_profile_endpoint = f"{ self .base_url } /api/password"
608
697
609
- **Example: Update user field**
698
+ with self ._get_httpx_client () as client :
699
+ request = self ._get_change_password_request (
700
+ client ,
701
+ endpoint = change_password_profile_endpoint ,
702
+ access_token = access_token ,
703
+ new_password = new_password ,
704
+ )
705
+ response = client .send (request )
610
706
611
- To update [user field](https://docs.fief.dev/getting-started/user-fields/) values, you need to nest them into a `fields` dictionary, indexed by their slug.
707
+ self ._handle_request_error (response )
708
+
709
+ return response .json ()
710
+
711
+ def email_change (self , access_token : str , email : str ) -> FiefUserInfo :
712
+ """
713
+ Request an email change with the Fief API using a valid access token.
714
+
715
+ The user will receive a verification code on this new email address.
716
+ It shall be used with the method `email_verify` to complete the modification.
717
+
718
+ **An access token with an ACR of at least level 1 is required.**
719
+
720
+ :param access_token: A valid access token.
721
+ :param email: The new email address.
722
+
723
+ **Example**
612
724
613
725
```py
614
- userinfo = fief.update_profile ("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } } )
726
+ userinfo = fief.email_change ("ACCESS_TOKEN", "[email protected] " )
615
727
```
616
728
"""
617
- update_profile_endpoint = f"{ self .base_url } /api/profile "
729
+ email_change_endpoint = f"{ self .base_url } /api/email/change "
618
730
619
731
with self ._get_httpx_client () as client :
620
- request = self ._get_update_profile_request (
732
+ request = self ._get_email_change_request (
621
733
client ,
622
- endpoint = update_profile_endpoint ,
734
+ endpoint = email_change_endpoint ,
623
735
access_token = access_token ,
624
- data = data ,
736
+ email = email ,
737
+ )
738
+ response = client .send (request )
739
+
740
+ self ._handle_request_error (response )
741
+
742
+ return response .json ()
743
+
744
+ def email_verify (self , access_token : str , code : str ) -> FiefUserInfo :
745
+ """
746
+ Verify the user email with the Fief API using a valid access token and verification code.
747
+
748
+ **An access token with an ACR of at least level 1 is required.**
749
+
750
+ :param access_token: A valid access token.
751
+ :param code: The verification code received by email.
752
+
753
+ **Example**
754
+
755
+ ```py
756
+ userinfo = fief.email_verify("ACCESS_TOKEN", "ABCDE")
757
+ ```
758
+ """
759
+ email_verify_endpoint = f"{ self .base_url } /api/email/verify"
760
+
761
+ with self ._get_httpx_client () as client :
762
+ request = self ._get_email_verify_request (
763
+ client ,
764
+ endpoint = email_verify_endpoint ,
765
+ access_token = access_token ,
766
+ code = code ,
625
767
)
626
768
response = client .send (request )
627
769
@@ -924,34 +1066,117 @@ async def update_profile(
924
1066
:param access_token: A valid access token.
925
1067
:param data: A dictionary containing the data to update.
926
1068
927
- **Example: Update email address**
1069
+ **Example: Update user field**
1070
+
1071
+ To update [user field](https://docs.fief.dev/getting-started/user-fields/) values, you need to nest them into a `fields` dictionary, indexed by their slug.
928
1072
929
1073
```py
930
- userinfo = await fief.update_profile("ACCESS_TOKEN", { "email ": "[email protected] " })
1074
+ userinfo = await fief.update_profile("ACCESS_TOKEN", { "fields ": { "first_name": "Anne" } })
931
1075
```
1076
+ """
1077
+ update_profile_endpoint = f"{ self .base_url } /api/profile"
1078
+
1079
+ async with self ._get_httpx_client () as client :
1080
+ request = self ._get_update_profile_request (
1081
+ client ,
1082
+ endpoint = update_profile_endpoint ,
1083
+ access_token = access_token ,
1084
+ data = data ,
1085
+ )
1086
+ response = await client .send (request )
1087
+
1088
+ self ._handle_request_error (response )
1089
+
1090
+ return response .json ()
1091
+
1092
+ async def change_password (
1093
+ self , access_token : str , new_password : str
1094
+ ) -> FiefUserInfo :
1095
+ """
1096
+ Change the user password with the Fief API using a valid access token.
1097
+
1098
+ **An access token with an ACR of at least level 1 is required.**
1099
+
1100
+ :param access_token: A valid access token.
1101
+ :param new_password: The new password.
932
1102
933
- **Example: Update password **
1103
+ **Example**
934
1104
935
1105
```py
936
- userinfo = await fief.update_profile ("ACCESS_TOKEN", { "password": " herminetincture" } )
1106
+ userinfo = await fief.change_password ("ACCESS_TOKEN", " herminetincture")
937
1107
```
1108
+ """
1109
+ change_password_profile_endpoint = f"{ self .base_url } /api/password"
938
1110
939
- **Example: Update user field**
1111
+ async with self ._get_httpx_client () as client :
1112
+ request = self ._get_change_password_request (
1113
+ client ,
1114
+ endpoint = change_password_profile_endpoint ,
1115
+ access_token = access_token ,
1116
+ new_password = new_password ,
1117
+ )
1118
+ response = await client .send (request )
940
1119
941
- To update [user field](https://docs.fief.dev/getting-started/user-fields/) values, you need to nest them into a `fields` dictionary, indexed by their slug.
1120
+ self ._handle_request_error (response )
1121
+
1122
+ return response .json ()
1123
+
1124
+ async def email_change (self , access_token : str , email : str ) -> FiefUserInfo :
1125
+ """
1126
+ Request an email change with the Fief API using a valid access token.
1127
+
1128
+ The user will receive a verification code on this new email address.
1129
+ It shall be used with the method `email_verify` to complete the modification.
1130
+
1131
+ **An access token with an ACR of at least level 1 is required.**
1132
+
1133
+ :param access_token: A valid access token.
1134
+ :param email: The new email address.
1135
+
1136
+ **Example**
942
1137
943
1138
```py
944
- userinfo = await fief.update_profile ("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } } )
1139
+ userinfo = await fief.email_change ("ACCESS_TOKEN", "[email protected] " )
945
1140
```
946
1141
"""
947
- update_profile_endpoint = f"{ self .base_url } /api/profile "
1142
+ email_change_endpoint = f"{ self .base_url } /api/email/change "
948
1143
949
1144
async with self ._get_httpx_client () as client :
950
- request = self ._get_update_profile_request (
1145
+ request = self ._get_email_change_request (
951
1146
client ,
952
- endpoint = update_profile_endpoint ,
1147
+ endpoint = email_change_endpoint ,
953
1148
access_token = access_token ,
954
- data = data ,
1149
+ email = email ,
1150
+ )
1151
+ response = await client .send (request )
1152
+
1153
+ self ._handle_request_error (response )
1154
+
1155
+ return response .json ()
1156
+
1157
+ async def email_verify (self , access_token : str , code : str ) -> FiefUserInfo :
1158
+ """
1159
+ Verify the user email with the Fief API using a valid access token and verification code.
1160
+
1161
+ **An access token with an ACR of at least level 1 is required.**
1162
+
1163
+ :param access_token: A valid access token.
1164
+ :param code: The verification code received by email.
1165
+
1166
+ **Example**
1167
+
1168
+ ```py
1169
+ userinfo = fief.email_verify("ACCESS_TOKEN", "ABCDE")
1170
+ ```
1171
+ """
1172
+ email_verify_endpoint = f"{ self .base_url } /api/email/verify"
1173
+
1174
+ async with self ._get_httpx_client () as client :
1175
+ request = self ._get_email_verify_request (
1176
+ client ,
1177
+ endpoint = email_verify_endpoint ,
1178
+ access_token = access_token ,
1179
+ code = code ,
955
1180
)
956
1181
response = await client .send (request )
957
1182
0 commit comments