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/__init__.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@fileoverview Official MemyAuth Python SDK — async-first with a mechanically generated sync facade.
|
|
3
|
+
@module memyauth
|
|
4
|
+
@description Public entrypoint. Async: `memyauth.MemyAuth`. Sync: `memyauth.sync.MemyAuth`. The
|
|
5
|
+
async modules (`_client.py`, `_discovery.py`, `_oidc.py`, `_iam.py`, `_idtoken.py`,
|
|
6
|
+
`_transport.py`) are the ONLY hand-written client surface; `_sync/*.py` is mechanically derived
|
|
7
|
+
by `scripts/gen_sync.py` — never hand-edit it. `_roles.py`, `_session.py`, `_state.py`,
|
|
8
|
+
`_config.py`, `_errors.py`, `_types.py`, `_pkce.py` are pure/synchronous and shared verbatim by
|
|
9
|
+
both surfaces (no twin exists or is needed).
|
|
10
|
+
@created 2026-09-06
|
|
11
|
+
"""
|
|
12
|
+
from ._client import MemyAuth, SDK_VERSION
|
|
13
|
+
from ._config import ResolvedConfig, RetryConfig, resolve_config
|
|
14
|
+
from ._discovery import DiscoveryService
|
|
15
|
+
from ._errors import (
|
|
16
|
+
BadRequestError,
|
|
17
|
+
ConfigError,
|
|
18
|
+
DiscoveryError,
|
|
19
|
+
DiscoveryEscapeError,
|
|
20
|
+
ForbiddenError,
|
|
21
|
+
GoneError,
|
|
22
|
+
IdTokenError,
|
|
23
|
+
IssuerMismatchError,
|
|
24
|
+
MemyAuthError,
|
|
25
|
+
NetworkError,
|
|
26
|
+
NotFoundError,
|
|
27
|
+
ProtocolError,
|
|
28
|
+
RefreshRejectedError,
|
|
29
|
+
ServiceUnavailableError,
|
|
30
|
+
SessionTokenError,
|
|
31
|
+
StateCookieError,
|
|
32
|
+
StateMismatchError,
|
|
33
|
+
TimeoutError,
|
|
34
|
+
TokenExchangeError,
|
|
35
|
+
UnauthorizedError,
|
|
36
|
+
UnsupportedAlgorithmError,
|
|
37
|
+
UnsupportedAlgorithmPinError,
|
|
38
|
+
UserInfoError,
|
|
39
|
+
decode_error,
|
|
40
|
+
)
|
|
41
|
+
from ._iam import IamService
|
|
42
|
+
from ._idtoken import IdTokenVerifier
|
|
43
|
+
from ._oidc import OidcService
|
|
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 ._types import (
|
|
49
|
+
AuthorizationRequest,
|
|
50
|
+
Clock,
|
|
51
|
+
DiscoveryHealth,
|
|
52
|
+
IamOrgEntry,
|
|
53
|
+
IamSession,
|
|
54
|
+
IamTenant,
|
|
55
|
+
IamUserContext,
|
|
56
|
+
IdpTokenClaims,
|
|
57
|
+
OidcDiscoveryDocument,
|
|
58
|
+
OidcTokenResponse,
|
|
59
|
+
RandomSource,
|
|
60
|
+
SessionAccessClaims,
|
|
61
|
+
SessionAccessInput,
|
|
62
|
+
SessionRefreshClaims,
|
|
63
|
+
SessionRefreshInput,
|
|
64
|
+
StateCookieInput,
|
|
65
|
+
StateCookiePayload,
|
|
66
|
+
SystemClock,
|
|
67
|
+
UserContext,
|
|
68
|
+
UserContextOrgEntry,
|
|
69
|
+
UserContextTenant,
|
|
70
|
+
UserInfo,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
__version__ = SDK_VERSION
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"MemyAuth",
|
|
77
|
+
"SDK_VERSION",
|
|
78
|
+
"__version__",
|
|
79
|
+
"resolve_config",
|
|
80
|
+
"ResolvedConfig",
|
|
81
|
+
"RetryConfig",
|
|
82
|
+
"DiscoveryService",
|
|
83
|
+
"OidcService",
|
|
84
|
+
"IamService",
|
|
85
|
+
"IdTokenVerifier",
|
|
86
|
+
"RoleMapper",
|
|
87
|
+
"DEFAULT_ROLE_MAP",
|
|
88
|
+
"CONSUMER_ROLES",
|
|
89
|
+
"DEFAULT_CONSUMER_ROLE",
|
|
90
|
+
"GLOBAL_ADMIN_IDP_ROLES",
|
|
91
|
+
"SessionTokenService",
|
|
92
|
+
"StateCookieService",
|
|
93
|
+
"generate_code_verifier",
|
|
94
|
+
"generate_code_challenge",
|
|
95
|
+
"SystemRandomSource",
|
|
96
|
+
# errors
|
|
97
|
+
"MemyAuthError",
|
|
98
|
+
"ConfigError",
|
|
99
|
+
"UnsupportedAlgorithmPinError",
|
|
100
|
+
"NetworkError",
|
|
101
|
+
"TimeoutError",
|
|
102
|
+
"ProtocolError",
|
|
103
|
+
"BadRequestError",
|
|
104
|
+
"UnauthorizedError",
|
|
105
|
+
"ForbiddenError",
|
|
106
|
+
"NotFoundError",
|
|
107
|
+
"GoneError",
|
|
108
|
+
"ServiceUnavailableError",
|
|
109
|
+
"DiscoveryError",
|
|
110
|
+
"IssuerMismatchError",
|
|
111
|
+
"DiscoveryEscapeError",
|
|
112
|
+
"TokenExchangeError",
|
|
113
|
+
"RefreshRejectedError",
|
|
114
|
+
"UserInfoError",
|
|
115
|
+
"IdTokenError",
|
|
116
|
+
"UnsupportedAlgorithmError",
|
|
117
|
+
"SessionTokenError",
|
|
118
|
+
"StateCookieError",
|
|
119
|
+
"StateMismatchError",
|
|
120
|
+
"decode_error",
|
|
121
|
+
# types
|
|
122
|
+
"Clock",
|
|
123
|
+
"RandomSource",
|
|
124
|
+
"SystemClock",
|
|
125
|
+
"OidcDiscoveryDocument",
|
|
126
|
+
"DiscoveryHealth",
|
|
127
|
+
"AuthorizationRequest",
|
|
128
|
+
"OidcTokenResponse",
|
|
129
|
+
"UserInfo",
|
|
130
|
+
"UserContext",
|
|
131
|
+
"UserContextTenant",
|
|
132
|
+
"UserContextOrgEntry",
|
|
133
|
+
"IamUserContext",
|
|
134
|
+
"IamSession",
|
|
135
|
+
"IamOrgEntry",
|
|
136
|
+
"IamTenant",
|
|
137
|
+
"SessionAccessInput",
|
|
138
|
+
"SessionAccessClaims",
|
|
139
|
+
"SessionRefreshInput",
|
|
140
|
+
"SessionRefreshClaims",
|
|
141
|
+
"StateCookieInput",
|
|
142
|
+
"StateCookiePayload",
|
|
143
|
+
"IdpTokenClaims",
|
|
144
|
+
]
|
memyauth/_client.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@fileoverview Top-level `MemyAuth` async client — composes the seven capability services.
|
|
3
|
+
@module memyauth._client
|
|
4
|
+
@description [async -> _sync/_client.py twin via scripts/gen_sync.py]. `SDK_VERSION` lives HERE
|
|
5
|
+
(design §G.5's six-file version-parity check reads it via
|
|
6
|
+
`/SDK_VERSION\\s*=\\s*["\\']([^"\\']+)["\\']/`). `.discovery`, `.oidc`, `.iam` and `.roles` are
|
|
7
|
+
EAGER (built in `__init__`) because their prerequisites are always present once `resolve_config`
|
|
8
|
+
succeeds; `.session`, `.state` and `.id_token` are LAZY properties — `.session`/`.state` raise
|
|
9
|
+
`ConfigError` at first access when their signing secret was never configured (an IAM-only
|
|
10
|
+
consumer should never need one), while `.id_token` never raises (`id_token_validation` is a
|
|
11
|
+
required, already-validated field, so its prerequisite is always satisfiable) but is still built
|
|
12
|
+
lazily and cached, purely to avoid touching `discovery` before it is needed.
|
|
13
|
+
@dependencies ._config, ._discovery, ._oidc, ._iam, ._idtoken, ._roles, ._session, ._state
|
|
14
|
+
@relatedFiles __init__.py (re-exports MemyAuth + SDK_VERSION), sync/__init__.py (the generated
|
|
15
|
+
sync facade), _config.py (resolve_config), tests/test_client.py
|
|
16
|
+
@created 2026-09-06
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, Optional
|
|
21
|
+
|
|
22
|
+
# ABSOLUTE imports for the shared/pure modules; RELATIVE for the sibling async modules that DO
|
|
23
|
+
# get _sync/ twins, so the generated _sync/_client.py binds to ITS siblings in _sync/ (see
|
|
24
|
+
# _transport.py's header for why this split is required).
|
|
25
|
+
from memyauth._config import ResolvedConfig, resolve_config
|
|
26
|
+
from memyauth._errors import ConfigError
|
|
27
|
+
from memyauth._roles import RoleMapper
|
|
28
|
+
from memyauth._session import SessionTokenService
|
|
29
|
+
from memyauth._state import StateCookieService
|
|
30
|
+
|
|
31
|
+
from ._discovery import DiscoveryService
|
|
32
|
+
from ._iam import IamService
|
|
33
|
+
from ._idtoken import IdTokenVerifier
|
|
34
|
+
from ._oidc import OidcService
|
|
35
|
+
|
|
36
|
+
__all__ = ["MemyAuth", "SDK_VERSION"]
|
|
37
|
+
|
|
38
|
+
SDK_VERSION = "0.1.0"
|
|
39
|
+
|
|
40
|
+
_SECRET_FIELDS = (
|
|
41
|
+
"client_secret",
|
|
42
|
+
"iam_app_secret",
|
|
43
|
+
"iam_m2m_token",
|
|
44
|
+
"session_signing_secret",
|
|
45
|
+
"state_signing_secret",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class MemyAuth:
|
|
50
|
+
"""Async MemyAuth client — the composition root for all nine customer capabilities.
|
|
51
|
+
|
|
52
|
+
Usage::
|
|
53
|
+
|
|
54
|
+
async with MemyAuth(
|
|
55
|
+
issuer_url="https://auth.memy.dev/oidc",
|
|
56
|
+
client_id="my-app",
|
|
57
|
+
client_secret="...",
|
|
58
|
+
public_base_url="https://my-app.example.com",
|
|
59
|
+
id_token_validation="local-jwks",
|
|
60
|
+
) as auth:
|
|
61
|
+
req = await auth.oidc.build_authorization_url()
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
65
|
+
self._config: ResolvedConfig = resolve_config(**kwargs)
|
|
66
|
+
|
|
67
|
+
self.discovery = DiscoveryService(
|
|
68
|
+
issuer_url=self._config["issuer_url"],
|
|
69
|
+
internal_issuer_url=self._config["internal_issuer_url"],
|
|
70
|
+
timeout_ms=self._config["discovery_timeout_ms"],
|
|
71
|
+
transport=self._config["transport"],
|
|
72
|
+
clock=self._config["clock"],
|
|
73
|
+
)
|
|
74
|
+
self.oidc = OidcService(
|
|
75
|
+
config=self._config,
|
|
76
|
+
discovery=self.discovery,
|
|
77
|
+
transport=self._config["transport"],
|
|
78
|
+
random=self._config["random"],
|
|
79
|
+
)
|
|
80
|
+
self.iam = IamService(config=self._config, transport=self._config["transport"])
|
|
81
|
+
self.roles = RoleMapper(
|
|
82
|
+
role_map=self._config["role_map"],
|
|
83
|
+
role_overrides=self._config["role_overrides"],
|
|
84
|
+
default_role=self._config["default_role"],
|
|
85
|
+
global_admin_roles=self._config["global_admin_roles"],
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self._session: Optional[SessionTokenService] = None
|
|
89
|
+
self._state: Optional[StateCookieService] = None
|
|
90
|
+
self._id_token: Optional[IdTokenVerifier] = None
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def session(self) -> SessionTokenService:
|
|
94
|
+
if self._session is None:
|
|
95
|
+
secret = self._config.get("session_signing_secret")
|
|
96
|
+
if not secret:
|
|
97
|
+
raise ConfigError("sessionSigningSecret is required to issue or verify session tokens")
|
|
98
|
+
self._session = SessionTokenService(
|
|
99
|
+
signing_secret=secret,
|
|
100
|
+
access_ttl_seconds=self._config["access_ttl_seconds"],
|
|
101
|
+
refresh_ttl_seconds=self._config["refresh_ttl_seconds"],
|
|
102
|
+
issuer=self._config["session_issuer"],
|
|
103
|
+
clock=self._config["clock"],
|
|
104
|
+
)
|
|
105
|
+
return self._session
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def state(self) -> StateCookieService:
|
|
109
|
+
if self._state is None:
|
|
110
|
+
secret = self._config.get("state_signing_secret") or self._config.get("session_signing_secret")
|
|
111
|
+
if not secret:
|
|
112
|
+
raise ConfigError("stateSigningSecret (or sessionSigningSecret) is required to sign state cookies")
|
|
113
|
+
self._state = StateCookieService(
|
|
114
|
+
signing_secret=secret,
|
|
115
|
+
ttl_seconds=self._config["state_ttl_seconds"],
|
|
116
|
+
cookie_name=self._config["state_cookie_name"],
|
|
117
|
+
clock=self._config["clock"],
|
|
118
|
+
)
|
|
119
|
+
return self._state
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def id_token(self) -> IdTokenVerifier:
|
|
123
|
+
"""Always available — `id_token_validation` is a required, already-validated field, so
|
|
124
|
+
this never raises. Still lazy + cached, purely to defer construction cost."""
|
|
125
|
+
if self._id_token is None:
|
|
126
|
+
self._id_token = IdTokenVerifier(
|
|
127
|
+
mode=self._config["id_token_validation"],
|
|
128
|
+
allowed_algorithms=self._config["allowed_algorithms"],
|
|
129
|
+
discovery=self.discovery,
|
|
130
|
+
transport=self._config["transport"],
|
|
131
|
+
clock=self._config["clock"],
|
|
132
|
+
)
|
|
133
|
+
return self._id_token
|
|
134
|
+
|
|
135
|
+
def resolved_config(self) -> dict[str, Any]:
|
|
136
|
+
"""The resolved config, with every secret-bearing field replaced by `"<set>"` /
|
|
137
|
+
`"<unset>"` — safe to log or hand to a conformance driver."""
|
|
138
|
+
redacted: dict[str, Any] = dict(self._config)
|
|
139
|
+
redacted.pop("transport", None)
|
|
140
|
+
redacted.pop("clock", None)
|
|
141
|
+
redacted.pop("random", None)
|
|
142
|
+
for field in _SECRET_FIELDS:
|
|
143
|
+
redacted[field] = "<set>" if self._config.get(field) else "<unset>"
|
|
144
|
+
return redacted
|
|
145
|
+
|
|
146
|
+
def capabilities(self) -> dict[str, bool]:
|
|
147
|
+
"""Which lazily-gated capabilities are currently usable, without constructing them."""
|
|
148
|
+
return {
|
|
149
|
+
"discovery": True,
|
|
150
|
+
"oidc": True,
|
|
151
|
+
"iam": True,
|
|
152
|
+
"roles": True,
|
|
153
|
+
"session": bool(self._config.get("session_signing_secret")),
|
|
154
|
+
"state": bool(self._config.get("state_signing_secret") or self._config.get("session_signing_secret")),
|
|
155
|
+
"id_token": True,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async def close(self) -> None:
|
|
159
|
+
await self.discovery.close() # type: ignore[attr-defined]
|
|
160
|
+
await self.oidc.close() # type: ignore[attr-defined]
|
|
161
|
+
await self.iam.close() # type: ignore[attr-defined]
|
|
162
|
+
if self._id_token is not None:
|
|
163
|
+
await self._id_token.close() # type: ignore[attr-defined]
|
|
164
|
+
|
|
165
|
+
async def __aenter__(self) -> "MemyAuth":
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
169
|
+
await self.close()
|