kstlib 0.0.1a0__py3-none-any.whl → 1.0.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.
- kstlib/__init__.py +266 -1
- kstlib/__main__.py +16 -0
- kstlib/alerts/__init__.py +110 -0
- kstlib/alerts/channels/__init__.py +36 -0
- kstlib/alerts/channels/base.py +197 -0
- kstlib/alerts/channels/email.py +227 -0
- kstlib/alerts/channels/slack.py +389 -0
- kstlib/alerts/exceptions.py +72 -0
- kstlib/alerts/manager.py +651 -0
- kstlib/alerts/models.py +142 -0
- kstlib/alerts/throttle.py +263 -0
- kstlib/auth/__init__.py +139 -0
- kstlib/auth/callback.py +399 -0
- kstlib/auth/config.py +502 -0
- kstlib/auth/errors.py +127 -0
- kstlib/auth/models.py +316 -0
- kstlib/auth/providers/__init__.py +14 -0
- kstlib/auth/providers/base.py +393 -0
- kstlib/auth/providers/oauth2.py +645 -0
- kstlib/auth/providers/oidc.py +821 -0
- kstlib/auth/session.py +338 -0
- kstlib/auth/token.py +482 -0
- kstlib/cache/__init__.py +50 -0
- kstlib/cache/decorator.py +261 -0
- kstlib/cache/strategies.py +516 -0
- kstlib/cli/__init__.py +8 -0
- kstlib/cli/app.py +195 -0
- kstlib/cli/commands/__init__.py +5 -0
- kstlib/cli/commands/auth/__init__.py +39 -0
- kstlib/cli/commands/auth/common.py +122 -0
- kstlib/cli/commands/auth/login.py +325 -0
- kstlib/cli/commands/auth/logout.py +74 -0
- kstlib/cli/commands/auth/providers.py +57 -0
- kstlib/cli/commands/auth/status.py +291 -0
- kstlib/cli/commands/auth/token.py +199 -0
- kstlib/cli/commands/auth/whoami.py +106 -0
- kstlib/cli/commands/config.py +89 -0
- kstlib/cli/commands/ops/__init__.py +39 -0
- kstlib/cli/commands/ops/attach.py +49 -0
- kstlib/cli/commands/ops/common.py +269 -0
- kstlib/cli/commands/ops/list_sessions.py +252 -0
- kstlib/cli/commands/ops/logs.py +49 -0
- kstlib/cli/commands/ops/start.py +98 -0
- kstlib/cli/commands/ops/status.py +138 -0
- kstlib/cli/commands/ops/stop.py +60 -0
- kstlib/cli/commands/rapi/__init__.py +60 -0
- kstlib/cli/commands/rapi/call.py +341 -0
- kstlib/cli/commands/rapi/list.py +99 -0
- kstlib/cli/commands/rapi/show.py +206 -0
- kstlib/cli/commands/secrets/__init__.py +35 -0
- kstlib/cli/commands/secrets/common.py +425 -0
- kstlib/cli/commands/secrets/decrypt.py +88 -0
- kstlib/cli/commands/secrets/doctor.py +743 -0
- kstlib/cli/commands/secrets/encrypt.py +242 -0
- kstlib/cli/commands/secrets/shred.py +96 -0
- kstlib/cli/common.py +86 -0
- kstlib/config/__init__.py +76 -0
- kstlib/config/exceptions.py +110 -0
- kstlib/config/export.py +225 -0
- kstlib/config/loader.py +963 -0
- kstlib/config/sops.py +287 -0
- kstlib/db/__init__.py +54 -0
- kstlib/db/aiosqlcipher.py +137 -0
- kstlib/db/cipher.py +112 -0
- kstlib/db/database.py +367 -0
- kstlib/db/exceptions.py +25 -0
- kstlib/db/pool.py +302 -0
- kstlib/helpers/__init__.py +35 -0
- kstlib/helpers/exceptions.py +11 -0
- kstlib/helpers/time_trigger.py +396 -0
- kstlib/kstlib.conf.yml +890 -0
- kstlib/limits.py +963 -0
- kstlib/logging/__init__.py +108 -0
- kstlib/logging/manager.py +633 -0
- kstlib/mail/__init__.py +42 -0
- kstlib/mail/builder.py +626 -0
- kstlib/mail/exceptions.py +27 -0
- kstlib/mail/filesystem.py +248 -0
- kstlib/mail/transport.py +224 -0
- kstlib/mail/transports/__init__.py +19 -0
- kstlib/mail/transports/gmail.py +268 -0
- kstlib/mail/transports/resend.py +324 -0
- kstlib/mail/transports/smtp.py +326 -0
- kstlib/meta.py +72 -0
- kstlib/metrics/__init__.py +88 -0
- kstlib/metrics/decorators.py +1090 -0
- kstlib/metrics/exceptions.py +14 -0
- kstlib/monitoring/__init__.py +116 -0
- kstlib/monitoring/_styles.py +163 -0
- kstlib/monitoring/cell.py +57 -0
- kstlib/monitoring/config.py +424 -0
- kstlib/monitoring/delivery.py +579 -0
- kstlib/monitoring/exceptions.py +63 -0
- kstlib/monitoring/image.py +220 -0
- kstlib/monitoring/kv.py +79 -0
- kstlib/monitoring/list.py +69 -0
- kstlib/monitoring/metric.py +88 -0
- kstlib/monitoring/monitoring.py +341 -0
- kstlib/monitoring/renderer.py +139 -0
- kstlib/monitoring/service.py +392 -0
- kstlib/monitoring/table.py +129 -0
- kstlib/monitoring/types.py +56 -0
- kstlib/ops/__init__.py +86 -0
- kstlib/ops/base.py +148 -0
- kstlib/ops/container.py +577 -0
- kstlib/ops/exceptions.py +209 -0
- kstlib/ops/manager.py +407 -0
- kstlib/ops/models.py +176 -0
- kstlib/ops/tmux.py +372 -0
- kstlib/ops/validators.py +287 -0
- kstlib/py.typed +0 -0
- kstlib/rapi/__init__.py +118 -0
- kstlib/rapi/client.py +875 -0
- kstlib/rapi/config.py +861 -0
- kstlib/rapi/credentials.py +887 -0
- kstlib/rapi/exceptions.py +213 -0
- kstlib/resilience/__init__.py +101 -0
- kstlib/resilience/circuit_breaker.py +440 -0
- kstlib/resilience/exceptions.py +95 -0
- kstlib/resilience/heartbeat.py +491 -0
- kstlib/resilience/rate_limiter.py +506 -0
- kstlib/resilience/shutdown.py +417 -0
- kstlib/resilience/watchdog.py +637 -0
- kstlib/secrets/__init__.py +29 -0
- kstlib/secrets/exceptions.py +19 -0
- kstlib/secrets/models.py +62 -0
- kstlib/secrets/providers/__init__.py +79 -0
- kstlib/secrets/providers/base.py +58 -0
- kstlib/secrets/providers/environment.py +66 -0
- kstlib/secrets/providers/keyring.py +107 -0
- kstlib/secrets/providers/kms.py +223 -0
- kstlib/secrets/providers/kwargs.py +101 -0
- kstlib/secrets/providers/sops.py +209 -0
- kstlib/secrets/resolver.py +221 -0
- kstlib/secrets/sensitive.py +130 -0
- kstlib/secure/__init__.py +23 -0
- kstlib/secure/fs.py +194 -0
- kstlib/secure/permissions.py +70 -0
- kstlib/ssl.py +347 -0
- kstlib/ui/__init__.py +23 -0
- kstlib/ui/exceptions.py +26 -0
- kstlib/ui/panels.py +484 -0
- kstlib/ui/spinner.py +864 -0
- kstlib/ui/tables.py +382 -0
- kstlib/utils/__init__.py +48 -0
- kstlib/utils/dict.py +36 -0
- kstlib/utils/formatting.py +338 -0
- kstlib/utils/http_trace.py +237 -0
- kstlib/utils/lazy.py +49 -0
- kstlib/utils/secure_delete.py +205 -0
- kstlib/utils/serialization.py +247 -0
- kstlib/utils/text.py +56 -0
- kstlib/utils/validators.py +124 -0
- kstlib/websocket/__init__.py +97 -0
- kstlib/websocket/exceptions.py +214 -0
- kstlib/websocket/manager.py +1102 -0
- kstlib/websocket/models.py +361 -0
- kstlib-1.0.1.dist-info/METADATA +201 -0
- kstlib-1.0.1.dist-info/RECORD +163 -0
- {kstlib-0.0.1a0.dist-info → kstlib-1.0.1.dist-info}/WHEEL +1 -1
- kstlib-1.0.1.dist-info/entry_points.txt +2 -0
- kstlib-1.0.1.dist-info/licenses/LICENSE.md +9 -0
- kstlib-0.0.1a0.dist-info/METADATA +0 -29
- kstlib-0.0.1a0.dist-info/RECORD +0 -6
- kstlib-0.0.1a0.dist-info/licenses/LICENSE.md +0 -5
- {kstlib-0.0.1a0.dist-info → kstlib-1.0.1.dist-info}/top_level.txt +0 -0
kstlib/auth/models.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""Data models for the authentication module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthFlow(str, Enum):
|
|
12
|
+
"""OAuth2/OIDC authentication flows supported by the module.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
AUTHORIZATION_CODE: Standard OAuth2 Authorization Code flow.
|
|
16
|
+
AUTHORIZATION_CODE_PKCE: Authorization Code with PKCE extension (recommended).
|
|
17
|
+
CLIENT_CREDENTIALS: Machine-to-machine authentication (no user interaction).
|
|
18
|
+
DEVICE_CODE: For devices with limited input capabilities.
|
|
19
|
+
REFRESH_TOKEN: Token refresh flow (internal use).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
AUTHORIZATION_CODE = "authorization_code"
|
|
23
|
+
AUTHORIZATION_CODE_PKCE = "authorization_code_pkce"
|
|
24
|
+
CLIENT_CREDENTIALS = "client_credentials"
|
|
25
|
+
DEVICE_CODE = "device_code"
|
|
26
|
+
REFRESH_TOKEN = "refresh_token"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TokenType(str, Enum):
|
|
30
|
+
"""Token type as returned by the authorization server."""
|
|
31
|
+
|
|
32
|
+
BEARER = "Bearer"
|
|
33
|
+
MAC = "MAC"
|
|
34
|
+
DPOP = "DPoP"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PreflightStatus(str, Enum):
|
|
38
|
+
"""Status of a preflight validation step."""
|
|
39
|
+
|
|
40
|
+
SUCCESS = "success"
|
|
41
|
+
FAILURE = "failure"
|
|
42
|
+
WARNING = "warning"
|
|
43
|
+
SKIPPED = "skipped"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(slots=True)
|
|
47
|
+
class Token: # pylint: disable=too-many-instance-attributes
|
|
48
|
+
"""Represents an OAuth2/OIDC token set.
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
access_token: The access token issued by the authorization server.
|
|
52
|
+
token_type: Token type (usually "Bearer").
|
|
53
|
+
expires_at: Absolute expiration time (UTC). None if unknown.
|
|
54
|
+
refresh_token: Optional refresh token for obtaining new access tokens.
|
|
55
|
+
scope: List of granted scopes.
|
|
56
|
+
id_token: OIDC ID token (JWT) containing user claims. None for pure OAuth2.
|
|
57
|
+
issued_at: When the token was issued (UTC).
|
|
58
|
+
metadata: Additional provider-specific data.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
>>> from datetime import datetime, timezone
|
|
62
|
+
>>> token = Token(
|
|
63
|
+
... access_token="eyJhbGc...",
|
|
64
|
+
... expires_at=datetime(2025, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
|
65
|
+
... refresh_token="dGhpcyBpcyBh...",
|
|
66
|
+
... scope=["openid", "profile"],
|
|
67
|
+
... )
|
|
68
|
+
>>> token.is_expired
|
|
69
|
+
True
|
|
70
|
+
>>> token.is_refreshable
|
|
71
|
+
True
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
access_token: str
|
|
75
|
+
token_type: TokenType | str = TokenType.BEARER
|
|
76
|
+
expires_at: datetime | None = None
|
|
77
|
+
refresh_token: str | None = None
|
|
78
|
+
scope: list[str] = field(default_factory=list)
|
|
79
|
+
id_token: str | None = None
|
|
80
|
+
issued_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
81
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def is_expired(self) -> bool:
|
|
85
|
+
"""Check if the access token has expired.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
True if expired or expiration is unknown and token is old (>1h).
|
|
89
|
+
"""
|
|
90
|
+
if self.expires_at is None:
|
|
91
|
+
# Conservative: assume expired after 1 hour if no expiry info
|
|
92
|
+
return datetime.now(timezone.utc) > self.issued_at + timedelta(hours=1)
|
|
93
|
+
return datetime.now(timezone.utc) >= self.expires_at
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def is_refreshable(self) -> bool:
|
|
97
|
+
"""Check if the token can be refreshed.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if a refresh_token is available.
|
|
101
|
+
"""
|
|
102
|
+
return self.refresh_token is not None
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def expires_in(self) -> int | None:
|
|
106
|
+
"""Seconds until expiration. None if unknown, negative if expired."""
|
|
107
|
+
if self.expires_at is None:
|
|
108
|
+
return None
|
|
109
|
+
delta = self.expires_at - datetime.now(timezone.utc)
|
|
110
|
+
return int(delta.total_seconds())
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def should_refresh(self) -> bool:
|
|
114
|
+
"""Check if the token should be proactively refreshed.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if token expires within 60 seconds or is already expired.
|
|
118
|
+
"""
|
|
119
|
+
if self.expires_at is None:
|
|
120
|
+
return self.is_expired
|
|
121
|
+
# Refresh 60 seconds before actual expiry
|
|
122
|
+
buffer = timedelta(seconds=60)
|
|
123
|
+
return datetime.now(timezone.utc) >= (self.expires_at - buffer)
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def from_response(cls, data: dict[str, Any]) -> Token:
|
|
127
|
+
"""Create a Token from an OAuth2 token response.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
data: Raw token response from the authorization server.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Token instance populated from the response.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> response = {
|
|
137
|
+
... "access_token": "eyJhbGc...",
|
|
138
|
+
... "token_type": "Bearer",
|
|
139
|
+
... "expires_in": 3600,
|
|
140
|
+
... "refresh_token": "dGhpcyBpcyBh...",
|
|
141
|
+
... "scope": "openid profile",
|
|
142
|
+
... "id_token": "eyJhbGc...",
|
|
143
|
+
... }
|
|
144
|
+
>>> token = Token.from_response(response)
|
|
145
|
+
>>> token.scope
|
|
146
|
+
['openid', 'profile']
|
|
147
|
+
"""
|
|
148
|
+
now = datetime.now(timezone.utc)
|
|
149
|
+
|
|
150
|
+
# Parse expires_at from expires_in
|
|
151
|
+
expires_at = None
|
|
152
|
+
if "expires_in" in data:
|
|
153
|
+
expires_at = now + timedelta(seconds=int(data["expires_in"]))
|
|
154
|
+
elif "expires_at" in data:
|
|
155
|
+
# Some servers return absolute timestamp
|
|
156
|
+
expires_at = datetime.fromtimestamp(data["expires_at"], tz=timezone.utc)
|
|
157
|
+
|
|
158
|
+
# Parse scope (can be string or list)
|
|
159
|
+
scope_raw = data.get("scope", [])
|
|
160
|
+
scope = (scope_raw.split() if scope_raw else []) if isinstance(scope_raw, str) else list(scope_raw)
|
|
161
|
+
|
|
162
|
+
# Extract known fields, rest goes to metadata
|
|
163
|
+
known_fields = {
|
|
164
|
+
"access_token",
|
|
165
|
+
"token_type",
|
|
166
|
+
"expires_in",
|
|
167
|
+
"expires_at",
|
|
168
|
+
"refresh_token",
|
|
169
|
+
"scope",
|
|
170
|
+
"id_token",
|
|
171
|
+
}
|
|
172
|
+
metadata = {k: v for k, v in data.items() if k not in known_fields}
|
|
173
|
+
|
|
174
|
+
return cls(
|
|
175
|
+
access_token=data["access_token"],
|
|
176
|
+
token_type=data.get("token_type", TokenType.BEARER),
|
|
177
|
+
expires_at=expires_at,
|
|
178
|
+
refresh_token=data.get("refresh_token"),
|
|
179
|
+
scope=scope,
|
|
180
|
+
id_token=data.get("id_token"),
|
|
181
|
+
issued_at=now,
|
|
182
|
+
metadata=metadata,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def to_dict(self) -> dict[str, Any]:
|
|
186
|
+
"""Serialize token to dictionary for storage.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Dictionary representation suitable for JSON serialization.
|
|
190
|
+
"""
|
|
191
|
+
return {
|
|
192
|
+
"access_token": self.access_token,
|
|
193
|
+
"token_type": str(self.token_type.value if isinstance(self.token_type, TokenType) else self.token_type),
|
|
194
|
+
"expires_at": self.expires_at.isoformat() if self.expires_at else None,
|
|
195
|
+
"refresh_token": self.refresh_token,
|
|
196
|
+
"scope": self.scope,
|
|
197
|
+
"id_token": self.id_token,
|
|
198
|
+
"issued_at": self.issued_at.isoformat(),
|
|
199
|
+
"metadata": self.metadata,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def from_dict(cls, data: dict[str, Any]) -> Token:
|
|
204
|
+
"""Deserialize token from dictionary (storage retrieval).
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
data: Dictionary from to_dict() or storage.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Token instance.
|
|
211
|
+
"""
|
|
212
|
+
expires_at = None
|
|
213
|
+
if data.get("expires_at"):
|
|
214
|
+
expires_at = datetime.fromisoformat(data["expires_at"])
|
|
215
|
+
|
|
216
|
+
issued_at = datetime.now(timezone.utc)
|
|
217
|
+
if data.get("issued_at"):
|
|
218
|
+
issued_at = datetime.fromisoformat(data["issued_at"])
|
|
219
|
+
|
|
220
|
+
return cls(
|
|
221
|
+
access_token=data["access_token"],
|
|
222
|
+
token_type=data.get("token_type", TokenType.BEARER),
|
|
223
|
+
expires_at=expires_at,
|
|
224
|
+
refresh_token=data.get("refresh_token"),
|
|
225
|
+
scope=data.get("scope", []),
|
|
226
|
+
id_token=data.get("id_token"),
|
|
227
|
+
issued_at=issued_at,
|
|
228
|
+
metadata=data.get("metadata", {}),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@dataclass(slots=True)
|
|
233
|
+
class PreflightResult:
|
|
234
|
+
"""Result of a single preflight validation step.
|
|
235
|
+
|
|
236
|
+
Attributes:
|
|
237
|
+
step: Name/identifier of the validation step.
|
|
238
|
+
status: Outcome of the step (success, failure, warning, skipped).
|
|
239
|
+
message: Human-readable description of the result.
|
|
240
|
+
details: Optional additional information (URLs checked, errors, etc.).
|
|
241
|
+
duration_ms: Time taken for this step in milliseconds.
|
|
242
|
+
|
|
243
|
+
Example:
|
|
244
|
+
>>> result = PreflightResult(
|
|
245
|
+
... step="discovery",
|
|
246
|
+
... status=PreflightStatus.SUCCESS,
|
|
247
|
+
... message="Discovery document fetched successfully",
|
|
248
|
+
... details={"issuer": "https://idp.example.com", "endpoints": 5},
|
|
249
|
+
... duration_ms=234,
|
|
250
|
+
... )
|
|
251
|
+
>>> result.success
|
|
252
|
+
True
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
step: str
|
|
256
|
+
status: PreflightStatus
|
|
257
|
+
message: str
|
|
258
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
259
|
+
duration_ms: int | None = None
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def success(self) -> bool:
|
|
263
|
+
"""Check if step passed (success or warning)."""
|
|
264
|
+
return self.status in (PreflightStatus.SUCCESS, PreflightStatus.WARNING)
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def failed(self) -> bool:
|
|
268
|
+
"""Check if step failed."""
|
|
269
|
+
return self.status == PreflightStatus.FAILURE
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@dataclass(slots=True)
|
|
273
|
+
class PreflightReport:
|
|
274
|
+
"""Aggregated results from a complete preflight check.
|
|
275
|
+
|
|
276
|
+
Attributes:
|
|
277
|
+
provider_name: Name of the provider being validated.
|
|
278
|
+
results: List of individual step results.
|
|
279
|
+
started_at: When the preflight started.
|
|
280
|
+
completed_at: When the preflight finished.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
provider_name: str
|
|
284
|
+
results: list[PreflightResult] = field(default_factory=list)
|
|
285
|
+
started_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
286
|
+
completed_at: datetime | None = None
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def success(self) -> bool:
|
|
290
|
+
"""Check if all steps passed (no failures)."""
|
|
291
|
+
return all(not r.failed for r in self.results)
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def total_duration_ms(self) -> int:
|
|
295
|
+
"""Total time for all steps in milliseconds."""
|
|
296
|
+
return sum(r.duration_ms or 0 for r in self.results)
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def failed_steps(self) -> list[PreflightResult]:
|
|
300
|
+
"""List of failed steps."""
|
|
301
|
+
return [r for r in self.results if r.failed]
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def warnings(self) -> list[PreflightResult]:
|
|
305
|
+
"""List of steps with warnings."""
|
|
306
|
+
return [r for r in self.results if r.status == PreflightStatus.WARNING]
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
__all__ = [
|
|
310
|
+
"AuthFlow",
|
|
311
|
+
"PreflightReport",
|
|
312
|
+
"PreflightResult",
|
|
313
|
+
"PreflightStatus",
|
|
314
|
+
"Token",
|
|
315
|
+
"TokenType",
|
|
316
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Authentication providers for OAuth2/OIDC."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from kstlib.auth.providers.base import AbstractAuthProvider, AuthProviderConfig
|
|
6
|
+
from kstlib.auth.providers.oauth2 import OAuth2Provider
|
|
7
|
+
from kstlib.auth.providers.oidc import OIDCProvider
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AbstractAuthProvider",
|
|
11
|
+
"AuthProviderConfig",
|
|
12
|
+
"OAuth2Provider",
|
|
13
|
+
"OIDCProvider",
|
|
14
|
+
]
|