Skip to content
This repository was archived by the owner on Apr 26, 2025. It is now read-only.

Commit de66076

Browse files
committed
Implement new change password and email methods
1 parent 07f4f77 commit de66076

File tree

6 files changed

+330
-47
lines changed

6 files changed

+330
-47
lines changed

fief_client/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
FiefAccessTokenInvalid,
77
FiefAccessTokenMissingPermission,
88
FiefAccessTokenMissingScope,
9+
FiefACR,
910
FiefAsync,
1011
FiefError,
1112
FiefIdTokenInvalid,
@@ -18,6 +19,7 @@
1819

1920
__all__ = [
2021
"Fief",
22+
"FiefACR",
2123
"FiefAsync",
2224
"FiefTokenResponse",
2325
"FiefAccessTokenInfo",

fief_client/client.py

Lines changed: 247 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import contextlib
22
import json
33
import uuid
4+
from enum import Enum
45
from typing import Any, Dict, List, Mapping, Optional, Tuple, TypedDict, Union
56
from urllib.parse import urlencode
67

@@ -13,6 +14,17 @@
1314
HTTPXClient = Union[httpx.Client, httpx.AsyncClient]
1415

1516

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+
1628
class FiefTokenResponse(TypedDict):
1729
"""
1830
Typed dictionary containing the tokens and related information returned by Fief after a successful authentication.
@@ -40,6 +52,7 @@ class FiefAccessTokenInfo(TypedDict):
4052
{
4153
"id": "aeeb8bfa-e8f4-4724-9427-c3d5af66190e",
4254
"scope": ["openid", "required_scope"],
55+
"acr": "1",
4356
"permissions": ["castles:read", "castles:create", "castles:update", "castles:delete"],
4457
"access_token": "ACCESS_TOKEN",
4558
}
@@ -50,6 +63,8 @@ class FiefAccessTokenInfo(TypedDict):
5063
"""ID of the user."""
5164
scope: List[str]
5265
"""List of granted scopes for this access token."""
66+
acr: FiefACR
67+
"""Level of Authentication Context class Reference."""
5368
permissions: List[str]
5469
"""List of [granted permissions](https://docs.fief.dev/getting-started/access-control/) for this user."""
5570
access_token: str
@@ -259,6 +274,7 @@ def _validate_access_token(
259274
return {
260275
"id": uuid.UUID(claims["sub"]),
261276
"scope": access_token_scope,
277+
"acr": claims["acr"],
262278
"permissions": permissions,
263279
"access_token": access_token,
264280
}
@@ -367,6 +383,51 @@ def _get_update_profile_request(
367383
json=data,
368384
)
369385

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+
370431
def _handle_request_error(self, response: httpx.Response):
371432
if response.is_error:
372433
raise FiefRequestError(response.status_code, response.text)
@@ -594,34 +655,115 @@ def update_profile(self, access_token: str, data: Dict[str, Any]) -> FiefUserInf
594655
:param access_token: A valid access token.
595656
:param data: A dictionary containing the data to update.
596657
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.
598661
599662
```py
600-
userinfo = fief.update_profile("ACCESS_TOKEN", { "email": "[email protected]" })
663+
userinfo = fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
601664
```
665+
"""
666+
update_profile_endpoint = f"{self.base_url}/api/profile"
602667

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**
604691
605692
```py
606-
userinfo = fief.update_profile("ACCESS_TOKEN", { "password": "herminetincture" })
693+
userinfo = fief.change_password("ACCESS_TOKEN", "herminetincture")
607694
```
695+
"""
696+
change_password_profile_endpoint = f"{self.base_url}/api/password"
608697

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)
610706

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**
612724
613725
```py
614-
userinfo = fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
726+
userinfo = fief.email_change("ACCESS_TOKEN", "[email protected]")
615727
```
616728
"""
617-
update_profile_endpoint = f"{self.base_url}/api/profile"
729+
email_change_endpoint = f"{self.base_url}/api/email/change"
618730

619731
with self._get_httpx_client() as client:
620-
request = self._get_update_profile_request(
732+
request = self._get_email_change_request(
621733
client,
622-
endpoint=update_profile_endpoint,
734+
endpoint=email_change_endpoint,
623735
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,
625767
)
626768
response = client.send(request)
627769

@@ -924,34 +1066,117 @@ async def update_profile(
9241066
:param access_token: A valid access token.
9251067
:param data: A dictionary containing the data to update.
9261068
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.
9281072
9291073
```py
930-
userinfo = await fief.update_profile("ACCESS_TOKEN", { "email": "[email protected]" })
1074+
userinfo = await fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
9311075
```
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.
9321102
933-
**Example: Update password**
1103+
**Example**
9341104
9351105
```py
936-
userinfo = await fief.update_profile("ACCESS_TOKEN", { "password": "herminetincture" })
1106+
userinfo = await fief.change_password("ACCESS_TOKEN", "herminetincture")
9371107
```
1108+
"""
1109+
change_password_profile_endpoint = f"{self.base_url}/api/password"
9381110

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)
9401119

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**
9421137
9431138
```py
944-
userinfo = await fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
1139+
userinfo = await fief.email_change("ACCESS_TOKEN", "[email protected]")
9451140
```
9461141
"""
947-
update_profile_endpoint = f"{self.base_url}/api/profile"
1142+
email_change_endpoint = f"{self.base_url}/api/email/change"
9481143

9491144
async with self._get_httpx_client() as client:
950-
request = self._get_update_profile_request(
1145+
request = self._get_email_change_request(
9511146
client,
952-
endpoint=update_profile_endpoint,
1147+
endpoint=email_change_endpoint,
9531148
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,
9551180
)
9561181
response = await client.send(request)
9571182

0 commit comments

Comments
 (0)