obi-auth 1.2.1__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.
obi_auth/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """obi_auth."""
2
+
3
+ from obi_auth.client import get_token, get_token_info, get_user_info
4
+ from obi_auth.typedef import AuthMode, DeploymentEnvironment, TokenProvider
5
+
6
+ __all__ = [
7
+ "get_token",
8
+ "get_token_info",
9
+ "get_user_info",
10
+ "DeploymentEnvironment",
11
+ "AuthMode",
12
+ "TokenProvider",
13
+ ]
obi_auth/cache.py ADDED
@@ -0,0 +1,129 @@
1
+ """Token cache module."""
2
+
3
+ import time
4
+
5
+ import jwt
6
+ from cryptography.fernet import Fernet, InvalidToken
7
+
8
+ from obi_auth.config import settings
9
+ from obi_auth.storage import Storage
10
+ from obi_auth.typedef import (
11
+ AuthManagerTokenInfo,
12
+ CachedAuthManagerTokenInfo,
13
+ CachedTokenInfo,
14
+ KeycloakTokenInfo,
15
+ )
16
+ from obi_auth.util import derive_fernet_key
17
+
18
+
19
+ class BaseTokenCache:
20
+ """Shared Fernet helpers for token caches."""
21
+
22
+ def __init__(self):
23
+ """Initialize the token cache."""
24
+ self._cipher = Fernet(key=derive_fernet_key())
25
+
26
+ def _encrypt_access_token(self, access_token: str) -> tuple[bytes, int]:
27
+ creation_time, time_to_live = _get_token_times(access_token)
28
+ fernet_token = self._cipher.encrypt_at_time(
29
+ data=access_token.encode(encoding="utf-8"),
30
+ current_time=creation_time,
31
+ )
32
+ return fernet_token, time_to_live
33
+
34
+ def _decrypt_access_token(self, token: bytes, ttl: int) -> str:
35
+ return self._cipher.decrypt_at_time(
36
+ token=token,
37
+ ttl=ttl,
38
+ current_time=_now(),
39
+ ).decode()
40
+
41
+
42
+ class TokenCache(BaseTokenCache):
43
+ """Cache for Keycloak access tokens."""
44
+
45
+ def get(self, storage: Storage) -> KeycloakTokenInfo | None:
46
+ """Get a cached Keycloak token if still valid, else None."""
47
+ if not (cached_token_info := storage.read()):
48
+ return None
49
+ if not isinstance(cached_token_info, CachedTokenInfo):
50
+ storage.clear()
51
+ return None
52
+ try:
53
+ access_token = self._decrypt_access_token(
54
+ cached_token_info.token, cached_token_info.ttl
55
+ )
56
+ except InvalidToken:
57
+ storage.clear()
58
+ return None
59
+ return KeycloakTokenInfo(access_token=access_token)
60
+
61
+ def set(self, token_info: KeycloakTokenInfo, storage: Storage) -> None:
62
+ """Store a Keycloak token in the cache."""
63
+ fernet_token, time_to_live = self._encrypt_access_token(token_info.access_token)
64
+ storage.write(CachedTokenInfo(token=fernet_token, ttl=time_to_live))
65
+
66
+
67
+ class AuthManagerTokenCache(BaseTokenCache):
68
+ """Cache for auth-manager access tokens and persistent token ids."""
69
+
70
+ def get(self, storage: Storage) -> AuthManagerTokenInfo | None:
71
+ """Get a cached auth-manager token.
72
+
73
+ Returns a valid access token when available. If the access token has expired
74
+ but a persistent token id is stored, returns ``AuthManagerTokenInfo`` with an
75
+ empty ``access_token`` so the caller can remint.
76
+ """
77
+ if not (cached_token_info := storage.read()):
78
+ return None
79
+ if not isinstance(cached_token_info, CachedAuthManagerTokenInfo):
80
+ storage.clear()
81
+ return None
82
+
83
+ try:
84
+ persistent_token_id = self._cipher.decrypt(
85
+ cached_token_info.persistent_token_id
86
+ ).decode()
87
+ except InvalidToken:
88
+ storage.clear()
89
+ return None
90
+
91
+ try:
92
+ access_token = self._decrypt_access_token(
93
+ cached_token_info.token, cached_token_info.ttl
94
+ )
95
+ except InvalidToken:
96
+ return AuthManagerTokenInfo(access_token=None, persistent_token_id=persistent_token_id)
97
+
98
+ return AuthManagerTokenInfo(
99
+ access_token=access_token, persistent_token_id=persistent_token_id
100
+ )
101
+
102
+ def set(self, token_info: AuthManagerTokenInfo, storage: Storage) -> None:
103
+ """Store an auth-manager token and persistent id in the cache."""
104
+ if token_info.access_token is None:
105
+ msg = "Cannot cache AuthManagerTokenInfo without an access_token"
106
+ raise ValueError(msg)
107
+ fernet_token, time_to_live = self._encrypt_access_token(token_info.access_token)
108
+ persistent_token_id = self._cipher.encrypt(
109
+ token_info.persistent_token_id.encode(encoding="utf-8")
110
+ )
111
+ storage.write(
112
+ CachedAuthManagerTokenInfo(
113
+ token=fernet_token,
114
+ ttl=time_to_live,
115
+ persistent_token_id=persistent_token_id,
116
+ )
117
+ )
118
+
119
+
120
+ def _now() -> int:
121
+ """Return UTC timestamp now."""
122
+ return int(time.time())
123
+
124
+
125
+ def _get_token_times(token: str) -> tuple[int, int]:
126
+ """Get the creation time and time to live of a token."""
127
+ info = jwt.decode(token.encode(), options={"verify_signature": False})
128
+ effective_ttl = info["exp"] - info["iat"] - settings.EPSILON_TOKEN_TTL_SECONDS
129
+ return info["iat"], effective_ttl
obi_auth/cli.py ADDED
@@ -0,0 +1,151 @@
1
+ """CLI for obi-auth."""
2
+
3
+ import json
4
+ import logging
5
+ import sys
6
+
7
+ try:
8
+ import click
9
+ except ImportError as exc:
10
+ raise SystemExit(
11
+ "The obi-auth CLI requires optional dependencies that are not installed.\n"
12
+ "Install them with:\n"
13
+ " pip install 'obi-auth[cli]'"
14
+ ) from exc
15
+
16
+ import obi_auth
17
+ from obi_auth.typedef import AuthMode, DeploymentEnvironment, TokenProvider
18
+
19
+
20
+ @click.group()
21
+ @click.option(
22
+ "--log-level",
23
+ type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], case_sensitive=False),
24
+ default="WARNING",
25
+ show_default=True,
26
+ help="Logging level",
27
+ )
28
+ def main(log_level: str):
29
+ """CLI for obi-auth."""
30
+ logging.basicConfig(level=log_level)
31
+
32
+
33
+ @main.command()
34
+ @click.option(
35
+ "--environment",
36
+ "-e",
37
+ type=click.Choice(DeploymentEnvironment),
38
+ default=DeploymentEnvironment.staging,
39
+ show_default=True,
40
+ help="Target environment",
41
+ )
42
+ @click.option(
43
+ "--auth-mode",
44
+ "-m",
45
+ type=click.Choice(AuthMode),
46
+ default=AuthMode.pkce,
47
+ show_default=True,
48
+ help="Authentication method",
49
+ )
50
+ @click.option(
51
+ "--token-provider",
52
+ "-p",
53
+ type=click.Choice(TokenProvider),
54
+ default=TokenProvider.keycloak,
55
+ show_default=True,
56
+ help="Token issuer: keycloak or auth_manager",
57
+ )
58
+ @click.option(
59
+ "--persistent-token-id",
60
+ default=None,
61
+ help="Persistent token id (required when auth-mode is persistent_token)",
62
+ )
63
+ @click.option(
64
+ "--force-refresh",
65
+ help="Clear the cached token and authenticate again",
66
+ is_flag=True,
67
+ default=False,
68
+ )
69
+ def get_token(
70
+ environment: DeploymentEnvironment,
71
+ auth_mode: AuthMode,
72
+ token_provider: TokenProvider,
73
+ persistent_token_id: str | None,
74
+ force_refresh: bool,
75
+ ):
76
+ """Authenticate, print the token to stdout."""
77
+ access_token = obi_auth.get_token(
78
+ environment=environment,
79
+ auth_mode=auth_mode,
80
+ token_provider=token_provider,
81
+ persistent_token_id=persistent_token_id,
82
+ force_refresh=force_refresh,
83
+ )
84
+ print(access_token)
85
+
86
+
87
+ @main.command()
88
+ @click.argument("access_token", required=False)
89
+ def decode_token(access_token: str | None):
90
+ """Decode token from argument or stdin."""
91
+ if not access_token:
92
+ access_token = sys.stdin.read().strip()
93
+
94
+ if not access_token:
95
+ raise click.ClickException("No access token provided via argument or stdin")
96
+
97
+ print(json.dumps(obi_auth.get_token_info(access_token), indent=2))
98
+
99
+
100
+ @main.command()
101
+ @click.option(
102
+ "--environment",
103
+ "-e",
104
+ type=click.Choice(DeploymentEnvironment),
105
+ default=DeploymentEnvironment.staging,
106
+ show_default=True,
107
+ help="Target environment",
108
+ )
109
+ @click.option(
110
+ "--auth-mode",
111
+ "-m",
112
+ type=click.Choice(AuthMode),
113
+ default=AuthMode.pkce,
114
+ show_default=True,
115
+ help="Authentication method",
116
+ )
117
+ @click.option(
118
+ "--token-provider",
119
+ "-p",
120
+ type=click.Choice(TokenProvider),
121
+ default=TokenProvider.keycloak,
122
+ show_default=True,
123
+ help="Token issuer: keycloak or auth_manager",
124
+ )
125
+ @click.option(
126
+ "--persistent-token-id",
127
+ default=None,
128
+ help="Persistent token id (required when auth-mode is persistent_token)",
129
+ )
130
+ @click.option(
131
+ "--force-refresh",
132
+ help="Clear the cached token and authenticate again",
133
+ is_flag=True,
134
+ default=False,
135
+ )
136
+ def get_user_info(
137
+ environment: DeploymentEnvironment,
138
+ auth_mode: AuthMode,
139
+ token_provider: TokenProvider,
140
+ persistent_token_id: str | None,
141
+ force_refresh: bool,
142
+ ):
143
+ """Show user info information."""
144
+ access_token = obi_auth.get_token(
145
+ environment=environment,
146
+ auth_mode=auth_mode,
147
+ token_provider=token_provider,
148
+ persistent_token_id=persistent_token_id,
149
+ force_refresh=force_refresh,
150
+ )
151
+ print(json.dumps(obi_auth.get_user_info(access_token, environment=environment), indent=2))
obi_auth/client.py ADDED
@@ -0,0 +1,236 @@
1
+ """This module provides a client for the obi_auth service."""
2
+
3
+ import logging
4
+ from collections.abc import Callable
5
+
6
+ import jwt
7
+
8
+ from obi_auth.cache import AuthManagerTokenCache, TokenCache
9
+ from obi_auth.config import settings
10
+ from obi_auth.exception import AuthFlowError, ClientError, ConfigError, LocalServerError
11
+ from obi_auth.flows.auth_manager import (
12
+ auth_manager_exchange_token,
13
+ auth_manager_mint_access_token,
14
+ )
15
+ from obi_auth.flows.daf import daf_authenticate
16
+ from obi_auth.flows.pkce import pkce_authenticate
17
+ from obi_auth.request import user_info
18
+ from obi_auth.server import AuthServer
19
+ from obi_auth.storage import Storage
20
+ from obi_auth.typedef import (
21
+ AuthManagerTokenInfo,
22
+ AuthMode,
23
+ DeploymentEnvironment,
24
+ KeycloakTokenInfo,
25
+ TokenProvider,
26
+ )
27
+
28
+ L = logging.getLogger(__name__)
29
+
30
+
31
+ _TOKEN_CACHE = TokenCache()
32
+ _AUTH_MANAGER_TOKEN_CACHE = AuthManagerTokenCache()
33
+
34
+
35
+ def get_token(
36
+ *,
37
+ environment: DeploymentEnvironment = DeploymentEnvironment.staging,
38
+ auth_mode: AuthMode = AuthMode.pkce,
39
+ token_provider: TokenProvider = TokenProvider.keycloak,
40
+ force_refresh: bool = False,
41
+ persistent_token_id: str | None = None,
42
+ ) -> str:
43
+ """Get token.
44
+
45
+ Args:
46
+ environment: Target deployment environment.
47
+ auth_mode: How to authenticate with Keycloak (``pkce`` or ``daf``), or
48
+ ``persistent_token`` to mint from an existing auth-manager id
49
+ (backwards-compatible public API for callers such as obi-one).
50
+ token_provider: Who issues the returned access token when ``auth_mode`` is
51
+ ``pkce`` or ``daf``. ``keycloak`` returns the Keycloak token;
52
+ ``auth_manager`` exchanges it and mints an auth-manager access token.
53
+ Ignored when ``auth_mode`` is ``persistent_token``.
54
+ force_refresh: Clear the cached token and authenticate again.
55
+ persistent_token_id: Required when ``auth_mode`` is ``persistent_token``.
56
+ """
57
+ auth_mode = AuthMode(auth_mode)
58
+ token_provider = TokenProvider(token_provider)
59
+
60
+ L.debug("Using %s as the config dir", settings.config_dir)
61
+
62
+ # Public API compat: get_token(auth_mode="persistent_token", persistent_token_id=...)
63
+ if auth_mode == AuthMode.persistent_token:
64
+ if not persistent_token_id:
65
+ raise ClientError("persistent_token_id is required when auth_mode is persistent_token.")
66
+ return _get_persistent_token(
67
+ environment=environment,
68
+ persistent_token_id=persistent_token_id,
69
+ force_refresh=force_refresh,
70
+ )
71
+
72
+ storage = Storage(
73
+ config_dir=settings.config_dir,
74
+ environment=environment,
75
+ key=f"{auth_mode}_{token_provider}",
76
+ )
77
+
78
+ if token_provider == TokenProvider.auth_manager:
79
+ return _get_auth_manager_token(
80
+ storage=storage,
81
+ environment=environment,
82
+ auth_mode=auth_mode,
83
+ force_refresh=force_refresh,
84
+ )
85
+ return _get_keycloak_token(
86
+ storage=storage,
87
+ environment=environment,
88
+ auth_mode=auth_mode,
89
+ force_refresh=force_refresh,
90
+ )
91
+
92
+
93
+ def _get_persistent_token(
94
+ *,
95
+ environment: DeploymentEnvironment,
96
+ persistent_token_id: str,
97
+ force_refresh: bool,
98
+ ) -> str:
99
+ """Mint from a known persistent id using the auth-manager cache/mint helpers."""
100
+ storage = Storage(
101
+ config_dir=settings.config_dir,
102
+ environment=environment,
103
+ key=persistent_token_id,
104
+ )
105
+ if force_refresh:
106
+ L.debug("Forcing token refresh, clearing cached token")
107
+ storage.clear()
108
+ elif token_info := _AUTH_MANAGER_TOKEN_CACHE.get(storage):
109
+ if token_info.access_token:
110
+ L.debug("Using cached token")
111
+ return token_info.access_token
112
+ if refreshed := _refresh_auth_manager_token(
113
+ token_info.persistent_token_id, storage=storage, environment=environment
114
+ ):
115
+ if refreshed.access_token is None:
116
+ raise ClientError("Authentication process failed.")
117
+ return refreshed.access_token
118
+
119
+ try:
120
+ token_info = auth_manager_mint_access_token(persistent_token_id, environment=environment)
121
+ except AuthFlowError as e:
122
+ raise ClientError("Authentication process failed.") from e
123
+
124
+ _AUTH_MANAGER_TOKEN_CACHE.set(token_info, storage)
125
+ if token_info.access_token is None:
126
+ raise ClientError("Authentication process failed.")
127
+ return token_info.access_token
128
+
129
+
130
+ def _get_keycloak_token(
131
+ *,
132
+ storage: Storage,
133
+ environment: DeploymentEnvironment,
134
+ auth_mode: AuthMode,
135
+ force_refresh: bool,
136
+ ) -> str:
137
+ if force_refresh:
138
+ L.debug("Forcing token refresh, clearing cached token")
139
+ storage.clear()
140
+ elif token_info := _TOKEN_CACHE.get(storage):
141
+ L.debug("Using cached token")
142
+ return token_info.access_token
143
+
144
+ auth_method = _get_auth_method(auth_mode)
145
+ token_info = auth_method(environment=environment)
146
+ _TOKEN_CACHE.set(token_info, storage)
147
+ return token_info.access_token
148
+
149
+
150
+ def _get_auth_manager_token(
151
+ *,
152
+ storage: Storage,
153
+ environment: DeploymentEnvironment,
154
+ auth_mode: AuthMode,
155
+ force_refresh: bool,
156
+ ) -> str:
157
+ if force_refresh:
158
+ L.debug("Forcing token refresh, clearing cached token")
159
+ storage.clear()
160
+ elif token_info := _AUTH_MANAGER_TOKEN_CACHE.get(storage):
161
+ if token_info.access_token:
162
+ L.debug("Using cached token")
163
+ return token_info.access_token
164
+ if refreshed := _refresh_auth_manager_token(
165
+ token_info.persistent_token_id, storage=storage, environment=environment
166
+ ):
167
+ if refreshed.access_token is None:
168
+ raise ClientError("Authentication process failed.")
169
+ return refreshed.access_token
170
+
171
+ auth_method = _get_auth_method(auth_mode)
172
+ keycloak_token = auth_method(environment=environment)
173
+ try:
174
+ token_info = auth_manager_exchange_token(keycloak_token, environment=environment)
175
+ except AuthFlowError as e:
176
+ raise ClientError("Authentication process failed.") from e
177
+
178
+ _AUTH_MANAGER_TOKEN_CACHE.set(token_info, storage)
179
+ if token_info.access_token is None:
180
+ raise ClientError("Authentication process failed.")
181
+ return token_info.access_token
182
+
183
+
184
+ def _refresh_auth_manager_token(
185
+ persistent_token_id: str, *, storage: Storage, environment: DeploymentEnvironment
186
+ ) -> AuthManagerTokenInfo | None:
187
+ """Mint a new access token from a cached persistent token id."""
188
+ L.debug("Cached access token expired, minting a new one from persistent token id")
189
+ try:
190
+ token_info = auth_manager_mint_access_token(persistent_token_id, environment=environment)
191
+ except AuthFlowError:
192
+ L.debug("Failed to mint access token from persistent token id, clearing cache")
193
+ storage.clear()
194
+ return None
195
+
196
+ _AUTH_MANAGER_TOKEN_CACHE.set(token_info, storage)
197
+ return token_info
198
+
199
+
200
+ def _get_auth_method(auth_mode: AuthMode) -> Callable[..., KeycloakTokenInfo]:
201
+ methods: dict[AuthMode, Callable[..., KeycloakTokenInfo]] = {
202
+ AuthMode.pkce: _pkce_authenticate,
203
+ AuthMode.daf: _daf_authenticate,
204
+ }
205
+ return methods[auth_mode]
206
+
207
+
208
+ def _pkce_authenticate(*, environment: DeploymentEnvironment) -> KeycloakTokenInfo:
209
+ try:
210
+ with AuthServer().run() as local_server:
211
+ return pkce_authenticate(server=local_server, environment=environment)
212
+ except AuthFlowError as e:
213
+ raise ClientError("Authentication process failed.") from e
214
+ except LocalServerError as e:
215
+ raise ClientError("Local server failed to authenticate.") from e
216
+ except ConfigError as e:
217
+ raise ClientError("There is a mistake with configuration settings.") from e
218
+
219
+
220
+ def _daf_authenticate(*, environment: DeploymentEnvironment) -> KeycloakTokenInfo:
221
+ try:
222
+ return daf_authenticate(environment=environment)
223
+ except AuthFlowError as e:
224
+ raise ClientError("Authentication process failed.") from e
225
+
226
+
227
+ def get_token_info(token: str) -> dict:
228
+ """Decode token information."""
229
+ return jwt.decode(token, options={"verify_signature": False})
230
+
231
+
232
+ def get_user_info(
233
+ token: str, environment: DeploymentEnvironment = DeploymentEnvironment.staging
234
+ ) -> dict:
235
+ """Get user info from valid token."""
236
+ return user_info(token, environment=environment).json()
obi_auth/config.py ADDED
@@ -0,0 +1,101 @@
1
+ """This module provides a config for the obi_auth service."""
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated
5
+
6
+ from pydantic import Field
7
+ from pydantic_settings import BaseSettings, SettingsConfigDict
8
+
9
+ from obi_auth.exception import ConfigError
10
+ from obi_auth.typedef import DeploymentEnvironment, KeycloakRealm
11
+ from obi_auth.util import get_config_dir
12
+
13
+
14
+ class Settings(BaseSettings):
15
+ """Environment settings for this library."""
16
+
17
+ model_config = SettingsConfigDict(
18
+ env_file=".env",
19
+ env_prefix="OBI_AUTH_",
20
+ env_file_encoding="utf-8",
21
+ case_sensitive=False,
22
+ validate_default=False,
23
+ extra="ignore",
24
+ )
25
+
26
+ config_dir: Annotated[
27
+ Path,
28
+ Field(
29
+ description="Directory to store the token.",
30
+ default_factory=get_config_dir,
31
+ ),
32
+ ]
33
+
34
+ KEYCLOAK_ENV: DeploymentEnvironment = DeploymentEnvironment.staging
35
+ KEYCLOAK_REALM: KeycloakRealm = KeycloakRealm.sbo
36
+ KEYCLOAK_CLIENT_ID: str = "obi-entitysdk-auth"
37
+
38
+ EPSILON_TOKEN_TTL_SECONDS: int = 60
39
+
40
+ LOCAL_SERVER_TIMEOUT: int = 60
41
+
42
+ def _get_domain_url(self, override_env: DeploymentEnvironment) -> str:
43
+ """Return domain url based on environment."""
44
+ match env := override_env or self.KEYCLOAK_ENV:
45
+ case DeploymentEnvironment.staging:
46
+ return "https://staging.cell-a.openbraininstitute.org"
47
+ case DeploymentEnvironment.production:
48
+ return "https://cell-a.openbraininstitute.org"
49
+ case _:
50
+ raise ConfigError(f"Unknown deployment environment {env}")
51
+
52
+ def get_keycloak_url(self, override_env: DeploymentEnvironment | None = None):
53
+ """Return keycloak url."""
54
+ url = self._get_domain_url(override_env or self.KEYCLOAK_ENV)
55
+ return f"{url}/auth/realms/{self.KEYCLOAK_REALM}"
56
+
57
+ def get_keycloak_token_endpoint(self, override_env: DeploymentEnvironment | None = None) -> str:
58
+ """Return keycloak token endpoint."""
59
+ base_url = self.get_keycloak_url(override_env=override_env)
60
+ return f"{base_url}/protocol/openid-connect/token"
61
+
62
+ def get_keycloak_auth_endpoint(self, override_env: DeploymentEnvironment | None = None) -> str:
63
+ """Return keycloak auth endpoint."""
64
+ base_url = self.get_keycloak_url(override_env=override_env)
65
+ return f"{base_url}/protocol/openid-connect/auth"
66
+
67
+ def get_keycloak_device_auth_endpoint(
68
+ self, override_env: DeploymentEnvironment | None = None
69
+ ) -> str:
70
+ """Return keycloack device auth endpoint."""
71
+ base_url = self.get_keycloak_url(override_env=override_env)
72
+ return f"{base_url}/protocol/openid-connect/auth/device"
73
+
74
+ def get_keycloak_user_info_endpoint(
75
+ self, override_env: DeploymentEnvironment | None = None
76
+ ) -> str:
77
+ """Return keycloak user info endpoint."""
78
+ base_url = self.get_keycloak_url(override_env=override_env)
79
+ return f"{base_url}/protocol/openid-connect/userinfo"
80
+
81
+ def get_auth_manager_url(self, override_env: DeploymentEnvironment | None = None) -> str:
82
+ """Return auth manager url."""
83
+ url = self._get_domain_url(override_env or self.KEYCLOAK_ENV)
84
+ return f"{url}/api/auth-manager/v1"
85
+
86
+ def get_auth_manager_access_token_endpoint(
87
+ self, override_env: DeploymentEnvironment | None = None
88
+ ) -> str:
89
+ """Return auth-manager token endpoint."""
90
+ base_url = self.get_auth_manager_url(override_env=override_env)
91
+ return f"{base_url}/access-token"
92
+
93
+ def get_auth_manager_token_exchange_endpoint(
94
+ self, override_env: DeploymentEnvironment | None = None
95
+ ) -> str:
96
+ """Return auth-manager token exchange endpoint."""
97
+ base_url = self.get_auth_manager_url(override_env=override_env)
98
+ return f"{base_url}/token-exchange"
99
+
100
+
101
+ settings = Settings()
obi_auth/exception.py ADDED
@@ -0,0 +1,21 @@
1
+ """Exceptions module."""
2
+
3
+
4
+ class ObiAuthError(Exception):
5
+ """Generic obi-auth error."""
6
+
7
+
8
+ class AuthFlowError(ObiAuthError):
9
+ """Authenticatin flow error."""
10
+
11
+
12
+ class ClientError(ObiAuthError):
13
+ """Client related error."""
14
+
15
+
16
+ class LocalServerError(ObiAuthError):
17
+ """Local server related error."""
18
+
19
+
20
+ class ConfigError(ObiAuthError):
21
+ """Configuration settings error."""
@@ -0,0 +1 @@
1
+ """Authentication flows."""