memydev-auth-sdk 0.1.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.
- memyauth/__init__.py +144 -0
- memyauth/_client.py +169 -0
- memyauth/_config.py +407 -0
- memyauth/_discovery.py +172 -0
- memyauth/_errors.py +270 -0
- memyauth/_iam.py +298 -0
- memyauth/_idtoken.py +165 -0
- memyauth/_oidc.py +237 -0
- memyauth/_pkce.py +61 -0
- memyauth/_roles.py +109 -0
- memyauth/_session.py +149 -0
- memyauth/_state.py +139 -0
- memyauth/_sync/__init__.py +3 -0
- memyauth/_sync/_client.py +171 -0
- memyauth/_sync/_discovery.py +174 -0
- memyauth/_sync/_iam.py +300 -0
- memyauth/_sync/_idtoken.py +167 -0
- memyauth/_sync/_oidc.py +239 -0
- memyauth/_sync/_transport.py +223 -0
- memyauth/_transport.py +221 -0
- memyauth/_types.py +309 -0
- memyauth/py.typed +0 -0
- memyauth/sync/__init__.py +147 -0
- memydev_auth_sdk-0.1.0.dist-info/METADATA +86 -0
- memydev_auth_sdk-0.1.0.dist-info/RECORD +26 -0
- memydev_auth_sdk-0.1.0.dist-info/WHEEL +4 -0
memyauth/_types.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@fileoverview Shared wire/projection type shapes and injection-seam protocols.
|
|
3
|
+
@module memyauth._types
|
|
4
|
+
@description TypedDict/Literal shapes for every public return value plus the Clock/RandomSource
|
|
5
|
+
protocols injected into every service (discovery, oidc, iam, idtoken, session, state). No
|
|
6
|
+
pydantic, no dataclasses — zero extra runtime dependency beyond httpx + PyJWT, matching the
|
|
7
|
+
customer SDK's zero-deps posture (js/ has zero runtime deps beyond jose). `UserContext` and its
|
|
8
|
+
nested shapes are deliberately `total=False`: the provider's `/iam/me` may add fields at any
|
|
9
|
+
time (C.5.1 / D2) and this SDK must never reject an unknown key — see `_iam.py` for the
|
|
10
|
+
hand-written validator that enforces "permissive about unknown fields, strict about known ones."
|
|
11
|
+
@dependencies typing (stdlib only)
|
|
12
|
+
@relatedFiles _iam.py, _discovery.py, _oidc.py, _idtoken.py, _session.py, _state.py, _config.py
|
|
13
|
+
@created 2026-09-06
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
from typing import Any, Literal, Protocol, TypedDict
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Injection seams (present on every service; §C preamble)
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Clock(Protocol):
|
|
27
|
+
def now(self) -> int:
|
|
28
|
+
"""Epoch milliseconds. Mirrors the JS `Clock` seam exactly."""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RandomSource(Protocol):
|
|
33
|
+
def bytes(self, n: int) -> bytes:
|
|
34
|
+
"""`n` cryptographically-random bytes. Mirrors the JS `RandomSource` seam exactly."""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SystemClock:
|
|
39
|
+
"""Default `Clock`: the wall clock, in epoch milliseconds."""
|
|
40
|
+
|
|
41
|
+
def now(self) -> int:
|
|
42
|
+
return int(time.time() * 1000)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Literal enums (§F, §C.10)
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
TokenEndpointAuthMethod = Literal["client_secret_post", "client_secret_basic"]
|
|
50
|
+
IdTokenValidationMode = Literal["local-jwks", "tls-token-endpoint"]
|
|
51
|
+
IamCallerMode = Literal["auto", "appiam", "appidp", "m2m"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Capability 1 — Discovery (§C.1)
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class OidcDiscoveryDocument(TypedDict, total=False):
|
|
60
|
+
issuer: str
|
|
61
|
+
authorization_endpoint: str
|
|
62
|
+
token_endpoint: str
|
|
63
|
+
userinfo_endpoint: str
|
|
64
|
+
jwks_uri: str
|
|
65
|
+
end_session_endpoint: str
|
|
66
|
+
scopes_supported: list[str]
|
|
67
|
+
id_token_signing_alg_values_supported: list[str]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DiscoveryHealth(TypedDict):
|
|
71
|
+
state: Literal["ok", "failed", "unknown"]
|
|
72
|
+
last_success_at: str | None
|
|
73
|
+
last_error: str | None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# Capability 2 — Authorization URL (§C.2)
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AuthorizationRequest(TypedDict):
|
|
82
|
+
url: str
|
|
83
|
+
code_verifier: str
|
|
84
|
+
code_challenge: str
|
|
85
|
+
state: str
|
|
86
|
+
nonce: str
|
|
87
|
+
redirect_uri: str
|
|
88
|
+
scope: str
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Capability 3 — Token exchange / refresh (§C.3)
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class OidcTokenResponse(TypedDict, total=False):
|
|
97
|
+
access_token: str
|
|
98
|
+
token_type: str
|
|
99
|
+
expires_in: int
|
|
100
|
+
id_token: str
|
|
101
|
+
refresh_token: str
|
|
102
|
+
scope: str
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Capability 4 — UserInfo (§C.4)
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class UserInfoTenantMirror(TypedDict, total=False):
|
|
111
|
+
id: str
|
|
112
|
+
code: str
|
|
113
|
+
idp_users_sub: str
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class UserInfo(TypedDict, total=False):
|
|
117
|
+
sub: str
|
|
118
|
+
email: str
|
|
119
|
+
email_verified: bool
|
|
120
|
+
name: str
|
|
121
|
+
picture: str
|
|
122
|
+
roles: list[str]
|
|
123
|
+
custom_data: dict[str, Any]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Capability 5 — IAM (§C.5, §B.1) — vendored (by hand, since this SDK has no
|
|
128
|
+
# sync-contract.mjs equivalent for Python) from iam/api/src/lib/user-context.ts:59-87.
|
|
129
|
+
# Deliberately `total=False` at every level: unknown keys are tolerated (C.5.1); the
|
|
130
|
+
# hand-written validator in _iam.py enforces the KNOWN keys strictly before this shape
|
|
131
|
+
# is ever handed to a caller.
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class UserContextTenant(TypedDict, total=False):
|
|
136
|
+
id: str
|
|
137
|
+
code: str
|
|
138
|
+
idp_users_sub: str
|
|
139
|
+
key: str # 64-char lowercase hex; bearer-equivalent — see tenantKeyRedaction rule.
|
|
140
|
+
created_at: str
|
|
141
|
+
updated_at: str
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class UserContextOrgEntry(TypedDict, total=False):
|
|
145
|
+
id: str
|
|
146
|
+
name: str
|
|
147
|
+
roles: list[str]
|
|
148
|
+
tenant: dict[str, Any] | None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class UserContext(TypedDict, total=False):
|
|
152
|
+
user: dict[str, Any]
|
|
153
|
+
tenant: UserContextTenant
|
|
154
|
+
organizations: dict[str, list[UserContextOrgEntry]]
|
|
155
|
+
permissions: list[str]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# Re-exported alias, matching the JS `export type IamUserContext = UserContext;`
|
|
159
|
+
IamUserContext = UserContext
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class IamOrgEntryTenant(TypedDict, total=False):
|
|
163
|
+
id: str
|
|
164
|
+
code: str
|
|
165
|
+
idp_users_sub: str
|
|
166
|
+
key: str
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class IamOrgEntry(TypedDict):
|
|
170
|
+
id: str
|
|
171
|
+
name: str
|
|
172
|
+
roles: list[str]
|
|
173
|
+
tenant: IamOrgEntryTenant | None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class IamTenant(TypedDict, total=False):
|
|
177
|
+
id: str
|
|
178
|
+
code: str
|
|
179
|
+
idp_users_sub: str
|
|
180
|
+
key: str
|
|
181
|
+
created_at: str
|
|
182
|
+
updated_at: str
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class IamOrganizations(TypedDict):
|
|
186
|
+
own: list[IamOrgEntry]
|
|
187
|
+
as_member: list[IamOrgEntry]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class IamSession(TypedDict, total=False):
|
|
191
|
+
user_id: str
|
|
192
|
+
idp_subject: str
|
|
193
|
+
email: str
|
|
194
|
+
email_verified: bool | None
|
|
195
|
+
display_name: str
|
|
196
|
+
tenant_id: str
|
|
197
|
+
tenant_code: str
|
|
198
|
+
tenant_key: str
|
|
199
|
+
idp_roles: list[str]
|
|
200
|
+
roles: list[str]
|
|
201
|
+
scopes: list[str]
|
|
202
|
+
permissions: list[str]
|
|
203
|
+
is_global_admin: bool
|
|
204
|
+
organizations: IamOrganizations
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# Capability 7 — Session tokens (§C.7) — an SDK-owned family, structurally
|
|
209
|
+
# distinct from provider (ES384) tokens. See _session.py for the six-way separation.
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class SessionAccessInput(TypedDict, total=False):
|
|
214
|
+
sub: str
|
|
215
|
+
tenants_id: str | None
|
|
216
|
+
roles: list[str]
|
|
217
|
+
email: str
|
|
218
|
+
email_verified: bool
|
|
219
|
+
is_global_admin: bool
|
|
220
|
+
auth_method: Literal["oidc", "local"]
|
|
221
|
+
iam_tenant_key: str
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class SessionAccessClaims(SessionAccessInput, total=False):
|
|
225
|
+
type: Literal["access"]
|
|
226
|
+
iss: str
|
|
227
|
+
iat: int
|
|
228
|
+
exp: int
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class SessionRefreshInput(TypedDict, total=False):
|
|
232
|
+
sub: str
|
|
233
|
+
tenants_id: str
|
|
234
|
+
jti: str
|
|
235
|
+
iam_tenant_key: str
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class SessionRefreshClaims(SessionRefreshInput, total=False):
|
|
239
|
+
type: Literal["refresh"]
|
|
240
|
+
iss: str
|
|
241
|
+
iat: int
|
|
242
|
+
exp: int
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# Capability 8 — State cookie (§C.8)
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class StateCookieInput(TypedDict, total=False):
|
|
251
|
+
code_verifier: str
|
|
252
|
+
nonce: str
|
|
253
|
+
state: str
|
|
254
|
+
return_to: str
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class StateCookiePayload(TypedDict):
|
|
258
|
+
code_verifier: str
|
|
259
|
+
nonce: str
|
|
260
|
+
state: str
|
|
261
|
+
return_to: str
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
# Capability C0 — ID-token trust strategies (§C.10)
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class IdpTokenClaims(TypedDict, total=False):
|
|
270
|
+
iss: str
|
|
271
|
+
sub: str
|
|
272
|
+
aud: str | list[str]
|
|
273
|
+
exp: int
|
|
274
|
+
iat: int
|
|
275
|
+
nonce: str
|
|
276
|
+
auth_time: int
|
|
277
|
+
azp: str
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
__all__ = [
|
|
281
|
+
"Clock",
|
|
282
|
+
"RandomSource",
|
|
283
|
+
"SystemClock",
|
|
284
|
+
"TokenEndpointAuthMethod",
|
|
285
|
+
"IdTokenValidationMode",
|
|
286
|
+
"IamCallerMode",
|
|
287
|
+
"OidcDiscoveryDocument",
|
|
288
|
+
"DiscoveryHealth",
|
|
289
|
+
"AuthorizationRequest",
|
|
290
|
+
"OidcTokenResponse",
|
|
291
|
+
"UserInfoTenantMirror",
|
|
292
|
+
"UserInfo",
|
|
293
|
+
"UserContextTenant",
|
|
294
|
+
"UserContextOrgEntry",
|
|
295
|
+
"UserContext",
|
|
296
|
+
"IamUserContext",
|
|
297
|
+
"IamOrgEntryTenant",
|
|
298
|
+
"IamOrgEntry",
|
|
299
|
+
"IamTenant",
|
|
300
|
+
"IamOrganizations",
|
|
301
|
+
"IamSession",
|
|
302
|
+
"SessionAccessInput",
|
|
303
|
+
"SessionAccessClaims",
|
|
304
|
+
"SessionRefreshInput",
|
|
305
|
+
"SessionRefreshClaims",
|
|
306
|
+
"StateCookieInput",
|
|
307
|
+
"StateCookiePayload",
|
|
308
|
+
"IdpTokenClaims",
|
|
309
|
+
]
|
memyauth/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@fileoverview Public synchronous facade — GENERATED twins from `_sync/` + the shared pure modules.
|
|
3
|
+
@module memyauth.sync
|
|
4
|
+
@description Thin re-export layer, hand-written (this file itself is NOT generated — only
|
|
5
|
+
`_sync/*.py` is). `RoleMapper`, `SessionTokenService`, `StateCookieService`,
|
|
6
|
+
`resolve_config`/`ResolvedConfig`, every error class, and every `_types.py` shape are SHARED —
|
|
7
|
+
the identical object the async surface imports, since they carry no `async`/`await` and
|
|
8
|
+
therefore have no sync/async distinction to generate. Only `MemyAuth`, `DiscoveryService`,
|
|
9
|
+
`OidcService`, `IamService` and `IdTokenVerifier` come from `_sync/`, the unasync-generated
|
|
10
|
+
twins of `_client.py`/`_discovery.py`/`_oidc.py`/`_iam.py`/`_idtoken.py`.
|
|
11
|
+
@dependencies ../_sync/* (generated), ../_roles.py, ../_session.py, ../_state.py, ../_config.py,
|
|
12
|
+
../_errors.py, ../_types.py, ../_pkce.py (all shared/pure)
|
|
13
|
+
@relatedFiles ../scripts/gen_sync.py (the generator), ../_sync/__init__.py (generated package
|
|
14
|
+
marker), ../../tests/test_sync_parity.py, ../../tests/test_sync_functional.py
|
|
15
|
+
@created 2026-09-06
|
|
16
|
+
"""
|
|
17
|
+
from .._config import ResolvedConfig, RetryConfig, resolve_config
|
|
18
|
+
from .._errors import (
|
|
19
|
+
BadRequestError,
|
|
20
|
+
ConfigError,
|
|
21
|
+
DiscoveryError,
|
|
22
|
+
DiscoveryEscapeError,
|
|
23
|
+
ForbiddenError,
|
|
24
|
+
GoneError,
|
|
25
|
+
IdTokenError,
|
|
26
|
+
IssuerMismatchError,
|
|
27
|
+
MemyAuthError,
|
|
28
|
+
NetworkError,
|
|
29
|
+
NotFoundError,
|
|
30
|
+
ProtocolError,
|
|
31
|
+
RefreshRejectedError,
|
|
32
|
+
ServiceUnavailableError,
|
|
33
|
+
SessionTokenError,
|
|
34
|
+
StateCookieError,
|
|
35
|
+
StateMismatchError,
|
|
36
|
+
TimeoutError,
|
|
37
|
+
TokenExchangeError,
|
|
38
|
+
UnauthorizedError,
|
|
39
|
+
UnsupportedAlgorithmError,
|
|
40
|
+
UnsupportedAlgorithmPinError,
|
|
41
|
+
UserInfoError,
|
|
42
|
+
decode_error,
|
|
43
|
+
)
|
|
44
|
+
from .._pkce import SystemRandomSource, generate_code_challenge, generate_code_verifier
|
|
45
|
+
from .._roles import CONSUMER_ROLES, DEFAULT_CONSUMER_ROLE, DEFAULT_ROLE_MAP, GLOBAL_ADMIN_IDP_ROLES, RoleMapper
|
|
46
|
+
from .._session import SessionTokenService
|
|
47
|
+
from .._state import StateCookieService
|
|
48
|
+
from .._sync._client import SDK_VERSION, MemyAuth
|
|
49
|
+
from .._sync._discovery import DiscoveryService
|
|
50
|
+
from .._sync._iam import IamService
|
|
51
|
+
from .._sync._idtoken import IdTokenVerifier
|
|
52
|
+
from .._sync._oidc import OidcService
|
|
53
|
+
from .._types import (
|
|
54
|
+
AuthorizationRequest,
|
|
55
|
+
Clock,
|
|
56
|
+
DiscoveryHealth,
|
|
57
|
+
IamOrgEntry,
|
|
58
|
+
IamSession,
|
|
59
|
+
IamTenant,
|
|
60
|
+
IamUserContext,
|
|
61
|
+
IdpTokenClaims,
|
|
62
|
+
OidcDiscoveryDocument,
|
|
63
|
+
OidcTokenResponse,
|
|
64
|
+
RandomSource,
|
|
65
|
+
SessionAccessClaims,
|
|
66
|
+
SessionAccessInput,
|
|
67
|
+
SessionRefreshClaims,
|
|
68
|
+
SessionRefreshInput,
|
|
69
|
+
StateCookieInput,
|
|
70
|
+
StateCookiePayload,
|
|
71
|
+
SystemClock,
|
|
72
|
+
UserContext,
|
|
73
|
+
UserContextOrgEntry,
|
|
74
|
+
UserContextTenant,
|
|
75
|
+
UserInfo,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
__version__ = SDK_VERSION
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"MemyAuth",
|
|
82
|
+
"SDK_VERSION",
|
|
83
|
+
"__version__",
|
|
84
|
+
"resolve_config",
|
|
85
|
+
"ResolvedConfig",
|
|
86
|
+
"RetryConfig",
|
|
87
|
+
"DiscoveryService",
|
|
88
|
+
"OidcService",
|
|
89
|
+
"IamService",
|
|
90
|
+
"IdTokenVerifier",
|
|
91
|
+
"RoleMapper",
|
|
92
|
+
"DEFAULT_ROLE_MAP",
|
|
93
|
+
"CONSUMER_ROLES",
|
|
94
|
+
"DEFAULT_CONSUMER_ROLE",
|
|
95
|
+
"GLOBAL_ADMIN_IDP_ROLES",
|
|
96
|
+
"SessionTokenService",
|
|
97
|
+
"StateCookieService",
|
|
98
|
+
"generate_code_verifier",
|
|
99
|
+
"generate_code_challenge",
|
|
100
|
+
"SystemRandomSource",
|
|
101
|
+
"MemyAuthError",
|
|
102
|
+
"ConfigError",
|
|
103
|
+
"UnsupportedAlgorithmPinError",
|
|
104
|
+
"NetworkError",
|
|
105
|
+
"TimeoutError",
|
|
106
|
+
"ProtocolError",
|
|
107
|
+
"BadRequestError",
|
|
108
|
+
"UnauthorizedError",
|
|
109
|
+
"ForbiddenError",
|
|
110
|
+
"NotFoundError",
|
|
111
|
+
"GoneError",
|
|
112
|
+
"ServiceUnavailableError",
|
|
113
|
+
"DiscoveryError",
|
|
114
|
+
"IssuerMismatchError",
|
|
115
|
+
"DiscoveryEscapeError",
|
|
116
|
+
"TokenExchangeError",
|
|
117
|
+
"RefreshRejectedError",
|
|
118
|
+
"UserInfoError",
|
|
119
|
+
"IdTokenError",
|
|
120
|
+
"UnsupportedAlgorithmError",
|
|
121
|
+
"SessionTokenError",
|
|
122
|
+
"StateCookieError",
|
|
123
|
+
"StateMismatchError",
|
|
124
|
+
"decode_error",
|
|
125
|
+
"Clock",
|
|
126
|
+
"RandomSource",
|
|
127
|
+
"SystemClock",
|
|
128
|
+
"OidcDiscoveryDocument",
|
|
129
|
+
"DiscoveryHealth",
|
|
130
|
+
"AuthorizationRequest",
|
|
131
|
+
"OidcTokenResponse",
|
|
132
|
+
"UserInfo",
|
|
133
|
+
"UserContext",
|
|
134
|
+
"UserContextTenant",
|
|
135
|
+
"UserContextOrgEntry",
|
|
136
|
+
"IamUserContext",
|
|
137
|
+
"IamSession",
|
|
138
|
+
"IamOrgEntry",
|
|
139
|
+
"IamTenant",
|
|
140
|
+
"SessionAccessInput",
|
|
141
|
+
"SessionAccessClaims",
|
|
142
|
+
"SessionRefreshInput",
|
|
143
|
+
"SessionRefreshClaims",
|
|
144
|
+
"StateCookieInput",
|
|
145
|
+
"StateCookiePayload",
|
|
146
|
+
"IdpTokenClaims",
|
|
147
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memydev-auth-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official MemyAuth (OIDC + IAM) client SDK for Python
|
|
5
|
+
License: Proprietary and unlicensed pending approved service terms.
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: httpx<1,>=0.27
|
|
8
|
+
Requires-Dist: pyjwt[crypto]<3,>=2.9
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: editables==0.5; extra == 'dev'
|
|
11
|
+
Requires-Dist: hatchling==1.27.0; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest-asyncio==0.24.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest==8.3.5; extra == 'dev'
|
|
14
|
+
Requires-Dist: respx==0.22.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: unasync==0.6.0; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# memydev-auth-sdk
|
|
19
|
+
|
|
20
|
+
Official Python client for **MemyAuth** — the estate's OIDC + IAM provider. Import name: `memyauth`.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import asyncio
|
|
24
|
+
from memyauth import MemyAuth
|
|
25
|
+
|
|
26
|
+
async def main() -> None:
|
|
27
|
+
async with MemyAuth(
|
|
28
|
+
issuer_url="https://auth.memy.dev/oidc",
|
|
29
|
+
client_id="my-app",
|
|
30
|
+
client_secret="...",
|
|
31
|
+
public_base_url="https://my-app.example.com",
|
|
32
|
+
id_token_validation="local-jwks", # or "tls-token-endpoint" — see below, no default
|
|
33
|
+
) as auth:
|
|
34
|
+
req = await auth.oidc.build_authorization_url()
|
|
35
|
+
# redirect the browser to req.url, persist req via auth.state.sign(...)
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
asyncio.run(main())
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
A synchronous facade is available for non-async callers:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from memyauth.sync import MemyAuth
|
|
45
|
+
|
|
46
|
+
with MemyAuth(issuer_url=..., client_id=..., client_secret=..., id_token_validation="local-jwks") as auth:
|
|
47
|
+
tokens = auth.oidc.exchange_code(code=..., code_verifier=...)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Choosing `id_token_validation`
|
|
51
|
+
|
|
52
|
+
MemyAuth signs ID tokens with **ES384 only**. Two ID-token trust strategies are both in production
|
|
53
|
+
use across the estate and neither is a silent default — you must choose:
|
|
54
|
+
|
|
55
|
+
- `local-jwks` (**recommended for new consumers**): verifies the ID token's signature locally
|
|
56
|
+
against the provider's JWKS, with an explicit algorithm allow-list (`["ES384"]` by default).
|
|
57
|
+
Stateless, no extra round trip, defends against ID-token substitution.
|
|
58
|
+
- `tls-token-endpoint`: performs **no** local signature verification; identity is derived from the
|
|
59
|
+
direct TLS channel to the token endpoint (OIDC Core §3.1.3.7 item 6) plus `/oidc/me` or
|
|
60
|
+
`/iam/me`. Choose this only when you never process the ID token directly.
|
|
61
|
+
|
|
62
|
+
An `allowed_algorithms` pin that cannot verify any token this provider issues (e.g. `["RS256"]`)
|
|
63
|
+
fails loudly at construction with `UnsupportedAlgorithmPinError` — this SDK exists in large part to
|
|
64
|
+
make that historically-real defect (shipped twice against this provider) impossible to repeat
|
|
65
|
+
silently.
|
|
66
|
+
|
|
67
|
+
## Async is the source of truth
|
|
68
|
+
|
|
69
|
+
`memyauth/_*.py` are hand-written and authoritative. `memyauth/_sync/*.py` and the `memyauth.sync`
|
|
70
|
+
facade are **mechanically generated** from them by `scripts/gen_sync.py` (via `unasync`) — never
|
|
71
|
+
hand-edit `_sync/`. Regenerate after any async change:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
python scripts/gen_sync.py # write the twins
|
|
75
|
+
python scripts/gen_sync.py --check # verify no drift; exits non-zero if the twins are stale
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
python3 -m venv .venv
|
|
82
|
+
.venv/bin/python -m pip install --upgrade pip
|
|
83
|
+
.venv/bin/python -m pip install hatchling==1.27.0 editables==0.5
|
|
84
|
+
.venv/bin/python -m pip install --no-build-isolation -e ".[dev]"
|
|
85
|
+
.venv/bin/python -m pytest -q
|
|
86
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
memyauth/__init__.py,sha256=2suvRP3XrsOxdCw34S6lg4Gr-8kH1ilVx9qxqPtoIDU,3836
|
|
2
|
+
memyauth/_client.py,sha256=Inye4y6vocOiKiedodv1jSTfBX85nLEDL-WMupwTa_M,7191
|
|
3
|
+
memyauth/_config.py,sha256=N0eEPEHN_ICgkKUMKroX-buXZpHgpht_XMackPX72kM,19233
|
|
4
|
+
memyauth/_discovery.py,sha256=HKDNtRgmTc_s9EOrWwJAxktqLu_Bh44SF8uClXJErSw,7617
|
|
5
|
+
memyauth/_errors.py,sha256=9_DlqVwKaPn0BuAQOzKm1iNYPAe2t_dzmxfh_yu1c_c,8554
|
|
6
|
+
memyauth/_iam.py,sha256=l_BmdCESPrY31qljMwMl_eIA90ImuQmmNj15B9SHWC0,13432
|
|
7
|
+
memyauth/_idtoken.py,sha256=WgjEcbr0eqXiDrNIRDFKdXo9QBP7H1DXn7AeLyjdWko,7672
|
|
8
|
+
memyauth/_oidc.py,sha256=MsdrspJSimNflmLzyIE1d5xFw_rutzL7jj_cDjevLVc,10810
|
|
9
|
+
memyauth/_pkce.py,sha256=5cR2jV-5_xeLbOOCNltttWS9YldyWg7OKcDP4CJIk28,2155
|
|
10
|
+
memyauth/_roles.py,sha256=F2_k6jlVRRical9rcKRdPpT-hze3jV01RfQ9GijjGfc,4539
|
|
11
|
+
memyauth/_session.py,sha256=A2UqBtFAWHLXeNtFAoYXjkKj5IbuQU0y26RLKInKe6w,6840
|
|
12
|
+
memyauth/_state.py,sha256=pM75vFDv_k9xSGIMjo79LUn3I1KmbZ0CO1ss2wINqnM,5594
|
|
13
|
+
memyauth/_transport.py,sha256=mgJlXbz1sLYW6dj5B-dIvzX_iz2uaB4_u4I1OvD7zig,9153
|
|
14
|
+
memyauth/_types.py,sha256=gdCLzlYOwKgIw69p4ar23d1oPmC9RLsrkGqD5uX7PW0,8431
|
|
15
|
+
memyauth/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
memyauth/_sync/__init__.py,sha256=C9HwbhD1iuIiCuRc8lXQr04PRZXNvYQQz7Vld5M4VMk,253
|
|
17
|
+
memyauth/_sync/_client.py,sha256=lbNWcGqLAtG_AKYjDy1sudn6gWM2zzjSxRHPC3TA35c,7300
|
|
18
|
+
memyauth/_sync/_discovery.py,sha256=fa5kAzwMInWqj8Dhb-XfNgYY7Sr8DiIQw1itIzH35Mc,7719
|
|
19
|
+
memyauth/_sync/_iam.py,sha256=HGH8ndzKqHoDESHlM2e_7K9OgBeLw4xTKCj1hMeV-b0,13528
|
|
20
|
+
memyauth/_sync/_idtoken.py,sha256=d7MKijTerr7vT-zhtBZGANFngMLeNuQjNkCJ9oHBX2Y,7778
|
|
21
|
+
memyauth/_sync/_oidc.py,sha256=6mt6emqrYm0elGu5Kd_8JRlpW3lpR1XSWeYwH2kQ8V4,10871
|
|
22
|
+
memyauth/_sync/_transport.py,sha256=QK_dcoR7LODYDNIErRjeG39f9RDCShu-WAVdMYxG4bY,9242
|
|
23
|
+
memyauth/sync/__init__.py,sha256=65nT-87ibWzsF-dQuwkF_E8DHbzd1lknxQQwLYeu8Z4,4298
|
|
24
|
+
memydev_auth_sdk-0.1.0.dist-info/METADATA,sha256=c_v3hmb3PPgtcbqrgIJyiRsJhsdGOwpq2Efi5Dy4DFk,3327
|
|
25
|
+
memydev_auth_sdk-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
+
memydev_auth_sdk-0.1.0.dist-info/RECORD,,
|