Skip to content

Commit b6b174b

Browse files
committed
Add auth JSON API subset coverage
1 parent 9fcc240 commit b6b174b

3 files changed

Lines changed: 666 additions & 1 deletion

File tree

insforge/auth/client.py

Lines changed: 203 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,25 @@
44
from typing import Any
55

66
from .._base_client import BaseClient
7+
from .._utils import quote_path_segment
78
from ..exceptions import InsforgeAuthError
8-
from .models import CurrentProfileResponse, SignInResponse
9+
from .models import AdminSessionExchangeRequest
10+
from .models import AnonymousTokenResponse
11+
from .models import AuthConfigResponse
12+
from .models import AuthConfigUpdateRequest
13+
from .models import AuthCurrentSessionResponse
14+
from .models import AuthDeleteUsersRequest
15+
from .models import AuthDeleteUsersResponse
16+
from .models import AuthSessionResponse
17+
from .models import AuthUserCreateRequest
18+
from .models import AuthUsersResponse
19+
from .models import CurrentProfileResponse
20+
from .models import LogoutResponse
21+
from .models import ProfileResponse
22+
from .models import PublicAuthConfigResponse
23+
from .models import RefreshRequest
24+
from .models import SignInResponse
25+
from .models import UserResponse
926

1027

1128
class AuthClient:
@@ -26,6 +43,191 @@ async def sign_in_with_password(
2643
)
2744
return SignInResponse.model_validate(payload)
2845

46+
async def get_public_config(self) -> PublicAuthConfigResponse:
47+
payload = await self._client._request_json("GET", "/api/auth/public-config")
48+
return PublicAuthConfigResponse.model_validate(payload)
49+
50+
async def get_profile(self, user_id: str) -> ProfileResponse:
51+
payload = await self._client._request_json(
52+
"GET",
53+
f"/api/auth/profiles/{quote_path_segment(user_id)}",
54+
)
55+
return ProfileResponse.model_validate(payload)
56+
57+
async def get_config(
58+
self,
59+
*,
60+
access_token: str | None = None,
61+
) -> AuthConfigResponse:
62+
payload = await self._client._request_json(
63+
"GET",
64+
"/api/auth/config",
65+
access_token=access_token,
66+
)
67+
return AuthConfigResponse.model_validate(payload)
68+
69+
async def update_config(
70+
self,
71+
config: Mapping[str, Any],
72+
*,
73+
access_token: str | None = None,
74+
) -> AuthConfigResponse:
75+
payload = AuthConfigUpdateRequest.model_validate(config).model_dump(
76+
by_alias=True,
77+
exclude_none=True,
78+
)
79+
response = await self._client._request_json(
80+
"PUT",
81+
"/api/auth/config",
82+
json=payload,
83+
access_token=access_token,
84+
exception_cls=InsforgeAuthError,
85+
)
86+
return AuthConfigResponse.model_validate(response)
87+
88+
async def list_users(
89+
self,
90+
*,
91+
offset: int | str | None = None,
92+
limit: int | str | None = None,
93+
search: str | None = None,
94+
access_token: str | None = None,
95+
) -> AuthUsersResponse:
96+
params: dict[str, str] = {}
97+
if offset is not None:
98+
params["offset"] = str(offset)
99+
if limit is not None:
100+
params["limit"] = str(limit)
101+
if search is not None:
102+
params["search"] = search
103+
104+
payload = await self._client._request_json(
105+
"GET",
106+
"/api/auth/users",
107+
params=params or None,
108+
access_token=access_token,
109+
)
110+
return AuthUsersResponse.model_validate(payload)
111+
112+
async def create_user(
113+
self,
114+
*,
115+
email: str,
116+
password: str,
117+
name: str | None = None,
118+
redirect_to: str | None = None,
119+
) -> AuthSessionResponse:
120+
payload = AuthUserCreateRequest(
121+
email=email,
122+
password=password,
123+
name=name,
124+
redirect_to=redirect_to,
125+
).model_dump(by_alias=True, exclude_none=True)
126+
response = await self._client._request_json(
127+
"POST",
128+
"/api/auth/users",
129+
json=payload,
130+
exception_cls=InsforgeAuthError,
131+
)
132+
return AuthSessionResponse.model_validate(response)
133+
134+
async def delete_users(
135+
self,
136+
user_ids: list[str],
137+
*,
138+
access_token: str | None = None,
139+
) -> AuthDeleteUsersResponse:
140+
payload = AuthDeleteUsersRequest(user_ids=user_ids).model_dump(by_alias=True)
141+
response = await self._client._request_json(
142+
"DELETE",
143+
"/api/auth/users",
144+
json=payload,
145+
access_token=access_token,
146+
)
147+
return AuthDeleteUsersResponse.model_validate(response)
148+
149+
async def get_user(
150+
self,
151+
user_id: str,
152+
*,
153+
access_token: str | None = None,
154+
) -> UserResponse:
155+
payload = await self._client._request_json(
156+
"GET",
157+
f"/api/auth/users/{quote_path_segment(user_id)}",
158+
access_token=access_token,
159+
)
160+
return UserResponse.model_validate(payload)
161+
162+
async def refresh(
163+
self,
164+
*,
165+
refresh_token: str,
166+
) -> AuthSessionResponse:
167+
payload = await self._client._request_json(
168+
"POST",
169+
"/api/auth/refresh",
170+
params={"client_type": "server"},
171+
json=RefreshRequest(refresh_token=refresh_token).model_dump(by_alias=True),
172+
exception_cls=InsforgeAuthError,
173+
)
174+
return AuthSessionResponse.model_validate(payload)
175+
176+
async def logout(self) -> LogoutResponse:
177+
payload = await self._client._request_json("POST", "/api/auth/logout")
178+
return LogoutResponse.model_validate(payload)
179+
180+
async def get_current_session(
181+
self,
182+
*,
183+
access_token: str | None = None,
184+
) -> AuthCurrentSessionResponse:
185+
payload = await self._client._request_json(
186+
"GET",
187+
"/api/auth/sessions/current",
188+
access_token=access_token,
189+
)
190+
return AuthCurrentSessionResponse.model_validate(payload)
191+
192+
async def admin_sign_in(
193+
self,
194+
*,
195+
email: str,
196+
password: str,
197+
) -> AuthSessionResponse:
198+
payload = await self._client._request_json(
199+
"POST",
200+
"/api/auth/admin/sessions",
201+
json={"email": email, "password": password},
202+
exception_cls=InsforgeAuthError,
203+
)
204+
return AuthSessionResponse.model_validate(payload)
205+
206+
async def exchange_admin_session(
207+
self,
208+
*,
209+
code: str,
210+
) -> AuthSessionResponse:
211+
payload = await self._client._request_json(
212+
"POST",
213+
"/api/auth/admin/sessions/exchange",
214+
json=AdminSessionExchangeRequest(code=code).model_dump(by_alias=True),
215+
exception_cls=InsforgeAuthError,
216+
)
217+
return AuthSessionResponse.model_validate(payload)
218+
219+
async def create_anon_token(
220+
self,
221+
*,
222+
access_token: str | None = None,
223+
) -> AnonymousTokenResponse:
224+
payload = await self._client._request_json(
225+
"POST",
226+
"/api/auth/tokens/anon",
227+
access_token=access_token,
228+
)
229+
return AnonymousTokenResponse.model_validate(payload)
230+
29231
async def update_current_profile(
30232
self,
31233
profile: Mapping[str, Any],

insforge/auth/models.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from datetime import datetime
34
from typing import Any
45

56
from pydantic import BaseModel, ConfigDict, Field
@@ -17,3 +18,156 @@ class CurrentProfileResponse(BaseModel):
1718

1819
user_id: str = Field(alias="userId")
1920
profile: dict[str, Any]
21+
22+
23+
class PublicAuthConfigResponse(BaseModel):
24+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
25+
26+
o_auth_providers: list[str] = Field(default_factory=list, alias="oAuthProviders")
27+
custom_o_auth_providers: list[str] = Field(default_factory=list, alias="customOAuthProviders")
28+
require_email_verification: bool | None = Field(default=None, alias="requireEmailVerification")
29+
password_min_length: int | None = Field(default=None, alias="passwordMinLength")
30+
require_number: bool | None = Field(default=None, alias="requireNumber")
31+
require_lowercase: bool | None = Field(default=None, alias="requireLowercase")
32+
require_uppercase: bool | None = Field(default=None, alias="requireUppercase")
33+
require_special_char: bool | None = Field(default=None, alias="requireSpecialChar")
34+
verify_email_method: str | None = Field(default=None, alias="verifyEmailMethod")
35+
reset_password_method: str | None = Field(default=None, alias="resetPasswordMethod")
36+
37+
38+
class ProfileResponse(BaseModel):
39+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
40+
41+
id: str
42+
profile: dict[str, Any] | None = None
43+
44+
45+
class AuthConfigResponse(BaseModel):
46+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
47+
48+
id: str | None = None
49+
require_email_verification: bool | None = Field(default=None, alias="requireEmailVerification")
50+
password_min_length: int | None = Field(default=None, alias="passwordMinLength")
51+
require_number: bool | None = Field(default=None, alias="requireNumber")
52+
require_lowercase: bool | None = Field(default=None, alias="requireLowercase")
53+
require_uppercase: bool | None = Field(default=None, alias="requireUppercase")
54+
require_special_char: bool | None = Field(default=None, alias="requireSpecialChar")
55+
verify_email_method: str | None = Field(default=None, alias="verifyEmailMethod")
56+
reset_password_method: str | None = Field(default=None, alias="resetPasswordMethod")
57+
allowed_redirect_urls: list[str] = Field(default_factory=list, alias="allowedRedirectUrls")
58+
created_at: datetime | None = Field(default=None, alias="createdAt")
59+
updated_at: datetime | None = Field(default=None, alias="updatedAt")
60+
61+
62+
class AuthConfigUpdateRequest(BaseModel):
63+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
64+
65+
require_email_verification: bool | None = Field(default=None, alias="requireEmailVerification")
66+
password_min_length: int | None = Field(default=None, alias="passwordMinLength")
67+
require_number: bool | None = Field(default=None, alias="requireNumber")
68+
require_lowercase: bool | None = Field(default=None, alias="requireLowercase")
69+
require_uppercase: bool | None = Field(default=None, alias="requireUppercase")
70+
require_special_char: bool | None = Field(default=None, alias="requireSpecialChar")
71+
verify_email_method: str | None = Field(default=None, alias="verifyEmailMethod")
72+
reset_password_method: str | None = Field(default=None, alias="resetPasswordMethod")
73+
allowed_redirect_urls: list[str] | None = Field(default=None, alias="allowedRedirectUrls")
74+
75+
76+
class UserResponse(BaseModel):
77+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
78+
79+
id: str | None = None
80+
email: str | None = None
81+
profile: dict[str, Any] | None = None
82+
metadata: dict[str, Any] | None = None
83+
email_verified: bool | None = Field(default=None, alias="emailVerified")
84+
providers: list[str] | None = None
85+
created_at: datetime | None = Field(default=None, alias="createdAt")
86+
updated_at: datetime | None = Field(default=None, alias="updatedAt")
87+
88+
89+
class AuthUserCreateRequest(BaseModel):
90+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
91+
92+
email: str
93+
password: str
94+
name: str | None = None
95+
redirect_to: str | None = Field(default=None, alias="redirectTo")
96+
97+
98+
class AuthPagination(BaseModel):
99+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
100+
101+
offset: int | None = None
102+
limit: int | None = None
103+
total: int | None = None
104+
105+
106+
class AuthUsersResponse(BaseModel):
107+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
108+
109+
data: list[UserResponse] = Field(default_factory=list)
110+
pagination: AuthPagination | None = None
111+
112+
113+
class AuthDeleteUsersRequest(BaseModel):
114+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
115+
116+
user_ids: list[str] = Field(alias="userIds")
117+
118+
119+
class AuthDeleteUsersResponse(BaseModel):
120+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
121+
122+
message: str | None = None
123+
deleted_count: int | None = Field(default=None, alias="deletedCount")
124+
125+
126+
class AuthCurrentSessionUser(BaseModel):
127+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
128+
129+
id: str | None = None
130+
email: str | None = None
131+
role: str | None = None
132+
133+
134+
class AuthCurrentSessionResponse(BaseModel):
135+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
136+
137+
user: AuthCurrentSessionUser
138+
139+
140+
class AuthSessionResponse(BaseModel):
141+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
142+
143+
user: UserResponse | None = None
144+
access_token: str | None = Field(default=None, alias="accessToken")
145+
csrf_token: str | None = Field(default=None, alias="csrfToken")
146+
refresh_token: str | None = Field(default=None, alias="refreshToken")
147+
require_email_verification: bool | None = Field(default=None, alias="requireEmailVerification")
148+
149+
150+
class RefreshRequest(BaseModel):
151+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
152+
153+
refresh_token: str = Field(alias="refreshToken")
154+
155+
156+
class AdminSessionExchangeRequest(BaseModel):
157+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
158+
159+
code: str
160+
161+
162+
class LogoutResponse(BaseModel):
163+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
164+
165+
success: bool | None = None
166+
message: str | None = None
167+
168+
169+
class AnonymousTokenResponse(BaseModel):
170+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
171+
172+
access_token: str | None = Field(default=None, alias="accessToken")
173+
message: str | None = None

0 commit comments

Comments
 (0)