authweave-core 7.0.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.
@@ -0,0 +1,45 @@
1
+ """Framework-neutral principal authentication contracts."""
2
+
3
+ from authweave_core.coordinator import AuthenticationCoordinator, RequestAuthenticationProvider
4
+ from authweave_core.models import (
5
+ Authenticated,
6
+ AuthenticationContext,
7
+ AuthenticationDecision,
8
+ AuthenticationEvidence,
9
+ AuthenticationRuntime,
10
+ CredentialMatch,
11
+ EvidenceValue,
12
+ FailureCode,
13
+ Invalid,
14
+ InvariantFailure,
15
+ NotApplicable,
16
+ PrincipalRef,
17
+ RequestView,
18
+ RouteProviderPolicy,
19
+ TlsPeerEvidence,
20
+ Unavailable,
21
+ )
22
+
23
+ __version__ = "7.0.0"
24
+
25
+ __all__ = (
26
+ "Authenticated",
27
+ "AuthenticationContext",
28
+ "AuthenticationCoordinator",
29
+ "AuthenticationDecision",
30
+ "AuthenticationEvidence",
31
+ "AuthenticationRuntime",
32
+ "CredentialMatch",
33
+ "EvidenceValue",
34
+ "FailureCode",
35
+ "Invalid",
36
+ "InvariantFailure",
37
+ "NotApplicable",
38
+ "PrincipalRef",
39
+ "RequestAuthenticationProvider",
40
+ "RequestView",
41
+ "RouteProviderPolicy",
42
+ "TlsPeerEvidence",
43
+ "Unavailable",
44
+ "__version__",
45
+ )
@@ -0,0 +1,148 @@
1
+ """Fail-closed authentication provider coordination."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
6
+
7
+ import anyio
8
+
9
+ from authweave_core.models import (
10
+ Authenticated,
11
+ AuthenticationDecision,
12
+ AuthenticationRuntime,
13
+ CredentialMatch,
14
+ FailureCode,
15
+ Invalid,
16
+ InvariantFailure,
17
+ NotApplicable,
18
+ RequestView,
19
+ RouteProviderPolicy,
20
+ Unavailable,
21
+ _validate_label,
22
+ )
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Iterable
26
+
27
+
28
+ @runtime_checkable
29
+ class RequestAuthenticationProvider(Protocol):
30
+ """Framework-neutral request authentication provider."""
31
+
32
+ name: str
33
+ profile: str
34
+
35
+ def match(self, request: RequestView) -> CredentialMatch:
36
+ """Classify credential ownership without performing expensive verification."""
37
+ ...
38
+
39
+ async def authenticate(
40
+ self,
41
+ request: RequestView,
42
+ runtime: AuthenticationRuntime,
43
+ ) -> AuthenticationDecision:
44
+ """Verify an owned credential presentation."""
45
+ ...
46
+
47
+
48
+ class AuthenticationCoordinator:
49
+ """Route providers deterministically and stop on every owned failure."""
50
+
51
+ def __init__(self, providers: Iterable[RequestAuthenticationProvider]) -> None:
52
+ """Freeze and validate the provider inventory.
53
+
54
+ Raises:
55
+ ValueError: If provider names or profiles are invalid or duplicated.
56
+ """
57
+ inventory = tuple(providers)
58
+ names: set[str] = set()
59
+ profiles: set[str] = set()
60
+ for provider in inventory:
61
+ _validate_label(provider.name, name="provider name")
62
+ _validate_label(provider.profile, name="provider profile")
63
+ if provider.name in names:
64
+ msg = f"duplicate provider name: {provider.name}"
65
+ raise ValueError(msg)
66
+ if provider.profile in profiles:
67
+ msg = f"duplicate provider profile: {provider.profile}"
68
+ raise ValueError(msg)
69
+ names.add(provider.name)
70
+ profiles.add(provider.profile)
71
+ self._providers = {provider.name: provider for provider in inventory}
72
+
73
+ async def authenticate(
74
+ self,
75
+ request: RequestView,
76
+ runtime: AuthenticationRuntime,
77
+ policy: RouteProviderPolicy,
78
+ ) -> AuthenticationDecision:
79
+ """Authenticate through exactly one credential owner.
80
+
81
+ Returns:
82
+ A terminal typed authentication decision.
83
+ """
84
+ selected = self._select_provider(request, policy)
85
+ if not isinstance(selected, RequestAuthenticationProvider):
86
+ return selected
87
+
88
+ decision = await self._authenticate_with_deadline(selected, request, runtime)
89
+ return self._validate_decision(selected, decision)
90
+
91
+ def _select_provider(
92
+ self,
93
+ request: RequestView,
94
+ policy: RouteProviderPolicy,
95
+ ) -> RequestAuthenticationProvider | AuthenticationDecision:
96
+ allowed: list[RequestAuthenticationProvider] = []
97
+ for name in policy.providers:
98
+ provider = self._providers.get(name)
99
+ if provider is None:
100
+ return InvariantFailure()
101
+ match provider.match(request):
102
+ case CredentialMatch.NOT_APPLICABLE:
103
+ continue
104
+ case CredentialMatch.OWNED:
105
+ allowed.append(provider)
106
+ case CredentialMatch.AMBIGUOUS:
107
+ return Invalid(FailureCode.AMBIGUOUS_CREDENTIALS)
108
+ case _:
109
+ return InvariantFailure()
110
+ if not allowed:
111
+ return NotApplicable()
112
+ return allowed[0]
113
+
114
+ @staticmethod
115
+ def _validate_decision(
116
+ provider: RequestAuthenticationProvider,
117
+ decision: object,
118
+ ) -> AuthenticationDecision:
119
+ if isinstance(decision, NotApplicable):
120
+ return InvariantFailure()
121
+ if isinstance(decision, Authenticated):
122
+ evidence = decision.context.evidence
123
+ if evidence.provider != provider.name or evidence.profile != provider.profile:
124
+ return InvariantFailure()
125
+ if not isinstance(decision, (Authenticated, Invalid, Unavailable, InvariantFailure)):
126
+ return InvariantFailure()
127
+ return decision
128
+
129
+ @staticmethod
130
+ async def _authenticate_with_deadline(
131
+ provider: RequestAuthenticationProvider,
132
+ request: RequestView,
133
+ runtime: AuthenticationRuntime,
134
+ ) -> AuthenticationDecision:
135
+ """Apply the request deadline without swallowing external cancellation.
136
+
137
+ Returns:
138
+ Provider decision or unavailable when the deadline expires.
139
+ """
140
+ if runtime.deadline is None:
141
+ return await provider.authenticate(request, runtime)
142
+
143
+ remaining = runtime.deadline - anyio.current_time()
144
+ if remaining <= 0:
145
+ return Unavailable()
146
+ with anyio.move_on_after(remaining) as timeout_scope:
147
+ decision = await provider.authenticate(request, runtime)
148
+ return Unavailable() if timeout_scope.cancelled_caught else decision
@@ -0,0 +1,357 @@
1
+ """Immutable contracts shared by authentication providers and adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass, field
7
+ from datetime import UTC, datetime
8
+ from enum import StrEnum
9
+ from types import MappingProxyType
10
+ from typing import TYPE_CHECKING
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Mapping
14
+
15
+ _LABEL_PATTERN = re.compile(r"^[a-z][a-z0-9_.-]{0,63}$")
16
+ _EXTENSION_KEY_PATTERN = re.compile(r"^[a-z][a-z0-9_.-]{0,62}:[a-z][a-z0-9_.-]{0,62}$")
17
+ _HTTP_TOKEN_PATTERN = re.compile(rb"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$")
18
+ _THUMBPRINT_PATTERN = re.compile(r"^[A-Za-z0-9_-]{43}$")
19
+ _MAX_ISSUER_LENGTH = 2048
20
+ _MAX_SUBJECT_LENGTH = 512
21
+ _MAX_VALUE_LENGTH = 512
22
+ _MAX_EXTENSIONS = 16
23
+ _MAX_EVIDENCE_VALUES = 64
24
+ _MAX_HEADERS = 128
25
+ _MAX_HEADER_BYTES = 65_536
26
+
27
+ type EvidenceValue = str | int | bool
28
+
29
+
30
+ def _validate_text(value: str, *, name: str, max_length: int = _MAX_VALUE_LENGTH) -> None:
31
+ if not value or value != value.strip() or len(value) > max_length:
32
+ msg = f"{name} must be non-empty, trimmed, and at most {max_length} characters"
33
+ raise ValueError(msg)
34
+
35
+
36
+ def _validate_label(value: str, *, name: str) -> None:
37
+ if _LABEL_PATTERN.fullmatch(value) is None:
38
+ msg = f"{name} must match {_LABEL_PATTERN.pattern!r}"
39
+ raise ValueError(msg)
40
+
41
+
42
+ def _validate_aware(value: datetime | None, *, name: str) -> None:
43
+ if value is not None and value.utcoffset() is None:
44
+ msg = f"{name} must be timezone-aware"
45
+ raise ValueError(msg)
46
+
47
+
48
+ @dataclass(frozen=True, slots=True, eq=False)
49
+ class PrincipalRef:
50
+ """Stable principal identity with a verified classification."""
51
+
52
+ issuer: str
53
+ subject: str
54
+ kind: str
55
+
56
+ def __post_init__(self) -> None:
57
+ """Validate bounded identity components."""
58
+ _validate_text(self.issuer, name="issuer", max_length=_MAX_ISSUER_LENGTH)
59
+ _validate_text(self.subject, name="subject", max_length=_MAX_SUBJECT_LENGTH)
60
+ _validate_label(self.kind, name="kind")
61
+
62
+ def __eq__(self, other: object) -> bool:
63
+ """Compare stable identity independently from classification.
64
+
65
+ Returns:
66
+ Whether issuer and subject match.
67
+ """
68
+ if not isinstance(other, PrincipalRef):
69
+ return NotImplemented
70
+ return (self.issuer, self.subject) == (other.issuer, other.subject)
71
+
72
+ def __hash__(self) -> int:
73
+ """Hash the stable identity independently from classification.
74
+
75
+ Returns:
76
+ Hash of issuer and subject.
77
+ """
78
+ return hash((self.issuer, self.subject))
79
+
80
+
81
+ @dataclass(frozen=True, slots=True)
82
+ class TlsPeerEvidence:
83
+ """Verified TLS peer facts produced by a trusted termination boundary."""
84
+
85
+ tls_version: str
86
+ certificate_thumbprint: str
87
+ certificate_not_before: datetime
88
+ certificate_not_after: datetime
89
+ revocation_checked_at: datetime
90
+ trust_anchor: str
91
+ termination_boundary: str
92
+
93
+ def __post_init__(self) -> None:
94
+ """Reject malformed or internally inconsistent TLS evidence.
95
+
96
+ Raises:
97
+ ValueError: If a field is malformed or the validity interval is empty.
98
+ """
99
+ _validate_text(self.tls_version, name="tls_version", max_length=32)
100
+ if _THUMBPRINT_PATTERN.fullmatch(self.certificate_thumbprint) is None:
101
+ msg = "certificate_thumbprint must be an unpadded base64url SHA-256 digest"
102
+ raise ValueError(msg)
103
+ _validate_aware(self.certificate_not_before, name="certificate_not_before")
104
+ _validate_aware(self.certificate_not_after, name="certificate_not_after")
105
+ _validate_aware(self.revocation_checked_at, name="revocation_checked_at")
106
+ if self.certificate_not_before >= self.certificate_not_after:
107
+ msg = "certificate_not_before must be earlier than certificate_not_after"
108
+ raise ValueError(msg)
109
+ _validate_text(self.trust_anchor, name="trust_anchor")
110
+ _validate_text(self.termination_boundary, name="termination_boundary")
111
+
112
+
113
+ @dataclass(frozen=True, slots=True)
114
+ class RequestView:
115
+ """Immutable, framework-neutral projection of authentication inputs."""
116
+
117
+ method: str
118
+ headers: tuple[tuple[bytes, bytes], ...] = field(default=(), repr=False)
119
+ scheme: str | None = None
120
+ authority: str | None = None
121
+ tls_peer: TlsPeerEvidence | None = None
122
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
123
+ correlation_id: str | None = None
124
+
125
+ def __post_init__(self) -> None:
126
+ """Validate request metadata without collapsing duplicate headers.
127
+
128
+ Raises:
129
+ ValueError: If request metadata is malformed.
130
+ """
131
+ try:
132
+ method = self.method.encode("ascii")
133
+ except UnicodeEncodeError as exc:
134
+ msg = "method must be an ASCII HTTP token"
135
+ raise ValueError(msg) from exc
136
+ if _HTTP_TOKEN_PATTERN.fullmatch(method) is None:
137
+ msg = "method must be an ASCII HTTP token"
138
+ raise ValueError(msg)
139
+ object.__setattr__(self, "headers", tuple(self.headers))
140
+ if (
141
+ len(self.headers) > _MAX_HEADERS
142
+ or sum(len(name) + len(value) for name, value in self.headers) > _MAX_HEADER_BYTES
143
+ ):
144
+ msg = "headers exceed the authentication projection limits"
145
+ raise ValueError(msg)
146
+ for name, value in self.headers:
147
+ if _HTTP_TOKEN_PATTERN.fullmatch(name) is None:
148
+ msg = "header names must be non-empty ASCII HTTP tokens"
149
+ raise ValueError(msg)
150
+ if b"\x00" in value or b"\r" in value or b"\n" in value:
151
+ msg = "header values must not contain NUL, CR, or LF"
152
+ raise ValueError(msg)
153
+ if self.scheme is not None:
154
+ _validate_text(self.scheme, name="scheme", max_length=32)
155
+ if self.authority is not None:
156
+ _validate_text(self.authority, name="authority", max_length=512)
157
+ _validate_aware(self.timestamp, name="timestamp")
158
+ if self.correlation_id is not None:
159
+ _validate_text(self.correlation_id, name="correlation_id")
160
+
161
+ def header_values(self, name: bytes) -> tuple[bytes, ...]:
162
+ """Return every value for a case-insensitive header name."""
163
+ normalized = name.lower()
164
+ return tuple(value for header_name, value in self.headers if header_name.lower() == normalized)
165
+
166
+
167
+ @dataclass(frozen=True, slots=True)
168
+ class AuthenticationEvidence:
169
+ """Verified, secret-free facts produced by an authentication provider."""
170
+
171
+ provider: str
172
+ profile: str
173
+ method: str
174
+ issuer: str
175
+ audiences: tuple[str, ...] = ()
176
+ scopes: tuple[str, ...] = ()
177
+ issued_at: datetime | None = None
178
+ not_before: datetime | None = None
179
+ expires_at: datetime | None = None
180
+ credential_id: str | None = None
181
+ token_id: str | None = None
182
+ confirmation_thumbprint: str | None = None
183
+ environment: str | None = None
184
+ extensions: Mapping[str, EvidenceValue] = field(default_factory=dict)
185
+
186
+ def __post_init__(self) -> None:
187
+ """Validate bounded evidence and freeze extension data."""
188
+ _validate_label(self.provider, name="provider")
189
+ _validate_label(self.profile, name="profile")
190
+ _validate_label(self.method, name="method")
191
+ _validate_text(self.issuer, name="issuer", max_length=_MAX_ISSUER_LENGTH)
192
+ object.__setattr__(self, "audiences", _freeze_evidence_values(self.audiences, name="audiences"))
193
+ object.__setattr__(self, "scopes", _freeze_evidence_values(self.scopes, name="scopes"))
194
+ _validate_evidence_times(self)
195
+ _validate_evidence_identifiers(self)
196
+ object.__setattr__(self, "extensions", _freeze_extensions(self.extensions))
197
+
198
+
199
+ @dataclass(frozen=True, slots=True)
200
+ class AuthenticationContext:
201
+ """Authenticated subject, actor, delegation chain, and verified evidence."""
202
+
203
+ subject: PrincipalRef
204
+ actor: PrincipalRef
205
+ evidence: AuthenticationEvidence
206
+ delegation_chain: tuple[PrincipalRef, ...] = ()
207
+
208
+ def __post_init__(self) -> None:
209
+ """Freeze the delegation chain."""
210
+ object.__setattr__(self, "delegation_chain", tuple(self.delegation_chain))
211
+
212
+
213
+ class FailureCode(StrEnum):
214
+ """Stable neutral authentication failure codes."""
215
+
216
+ MISSING = "missing"
217
+ MALFORMED = "malformed"
218
+ INVALID = "invalid"
219
+ EXPIRED = "expired"
220
+ NOT_YET_VALID = "not_yet_valid"
221
+ REVOKED = "revoked"
222
+ PRINCIPAL_DISABLED = "principal_disabled"
223
+ ISSUER_MISMATCH = "issuer_mismatch"
224
+ AUDIENCE_MISMATCH = "audience_mismatch"
225
+ TOKEN_TYPE_MISMATCH = "token_type_mismatch"
226
+ ALGORITHM_MISMATCH = "algorithm_mismatch"
227
+ SENDER_CONSTRAINT_MISMATCH = "sender_constraint_mismatch"
228
+ AMBIGUOUS_CREDENTIALS = "ambiguous_credentials"
229
+ PROVIDER_UNAVAILABLE = "provider_unavailable"
230
+ INTERNAL_INVARIANT = "internal_invariant"
231
+
232
+
233
+ class CredentialMatch(StrEnum):
234
+ """Credential ownership result returned by a provider matcher."""
235
+
236
+ NOT_APPLICABLE = "not_applicable"
237
+ OWNED = "owned"
238
+ AMBIGUOUS = "ambiguous"
239
+
240
+
241
+ @dataclass(frozen=True, slots=True)
242
+ class NotApplicable:
243
+ """No allowed provider owns a credential presentation."""
244
+
245
+
246
+ @dataclass(frozen=True, slots=True)
247
+ class Authenticated:
248
+ """Credential was fully verified."""
249
+
250
+ context: AuthenticationContext
251
+
252
+
253
+ @dataclass(frozen=True, slots=True)
254
+ class Invalid:
255
+ """Credential was owned but failed verification."""
256
+
257
+ code: FailureCode
258
+
259
+
260
+ @dataclass(frozen=True, slots=True)
261
+ class Unavailable:
262
+ """Credential verification could not complete safely."""
263
+
264
+ code: FailureCode = FailureCode.PROVIDER_UNAVAILABLE
265
+
266
+
267
+ @dataclass(frozen=True, slots=True)
268
+ class InvariantFailure:
269
+ """Provider or coordinator contract was violated."""
270
+
271
+ code: FailureCode = field(default=FailureCode.INTERNAL_INVARIANT, init=False)
272
+
273
+
274
+ type AuthenticationDecision = NotApplicable | Authenticated | Invalid | Unavailable | InvariantFailure
275
+
276
+
277
+ @dataclass(frozen=True, slots=True)
278
+ class AuthenticationRuntime:
279
+ """Request-scoped execution limits for providers."""
280
+
281
+ deadline: float | None = None
282
+
283
+
284
+ @dataclass(frozen=True, slots=True)
285
+ class RouteProviderPolicy:
286
+ """Ordered-free set of provider names permitted for a route."""
287
+
288
+ providers: tuple[str, ...] = ()
289
+
290
+ def __post_init__(self) -> None:
291
+ """Validate and deduplicate provider names.
292
+
293
+ Raises:
294
+ ValueError: If a provider name is invalid or duplicated.
295
+ """
296
+ providers = tuple(self.providers)
297
+ for provider in providers:
298
+ _validate_label(provider, name="provider")
299
+ if len(providers) > 1:
300
+ msg = "route provider policy permits at most one authentication profile"
301
+ raise ValueError(msg)
302
+ object.__setattr__(self, "providers", providers)
303
+
304
+
305
+ def _freeze_evidence_values(values: tuple[str, ...], *, name: str) -> tuple[str, ...]:
306
+ frozen = tuple(values)
307
+ if len(frozen) > _MAX_EVIDENCE_VALUES:
308
+ msg = f"{name} must contain at most {_MAX_EVIDENCE_VALUES} values"
309
+ raise ValueError(msg)
310
+ for value in frozen:
311
+ _validate_text(value, name=name)
312
+ return frozen
313
+
314
+
315
+ def _validate_evidence_times(evidence: AuthenticationEvidence) -> None:
316
+ for name in ("issued_at", "not_before", "expires_at"):
317
+ _validate_aware(getattr(evidence, name), name=name)
318
+ if (
319
+ evidence.not_before is not None
320
+ and evidence.expires_at is not None
321
+ and evidence.not_before >= evidence.expires_at
322
+ ):
323
+ msg = "not_before must be earlier than expires_at"
324
+ raise ValueError(msg)
325
+
326
+
327
+ def _validate_evidence_identifiers(evidence: AuthenticationEvidence) -> None:
328
+ for name in ("credential_id", "token_id", "environment"):
329
+ value = getattr(evidence, name)
330
+ if value is not None:
331
+ _validate_text(value, name=name)
332
+ if (
333
+ evidence.confirmation_thumbprint is not None
334
+ and _THUMBPRINT_PATTERN.fullmatch(
335
+ evidence.confirmation_thumbprint,
336
+ )
337
+ is None
338
+ ):
339
+ msg = "confirmation_thumbprint must be an unpadded base64url SHA-256 digest"
340
+ raise ValueError(msg)
341
+
342
+
343
+ def _freeze_extensions(extensions: Mapping[str, EvidenceValue]) -> Mapping[str, EvidenceValue]:
344
+ frozen = dict(extensions)
345
+ if len(frozen) > _MAX_EXTENSIONS:
346
+ msg = f"extensions must contain at most {_MAX_EXTENSIONS} entries"
347
+ raise ValueError(msg)
348
+ for key, value in frozen.items():
349
+ if _EXTENSION_KEY_PATTERN.fullmatch(key) is None:
350
+ msg = f"extension key {key!r} must be namespaced"
351
+ raise ValueError(msg)
352
+ if isinstance(value, str):
353
+ _validate_text(value, name=f"extension {key}", max_length=256)
354
+ elif not isinstance(value, (bool, int)):
355
+ msg = f"extension {key!r} must contain a string, integer, or boolean"
356
+ raise TypeError(msg)
357
+ return MappingProxyType(frozen)
File without changes
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: authweave-core
3
+ Version: 7.0.0
4
+ Summary: Framework-neutral principal authentication contracts and coordinator
5
+ Project-URL: homepage, https://github.com/ZYLVEXT/litestar-auth
6
+ Project-URL: documentation, https://zylvext.github.io/litestar-auth/
7
+ Project-URL: source, https://github.com/ZYLVEXT/litestar-auth
8
+ Project-URL: tracker, https://github.com/ZYLVEXT/litestar-auth/issues
9
+ Author-email: Vladislav Shepilov <shepilov.v@protonmail.com>
10
+ Maintainer-email: Vladislav Shepilov <shepilov.v@protonmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: authentication,framework-agnostic,principal,security
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Python: <3.15.0,>=3.12.0
23
+ Requires-Dist: anyio<5.0,>=4.14.2
24
+ Description-Content-Type: text/markdown
25
+
26
+ # authweave-core
27
+
28
+ Framework-neutral contracts and fail-closed coordination for principal authentication.
29
+
30
+ ```bash
31
+ uv add authweave-core
32
+ ```
33
+
34
+ The package models verified principals, authentication evidence, immutable request projections,
35
+ typed authentication decisions, provider ownership, route policies, and deadline-aware
36
+ coordination. It does not depend on a web framework, ORM, cache, or cryptography implementation.
37
+
38
+ `authweave-core` performs authentication orchestration only. Applications remain responsible for
39
+ resource authorization.
@@ -0,0 +1,8 @@
1
+ authweave_core/__init__.py,sha256=CGSV9DuCiN2G1gyLxZuiCDwrXSsg1muHGqL8zAFs2Fo,1030
2
+ authweave_core/coordinator.py,sha256=4FW1R0WzRV4q6ITI9iauin1NR-u7hnYBBmZCNQ4SJLw,5106
3
+ authweave_core/models.py,sha256=B_FUO_2kLHO_JbWVsL66A4_gx1Le0Nt3g71QY2jQ9jw,12895
4
+ authweave_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ authweave_core-7.0.0.dist-info/METADATA,sha256=QU9ZzkstJQJeFYh8c8v6C9g5dDLlmnWfkwbaxwiTcng,1682
6
+ authweave_core-7.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
7
+ authweave_core-7.0.0.dist-info/licenses/LICENSE,sha256=vICktpxlg9VJdRVRknH1W42Zl_8WEEfYWUQ75ql3eyI,1075
8
+ authweave_core-7.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vladislav Shepilov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.