@agenticprimitives/connect-auth 0.1.0-alpha.2
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.
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/csrf.d.ts +13 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/csrf.js +85 -0
- package/dist/csrf.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/methods/google.d.ts +72 -0
- package/dist/methods/google.d.ts.map +1 -0
- package/dist/methods/google.js +239 -0
- package/dist/methods/google.js.map +1 -0
- package/dist/methods/passkey.d.ts +106 -0
- package/dist/methods/passkey.d.ts.map +1 -0
- package/dist/methods/passkey.js +307 -0
- package/dist/methods/passkey.js.map +1 -0
- package/dist/methods/siwe.d.ts +92 -0
- package/dist/methods/siwe.d.ts.map +1 -0
- package/dist/methods/siwe.js +207 -0
- package/dist/methods/siwe.js.map +1 -0
- package/dist/salt.d.ts +22 -0
- package/dist/salt.d.ts.map +1 -0
- package/dist/salt.js +54 -0
- package/dist/salt.js.map +1 -0
- package/dist/sessions.d.ts +15 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +143 -0
- package/dist/sessions.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/verify-signature.d.ts +163 -0
- package/dist/verify-signature.d.ts.map +1 -0
- package/dist/verify-signature.js +118 -0
- package/dist/verify-signature.js.map +1 -0
- package/package.json +73 -0
- package/spec.md +6 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agentic Trust Labs
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @agenticprimitives/connect-auth
|
|
2
|
+
|
|
3
|
+
**Credential connection** for Smart Agents — passkey, SIWE, and OAuth ceremonies,
|
|
4
|
+
JWT sessions, and pluggable `Signer` interfaces.
|
|
5
|
+
|
|
6
|
+
This package resolves **how a user proves control** (passkey, EOA, OAuth). The
|
|
7
|
+
**canonical identity** is the Smart Agent address (`agent-account`). Session JWTs
|
|
8
|
+
use the SA as primary subject; credentials appear as signer claims only
|
|
9
|
+
([ADR-0010](../../docs/architecture/decisions/0010-smart-agent-canonical-identifier.md)).
|
|
10
|
+
|
|
11
|
+
Identity persists. Credentials rotate ([ADR-0011](../../docs/architecture/decisions/0011-credential-recovery-and-re-association.md)).
|
|
12
|
+
Custodian add/remove belongs to `custody`, not here.
|
|
13
|
+
|
|
14
|
+
## Use This When
|
|
15
|
+
|
|
16
|
+
- You implement passkey signup/login (WebAuthn).
|
|
17
|
+
- You implement SIWE or Google OAuth sign-in.
|
|
18
|
+
- You mint or verify JWT cookie sessions and CSRF tokens.
|
|
19
|
+
- You need `Signer` interfaces for `agent-account` or `delegation`.
|
|
20
|
+
- You derive CREATE2 salt from stable user scope (`deriveSaltFromEmail`, etc.).
|
|
21
|
+
|
|
22
|
+
## Do Not Use This For
|
|
23
|
+
|
|
24
|
+
- Smart Agent deploy, UserOps, or ERC-1271 account logic → `agent-account`.
|
|
25
|
+
- Enrolling / rotating custodians on an SA → `custody`.
|
|
26
|
+
- `.agent` names → `agent-naming`.
|
|
27
|
+
- Public AgentCard profiles → `agent-profile`.
|
|
28
|
+
- Delegation tokens or encrypted session rows → `delegation`.
|
|
29
|
+
- KMS backends → `key-custody` (implements `KMSSigner`).
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add @agenticprimitives/connect-auth
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 60-Second Quickstart
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { mintSession, verifySession } from '@agenticprimitives/connect-auth';
|
|
41
|
+
import * as passkey from '@agenticprimitives/connect-auth/passkey';
|
|
42
|
+
|
|
43
|
+
// In your HTTP handler (app wires cookies):
|
|
44
|
+
const { sessionClaims } = await passkey.completeSignup(req);
|
|
45
|
+
const cookieValue = mintSession(sessionClaims);
|
|
46
|
+
// sessionClaims MUST include canonical Smart Agent address as primary subject
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { deriveSaltFromEmail } from '@agenticprimitives/connect-auth';
|
|
51
|
+
import { AgentAccountClient } from '@agenticprimitives/agent-account';
|
|
52
|
+
|
|
53
|
+
// Salt from user scope — NOT from .agent name.
|
|
54
|
+
const salt = deriveSaltFromEmail(user.email, 0);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Main Concepts
|
|
58
|
+
|
|
59
|
+
- **Credential**: passkey, SIWE EOA, or OAuth identity — control facet, not the SA.
|
|
60
|
+
- **Canonical SA**: resolved after auth (custodian lookup); JWT primary subject.
|
|
61
|
+
- **Signer**: interface consumed by `agent-account` / `delegation`.
|
|
62
|
+
- **JWT session**: signed cookie session (distinct from `delegation` `SessionRow`).
|
|
63
|
+
|
|
64
|
+
See [`docs/concepts.md`](docs/concepts.md).
|
|
65
|
+
|
|
66
|
+
## Auth Method Subpaths (Tree-Shakable)
|
|
67
|
+
|
|
68
|
+
- `@agenticprimitives/connect-auth/passkey`
|
|
69
|
+
- `@agenticprimitives/connect-auth/siwe`
|
|
70
|
+
- `@agenticprimitives/connect-auth/google`
|
|
71
|
+
|
|
72
|
+
## Security Invariants
|
|
73
|
+
|
|
74
|
+
- JWT secrets never logged; CSRF origin exact-match.
|
|
75
|
+
- WebAuthn challenges one-shot.
|
|
76
|
+
- Salt derivation deterministic (keccak).
|
|
77
|
+
|
|
78
|
+
See [`docs/security.md`](docs/security.md) and [`AUDIT.md`](AUDIT.md).
|
|
79
|
+
|
|
80
|
+
## Documentation Map
|
|
81
|
+
|
|
82
|
+
- [`docs/concepts.md`](docs/concepts.md) — credential vs canonical SA.
|
|
83
|
+
- [`docs/cross-browser-secure-home-passkey.md`](docs/cross-browser-secure-home-passkey.md) — Chrome/Firefox + Windows Hello secure-home flow.
|
|
84
|
+
- [`docs/api.md`](docs/api.md) — public API guide.
|
|
85
|
+
- [`docs/security.md`](docs/security.md) — invariants.
|
|
86
|
+
- [`docs/troubleshooting.md`](docs/troubleshooting.md) — common errors.
|
|
87
|
+
- [`docs/migration.md`](docs/migration.md) — migration notes.
|
|
88
|
+
- [`CLAUDE.md`](CLAUDE.md) — agent routing.
|
|
89
|
+
- [`spec.md`](spec.md) — spec pointer.
|
|
90
|
+
|
|
91
|
+
## Validation
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pnpm check:connect-auth
|
|
95
|
+
pnpm check:forbidden-terms
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
UNLICENSED.
|
package/dist/csrf.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Produce a CSRF token bound to the given origin and the current timestamp.
|
|
3
|
+
*/
|
|
4
|
+
export declare function csrfTokenFor(origin: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Verify a CSRF token.
|
|
7
|
+
* - origin (parsed exactly from the token) MUST be in allowedOrigins (exact-match).
|
|
8
|
+
* - HMAC must match.
|
|
9
|
+
* - Timestamp must be within the last CSRF_VALIDITY_SECONDS window.
|
|
10
|
+
* Returns true iff all three hold.
|
|
11
|
+
*/
|
|
12
|
+
export declare function verifyCsrf(token: string, allowedOrigins: string[]): boolean;
|
|
13
|
+
//# sourceMappingURL=csrf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"AA0CA;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAyB3E"}
|
package/dist/csrf.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// CSRF tokens: HMAC-stamped origin + timestamp.
|
|
2
|
+
// Token format: base64url(JSON.stringify({origin, ts})).base64url(hmac).
|
|
3
|
+
// verifyCsrf checks: origin ∈ allowlist (exact match), ts ∈ recent window,
|
|
4
|
+
// HMAC valid. Constant-time compare.
|
|
5
|
+
import { hmac } from '@noble/hashes/hmac';
|
|
6
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
7
|
+
import { hexToBytes } from 'viem';
|
|
8
|
+
const CSRF_VALIDITY_SECONDS = 60 * 60; // 1 hour
|
|
9
|
+
function loadCsrfSecret() {
|
|
10
|
+
const hex = process.env.CSRF_SECRET;
|
|
11
|
+
if (!hex) {
|
|
12
|
+
throw new Error('connect-auth: CSRF_SECRET (hex) is required. Generate one: openssl rand -hex 32');
|
|
13
|
+
}
|
|
14
|
+
const bytes = hexToBytes(hex.startsWith('0x') ? hex : `0x${hex}`);
|
|
15
|
+
if (bytes.length < 16) {
|
|
16
|
+
throw new Error(`connect-auth: CSRF_SECRET too short (need ≥ 16 bytes)`);
|
|
17
|
+
}
|
|
18
|
+
return bytes;
|
|
19
|
+
}
|
|
20
|
+
function base64urlEncode(s) {
|
|
21
|
+
const data = typeof s === 'string' ? new TextEncoder().encode(s) : s;
|
|
22
|
+
const b64 = Buffer.from(data).toString('base64');
|
|
23
|
+
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
24
|
+
}
|
|
25
|
+
function base64urlDecode(s) {
|
|
26
|
+
let padded = s.replace(/-/g, '+').replace(/_/g, '/');
|
|
27
|
+
while (padded.length % 4)
|
|
28
|
+
padded += '=';
|
|
29
|
+
return new Uint8Array(Buffer.from(padded, 'base64'));
|
|
30
|
+
}
|
|
31
|
+
function constantTimeEqual(a, b) {
|
|
32
|
+
if (a.length !== b.length)
|
|
33
|
+
return false;
|
|
34
|
+
let diff = 0;
|
|
35
|
+
for (let i = 0; i < a.length; i++)
|
|
36
|
+
diff |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
37
|
+
return diff === 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Produce a CSRF token bound to the given origin and the current timestamp.
|
|
41
|
+
*/
|
|
42
|
+
export function csrfTokenFor(origin) {
|
|
43
|
+
const secret = loadCsrfSecret();
|
|
44
|
+
const ts = Math.floor(Date.now() / 1000);
|
|
45
|
+
const stamp = JSON.stringify({ origin, ts });
|
|
46
|
+
const stampEnc = base64urlEncode(stamp);
|
|
47
|
+
const sig = hmac(sha256, secret, new TextEncoder().encode(stampEnc));
|
|
48
|
+
return `${stampEnc}.${base64urlEncode(sig)}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Verify a CSRF token.
|
|
52
|
+
* - origin (parsed exactly from the token) MUST be in allowedOrigins (exact-match).
|
|
53
|
+
* - HMAC must match.
|
|
54
|
+
* - Timestamp must be within the last CSRF_VALIDITY_SECONDS window.
|
|
55
|
+
* Returns true iff all three hold.
|
|
56
|
+
*/
|
|
57
|
+
export function verifyCsrf(token, allowedOrigins) {
|
|
58
|
+
if (!token)
|
|
59
|
+
return false;
|
|
60
|
+
const parts = token.split('.');
|
|
61
|
+
if (parts.length !== 2)
|
|
62
|
+
return false;
|
|
63
|
+
const [stampEnc, sigEnc] = parts;
|
|
64
|
+
let stamp;
|
|
65
|
+
try {
|
|
66
|
+
stamp = JSON.parse(new TextDecoder().decode(base64urlDecode(stampEnc)));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (typeof stamp.origin !== 'string' || typeof stamp.ts !== 'number')
|
|
72
|
+
return false;
|
|
73
|
+
// Exact-match parsed URL allowlist (per spec §6) — origins are compared verbatim,
|
|
74
|
+
// never substring.
|
|
75
|
+
if (!allowedOrigins.includes(stamp.origin))
|
|
76
|
+
return false;
|
|
77
|
+
const now = Math.floor(Date.now() / 1000);
|
|
78
|
+
if (now - stamp.ts > CSRF_VALIDITY_SECONDS || now < stamp.ts)
|
|
79
|
+
return false;
|
|
80
|
+
const secret = loadCsrfSecret();
|
|
81
|
+
const expected = hmac(sha256, secret, new TextEncoder().encode(stampEnc));
|
|
82
|
+
const presented = base64urlDecode(sigEnc);
|
|
83
|
+
return constantTimeEqual(expected, presented);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=csrf.js.map
|
package/dist/csrf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../src/csrf.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { mintSession, verifySession, SESSION_COOKIE, SESSION_TTL_SECONDS } from './sessions';
|
|
2
|
+
export { csrfTokenFor, verifyCsrf } from './csrf';
|
|
3
|
+
export { deriveSaltFromLabel, deriveSaltFromEmail, type DeriveSaltFromEmailOpts } from './salt';
|
|
4
|
+
export { ERC1271_MAGIC, ERC6492_MAGIC, universalSignatureValidatorAbi, verifyUserSignature, verifyUserSignatureView, isErc6492Wrapped, } from './verify-signature';
|
|
5
|
+
export type { SignatureVerifyResult, VerifyUserSignatureArgs, UniversalValidatorClient, } from './verify-signature';
|
|
6
|
+
export { P256_N, base64urlEncode, base64urlDecode, parseDerSignature, normaliseLowS, buildWebAuthnAssertion, hashToWebAuthnChallenge, parseAttestationObject, parseAuthData, } from './methods/passkey';
|
|
7
|
+
export type { WebAuthnAssertion, ParsedAttestation } from './methods/passkey';
|
|
8
|
+
export type { Address, Hex, AuthMethod, JwtClaims, AuthenticatedUser, TypedDataDomain, TypedDataTypes, Signer, PasskeyAssertion, PasskeySigner, EOASigner, KMSSigner, } from './types';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,QAAQ,CAAC;AAChG,OAAO,EACL,aAAa,EACb,aAAa,EACb,8BAA8B,EAC9B,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EACL,MAAM,EACN,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,YAAY,EACV,OAAO,EACP,GAAG,EACH,UAAU,EACV,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,SAAS,GACV,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @agenticprimitives/connect-auth — public API
|
|
2
|
+
//
|
|
3
|
+
// See ../../specs/200-connect-auth.md for the full contract.
|
|
4
|
+
export { mintSession, verifySession, SESSION_COOKIE, SESSION_TTL_SECONDS } from './sessions';
|
|
5
|
+
export { csrfTokenFor, verifyCsrf } from './csrf';
|
|
6
|
+
export { deriveSaltFromLabel, deriveSaltFromEmail } from './salt';
|
|
7
|
+
export { ERC1271_MAGIC, ERC6492_MAGIC, universalSignatureValidatorAbi, verifyUserSignature, verifyUserSignatureView, isErc6492Wrapped, } from './verify-signature';
|
|
8
|
+
// WebAuthn ceremony helpers (preferred deep import:
|
|
9
|
+
// `@agenticprimitives/connect-auth/passkey` for tree-shaking — re-exported
|
|
10
|
+
// here for discoverability).
|
|
11
|
+
export { P256_N, base64urlEncode, base64urlDecode, parseDerSignature, normaliseLowS, buildWebAuthnAssertion, hashToWebAuthnChallenge, parseAttestationObject, parseAuthData, } from './methods/passkey';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,EAAE;AACF,6DAA6D;AAE7D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAgC,MAAM,QAAQ,CAAC;AAChG,OAAO,EACL,aAAa,EACb,aAAa,EACb,8BAA8B,EAC9B,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAO5B,oDAAoD;AACpD,2EAA2E;AAC3E,6BAA6B;AAC7B,OAAO,EACL,MAAM,EACN,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,aAAa,GACd,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface OidcProviderConfig {
|
|
2
|
+
/** Accepted `iss` claim values (Google issues `https://accounts.google.com`). */
|
|
3
|
+
issuers: string[];
|
|
4
|
+
authorizationEndpoint: string;
|
|
5
|
+
tokenEndpoint: string;
|
|
6
|
+
jwksUri: string;
|
|
7
|
+
}
|
|
8
|
+
/** Google's stable OIDC endpoints (discovery doc values, pinned). */
|
|
9
|
+
export declare const GOOGLE_OIDC: OidcProviderConfig;
|
|
10
|
+
export interface BeginLoginInput {
|
|
11
|
+
clientId: string;
|
|
12
|
+
redirectUri: string;
|
|
13
|
+
/** OIDC scopes; `openid` is forced. Defaults to `openid email profile`. */
|
|
14
|
+
scope?: string;
|
|
15
|
+
/** e.g. 'consent' | 'select_account'. */
|
|
16
|
+
prompt?: string;
|
|
17
|
+
config?: OidcProviderConfig;
|
|
18
|
+
}
|
|
19
|
+
export interface BeginLoginResult {
|
|
20
|
+
/** Redirect the user agent here. */
|
|
21
|
+
authUrl: string;
|
|
22
|
+
/** Store these in the broker's same-origin session; needed by completeLogin. */
|
|
23
|
+
codeVerifier: string;
|
|
24
|
+
state: string;
|
|
25
|
+
nonce: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Start the OIDC login: generate PKCE verifier/challenge, state, nonce, and the
|
|
29
|
+
* authorization URL. The caller stores `codeVerifier`/`state`/`nonce` server-side
|
|
30
|
+
* (the broker `BrokerSession`) and redirects to `authUrl`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function beginLogin(input: BeginLoginInput): BeginLoginResult;
|
|
33
|
+
export interface OidcPrincipal {
|
|
34
|
+
/** The verified issuer. */
|
|
35
|
+
iss: string;
|
|
36
|
+
/** The verified subject (immutable provider id). The facet key, NOT the identity. */
|
|
37
|
+
sub: string;
|
|
38
|
+
email: string | null;
|
|
39
|
+
/** Always true on success — login is rejected unless the provider asserts it. */
|
|
40
|
+
emailVerified: boolean;
|
|
41
|
+
name: string | null;
|
|
42
|
+
}
|
|
43
|
+
export interface CompleteLoginInput {
|
|
44
|
+
/** Authorization code returned to the redirect_uri. */
|
|
45
|
+
code: string;
|
|
46
|
+
/** `state` returned by the provider — compared to `expectedState`. */
|
|
47
|
+
returnedState: string;
|
|
48
|
+
/** Values produced by beginLogin and stored server-side. */
|
|
49
|
+
expectedState: string;
|
|
50
|
+
expectedNonce: string;
|
|
51
|
+
codeVerifier: string;
|
|
52
|
+
/** Must match the value used in beginLogin. */
|
|
53
|
+
redirectUri: string;
|
|
54
|
+
clientId: string;
|
|
55
|
+
clientSecret: string;
|
|
56
|
+
config?: OidcProviderConfig;
|
|
57
|
+
/** Injectable for tests (defaults to global fetch). */
|
|
58
|
+
fetchImpl?: typeof fetch;
|
|
59
|
+
/** Injectable clock in ms (defaults to Date.now). */
|
|
60
|
+
now?: () => number;
|
|
61
|
+
}
|
|
62
|
+
export type CompleteLoginResult = {
|
|
63
|
+
ok: true;
|
|
64
|
+
principal: OidcPrincipal;
|
|
65
|
+
} | {
|
|
66
|
+
ok: false;
|
|
67
|
+
reason: string;
|
|
68
|
+
};
|
|
69
|
+
/** Build the `(iss, sub)` facet key a directory keys an OIDC credential on. */
|
|
70
|
+
export declare function oidcFacetId(iss: string, sub: string): string;
|
|
71
|
+
export declare function completeLogin(input: CompleteLoginInput): Promise<CompleteLoginResult>;
|
|
72
|
+
//# sourceMappingURL=google.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/methods/google.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qEAAqE;AACrE,eAAO,MAAM,WAAW,EAAE,kBAKzB,CAAC;AA0DF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAqBnE;AAID,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iFAAiF;IACjF,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,aAAa,CAAA;CAAE,GACtC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAclC,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgE3F"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// Google OIDC auth method (real implementation).
|
|
2
|
+
//
|
|
3
|
+
// Authorization-code + PKCE (S256) + state + nonce, then id_token verification
|
|
4
|
+
// (RS256 via Web Crypto against the provider JWKS) and claim validation
|
|
5
|
+
// (iss / aud / exp / nonce + email_verified). Dependency-light: @noble/hashes
|
|
6
|
+
// for the PKCE challenge, Web Crypto (`globalThis.crypto.subtle`, Node 20+) for
|
|
7
|
+
// RSA verification — no JWT library.
|
|
8
|
+
//
|
|
9
|
+
// SECURITY (audit CN-3 / CN-4, ADR-0017):
|
|
10
|
+
// - PKCE + state + nonce are mandatory; `state` is compared constant-time.
|
|
11
|
+
// - `alg` is PINNED to RS256 — the token's own `alg` header is never trusted
|
|
12
|
+
// to select the algorithm; `alg: none` / HS* are rejected (alg-confusion).
|
|
13
|
+
// - `iss` + `aud` validated; `nonce` matched to the expected value.
|
|
14
|
+
// - `email_verified === true` is REQUIRED; the facet is keyed on (iss, sub),
|
|
15
|
+
// never on email (resists email-reuse takeover).
|
|
16
|
+
//
|
|
17
|
+
// This method returns a *verified OIDC principal* (iss/sub/email). It does NOT
|
|
18
|
+
// resolve to a canonical Smart Agent — that binding is the directory's job
|
|
19
|
+
// (spec 223) and the broker's (spec 224); connect-auth must not import them.
|
|
20
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
21
|
+
/** Google's stable OIDC endpoints (discovery doc values, pinned). */
|
|
22
|
+
export const GOOGLE_OIDC = {
|
|
23
|
+
issuers: ['https://accounts.google.com', 'accounts.google.com'],
|
|
24
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
25
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
26
|
+
jwksUri: 'https://www.googleapis.com/oauth2/v3/certs',
|
|
27
|
+
};
|
|
28
|
+
// ─── base64url + random helpers ───────────────────────────────────────
|
|
29
|
+
function b64urlEncode(bytes) {
|
|
30
|
+
let s;
|
|
31
|
+
if (typeof Buffer !== 'undefined') {
|
|
32
|
+
s = Buffer.from(bytes).toString('base64');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
let bin = '';
|
|
36
|
+
for (const b of bytes)
|
|
37
|
+
bin += String.fromCharCode(b);
|
|
38
|
+
s = btoa(bin);
|
|
39
|
+
}
|
|
40
|
+
return s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
41
|
+
}
|
|
42
|
+
function b64urlToBytes(s) {
|
|
43
|
+
let padded = s.replace(/-/g, '+').replace(/_/g, '/');
|
|
44
|
+
while (padded.length % 4)
|
|
45
|
+
padded += '=';
|
|
46
|
+
if (typeof Buffer !== 'undefined')
|
|
47
|
+
return new Uint8Array(Buffer.from(padded, 'base64'));
|
|
48
|
+
const bin = atob(padded);
|
|
49
|
+
const out = new Uint8Array(bin.length);
|
|
50
|
+
for (let i = 0; i < bin.length; i++)
|
|
51
|
+
out[i] = bin.charCodeAt(i);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function b64urlToString(s) {
|
|
55
|
+
return new TextDecoder().decode(b64urlToBytes(s));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Copy into a fresh ArrayBuffer-backed view. Web Crypto's `BufferSource` typing
|
|
59
|
+
* (TS 5.7 typed-array generics) rejects `Uint8Array<ArrayBufferLike>`; a fresh
|
|
60
|
+
* `new Uint8Array(n)` is `Uint8Array<ArrayBuffer>`, which is accepted.
|
|
61
|
+
*/
|
|
62
|
+
function freshBytes(u) {
|
|
63
|
+
const out = new Uint8Array(u.byteLength);
|
|
64
|
+
out.set(u);
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
function randomB64url(byteLen) {
|
|
68
|
+
const buf = new Uint8Array(byteLen);
|
|
69
|
+
globalThis.crypto.getRandomValues(buf);
|
|
70
|
+
return b64urlEncode(buf);
|
|
71
|
+
}
|
|
72
|
+
function constantTimeEqualStr(a, b) {
|
|
73
|
+
const ab = new TextEncoder().encode(a);
|
|
74
|
+
const bb = new TextEncoder().encode(b);
|
|
75
|
+
if (ab.length !== bb.length)
|
|
76
|
+
return false;
|
|
77
|
+
let diff = 0;
|
|
78
|
+
for (let i = 0; i < ab.length; i++)
|
|
79
|
+
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
80
|
+
return diff === 0;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Start the OIDC login: generate PKCE verifier/challenge, state, nonce, and the
|
|
84
|
+
* authorization URL. The caller stores `codeVerifier`/`state`/`nonce` server-side
|
|
85
|
+
* (the broker `BrokerSession`) and redirects to `authUrl`.
|
|
86
|
+
*/
|
|
87
|
+
export function beginLogin(input) {
|
|
88
|
+
const config = input.config ?? GOOGLE_OIDC;
|
|
89
|
+
const codeVerifier = randomB64url(32); // 43-char unreserved string (RFC 7636)
|
|
90
|
+
const codeChallenge = b64urlEncode(sha256(new TextEncoder().encode(codeVerifier)));
|
|
91
|
+
const state = randomB64url(24);
|
|
92
|
+
const nonce = randomB64url(24);
|
|
93
|
+
const scope = input.scope ?? 'openid email profile';
|
|
94
|
+
const params = new URLSearchParams({
|
|
95
|
+
response_type: 'code',
|
|
96
|
+
client_id: input.clientId,
|
|
97
|
+
redirect_uri: input.redirectUri,
|
|
98
|
+
scope: scope.includes('openid') ? scope : `openid ${scope}`,
|
|
99
|
+
state,
|
|
100
|
+
nonce,
|
|
101
|
+
code_challenge: codeChallenge,
|
|
102
|
+
code_challenge_method: 'S256',
|
|
103
|
+
});
|
|
104
|
+
if (input.prompt)
|
|
105
|
+
params.set('prompt', input.prompt);
|
|
106
|
+
return { authUrl: `${config.authorizationEndpoint}?${params.toString()}`, codeVerifier, state, nonce };
|
|
107
|
+
}
|
|
108
|
+
/** Build the `(iss, sub)` facet key a directory keys an OIDC credential on. */
|
|
109
|
+
export function oidcFacetId(iss, sub) {
|
|
110
|
+
return `${iss}#${sub}`;
|
|
111
|
+
}
|
|
112
|
+
export async function completeLogin(input) {
|
|
113
|
+
const config = input.config ?? GOOGLE_OIDC;
|
|
114
|
+
// Bind the global fetch to globalThis: the Cloudflare workerd runtime throws
|
|
115
|
+
// "Illegal invocation" if its native `fetch` is invoked with a `this` other
|
|
116
|
+
// than the global (e.g. `ctx.fetchImpl(url)` — a method call sets `this=ctx`).
|
|
117
|
+
// A bound function ignores the call-site `this`, so it is safe to pass around
|
|
118
|
+
// and call as a property. A caller-supplied mock is used as-is.
|
|
119
|
+
const doFetch = input.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
120
|
+
const nowMs = (input.now ?? Date.now)();
|
|
121
|
+
// 1. State (CSRF / mix-up defense) — constant-time, before any network call.
|
|
122
|
+
if (!input.returnedState || !constantTimeEqualStr(input.returnedState, input.expectedState)) {
|
|
123
|
+
return { ok: false, reason: 'state mismatch' };
|
|
124
|
+
}
|
|
125
|
+
// 2. Authorization-code → tokens (with PKCE code_verifier).
|
|
126
|
+
let idToken;
|
|
127
|
+
try {
|
|
128
|
+
const body = new URLSearchParams({
|
|
129
|
+
grant_type: 'authorization_code',
|
|
130
|
+
code: input.code,
|
|
131
|
+
redirect_uri: input.redirectUri,
|
|
132
|
+
client_id: input.clientId,
|
|
133
|
+
client_secret: input.clientSecret,
|
|
134
|
+
code_verifier: input.codeVerifier,
|
|
135
|
+
});
|
|
136
|
+
const res = await doFetch(config.tokenEndpoint, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },
|
|
139
|
+
body: body.toString(),
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok)
|
|
142
|
+
return { ok: false, reason: `token endpoint returned ${res.status}` };
|
|
143
|
+
const json = (await res.json());
|
|
144
|
+
if (!json.id_token)
|
|
145
|
+
return { ok: false, reason: 'token response missing id_token' };
|
|
146
|
+
idToken = json.id_token;
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
return { ok: false, reason: e instanceof Error ? `token exchange failed: ${e.message}` : 'token exchange failed' };
|
|
150
|
+
}
|
|
151
|
+
// 3. Verify the id_token.
|
|
152
|
+
const verified = await verifyIdToken(idToken, {
|
|
153
|
+
config,
|
|
154
|
+
clientId: input.clientId,
|
|
155
|
+
expectedNonce: input.expectedNonce,
|
|
156
|
+
nowMs,
|
|
157
|
+
fetchImpl: doFetch,
|
|
158
|
+
});
|
|
159
|
+
if (!verified.ok)
|
|
160
|
+
return verified;
|
|
161
|
+
// 4. email_verified is mandatory (audit CN-3 / P0-3).
|
|
162
|
+
const ev = verified.claims.email_verified;
|
|
163
|
+
const emailVerified = ev === true || ev === 'true';
|
|
164
|
+
if (!emailVerified)
|
|
165
|
+
return { ok: false, reason: 'email_verified is not true' };
|
|
166
|
+
return {
|
|
167
|
+
ok: true,
|
|
168
|
+
principal: {
|
|
169
|
+
iss: verified.claims.iss,
|
|
170
|
+
sub: verified.claims.sub,
|
|
171
|
+
email: verified.claims.email ?? null,
|
|
172
|
+
emailVerified: true,
|
|
173
|
+
name: verified.claims.name ?? null,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function verifyIdToken(idToken, ctx) {
|
|
178
|
+
const parts = idToken.split('.');
|
|
179
|
+
if (parts.length !== 3)
|
|
180
|
+
return { ok: false, reason: 'id_token is not a 3-part JWT' };
|
|
181
|
+
const [headerB64, payloadB64, sigB64] = parts;
|
|
182
|
+
let header;
|
|
183
|
+
let claims;
|
|
184
|
+
try {
|
|
185
|
+
header = JSON.parse(b64urlToString(headerB64));
|
|
186
|
+
claims = JSON.parse(b64urlToString(payloadB64));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return { ok: false, reason: 'id_token header/payload not valid JSON' };
|
|
190
|
+
}
|
|
191
|
+
// Alg pinning — never trust the token's alg to pick the algorithm (CN-4).
|
|
192
|
+
if (header.alg !== 'RS256')
|
|
193
|
+
return { ok: false, reason: `id_token alg must be RS256, got "${header.alg}"` };
|
|
194
|
+
if (!header.kid)
|
|
195
|
+
return { ok: false, reason: 'id_token missing kid' };
|
|
196
|
+
// Fetch JWKS and find the signing key.
|
|
197
|
+
let jwk;
|
|
198
|
+
try {
|
|
199
|
+
const res = await ctx.fetchImpl(ctx.config.jwksUri, { headers: { accept: 'application/json' } });
|
|
200
|
+
if (!res.ok)
|
|
201
|
+
return { ok: false, reason: `JWKS endpoint returned ${res.status}` };
|
|
202
|
+
const set = (await res.json());
|
|
203
|
+
const found = set.keys?.find((k) => k.kid === header.kid && k.kty === 'RSA');
|
|
204
|
+
if (!found)
|
|
205
|
+
return { ok: false, reason: `no RSA JWKS key for kid "${header.kid}"` };
|
|
206
|
+
jwk = found;
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
return { ok: false, reason: e instanceof Error ? `JWKS fetch failed: ${e.message}` : 'JWKS fetch failed' };
|
|
210
|
+
}
|
|
211
|
+
// Verify RS256 over `header.payload` via Web Crypto.
|
|
212
|
+
let signatureValid;
|
|
213
|
+
try {
|
|
214
|
+
const key = await globalThis.crypto.subtle.importKey('jwk', { kty: jwk.kty, n: jwk.n, e: jwk.e, alg: 'RS256', ext: true }, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);
|
|
215
|
+
signatureValid = await globalThis.crypto.subtle.verify({ name: 'RSASSA-PKCS1-v1_5' }, key, freshBytes(b64urlToBytes(sigB64)), freshBytes(new TextEncoder().encode(`${headerB64}.${payloadB64}`)));
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return { ok: false, reason: e instanceof Error ? `signature verification error: ${e.message}` : 'signature verification error' };
|
|
219
|
+
}
|
|
220
|
+
if (!signatureValid)
|
|
221
|
+
return { ok: false, reason: 'id_token signature invalid' };
|
|
222
|
+
// Claim validation.
|
|
223
|
+
if (!claims.iss || !ctx.config.issuers.includes(claims.iss)) {
|
|
224
|
+
return { ok: false, reason: `id_token iss "${claims.iss}" not accepted` };
|
|
225
|
+
}
|
|
226
|
+
const audOk = Array.isArray(claims.aud) ? claims.aud.includes(ctx.clientId) : claims.aud === ctx.clientId;
|
|
227
|
+
if (!audOk)
|
|
228
|
+
return { ok: false, reason: 'id_token aud does not match clientId' };
|
|
229
|
+
if (typeof claims.exp !== 'number' || claims.exp * 1000 <= ctx.nowMs) {
|
|
230
|
+
return { ok: false, reason: 'id_token expired' };
|
|
231
|
+
}
|
|
232
|
+
if (!claims.nonce || !constantTimeEqualStr(claims.nonce, ctx.expectedNonce)) {
|
|
233
|
+
return { ok: false, reason: 'id_token nonce mismatch' };
|
|
234
|
+
}
|
|
235
|
+
if (!claims.sub)
|
|
236
|
+
return { ok: false, reason: 'id_token missing sub' };
|
|
237
|
+
return { ok: true, claims };
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=google.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/methods/google.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,EAAE;AACF,+EAA+E;AAC/E,wEAAwE;AACxE,8EAA8E;AAC9E,gFAAgF;AAChF,qCAAqC;AACrC,EAAE;AACF,0CAA0C;AAC1C,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAC/E,sEAAsE;AACtE,+EAA+E;AAC/E,qDAAqD;AACrD,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAE7E,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAY9C,qEAAqE;AACrE,MAAM,CAAC,MAAM,WAAW,GAAuB;IAC7C,OAAO,EAAE,CAAC,6BAA6B,EAAE,qBAAqB,CAAC;IAC/D,qBAAqB,EAAE,8CAA8C;IACrE,aAAa,EAAE,qCAAqC;IACpD,OAAO,EAAE,4CAA4C;CACtD,CAAC;AAEF,yEAAyE;AAEzE,SAAS,YAAY,CAAC,KAAiB;IACrC,IAAI,CAAS,CAAC;IACd,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,KAAK,MAAM,CAAC,IAAI,KAAK;YAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC;IACD,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,aAAa,CAAC,CAAS;IAC9B,IAAI,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,GAAG,CAAC;IACxC,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxF,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,cAAc,CAAC,CAAS;IAC/B,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,CAAa;IAC/B,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACX,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,oBAAoB,CAAC,CAAS,EAAE,CAAS;IAChD,MAAM,EAAE,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,EAAE,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvC,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACxE,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAuBD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,WAAW,CAAC;IAC3C,MAAM,YAAY,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,uCAAuC;IAC9E,MAAM,aAAa,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;IAE/B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,sBAAsB,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,aAAa,EAAE,MAAM;QACrB,SAAS,EAAE,KAAK,CAAC,QAAQ;QACzB,YAAY,EAAE,KAAK,CAAC,WAAW;QAC/B,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE;QAC3D,KAAK;QACL,KAAK;QACL,cAAc,EAAE,aAAa;QAC7B,qBAAqB,EAAE,MAAM;KAC9B,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,MAAM;QAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAErD,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,qBAAqB,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACzG,CAAC;AAmDD,+EAA+E;AAC/E,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,GAAW;IAClD,OAAO,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAyB;IAC3D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,WAAW,CAAC;IAC3C,6EAA6E;IAC7E,4EAA4E;IAC5E,+EAA+E;IAC/E,8EAA8E;IAC9E,gEAAgE;IAChE,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAExC,6EAA6E;IAC7E,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC5F,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IACjD,CAAC;IAED,4DAA4D;IAC5D,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;YAC/B,UAAU,EAAE,oBAAoB;YAChC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,YAAY,EAAE,KAAK,CAAC,WAAW;YAC/B,SAAS,EAAE,KAAK,CAAC,QAAQ;YACzB,aAAa,EAAE,KAAK,CAAC,YAAY;YACjC,aAAa,EAAE,KAAK,CAAC,YAAY;SAClC,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE;YAC9C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE,MAAM,EAAE,kBAAkB,EAAE;YAC5F,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE;SACtB,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;QACnF,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA0B,CAAC;QACzD,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC;QACpF,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC1B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,uBAAuB,EAAE,CAAC;IACrH,CAAC;IAED,0BAA0B;IAC1B,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE;QAC5C,MAAM;QACN,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,KAAK;QACL,SAAS,EAAE,OAAO;KACnB,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,QAAQ,CAAC;IAElC,sDAAsD;IACtD,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC;IAC1C,MAAM,aAAa,GAAG,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,MAAM,CAAC;IACnD,IAAI,CAAC,aAAa;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,4BAA4B,EAAE,CAAC;IAE/E,OAAO;QACL,EAAE,EAAE,IAAI;QACR,SAAS,EAAE;YACT,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAI;YACzB,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAI;YACzB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI;YACpC,aAAa,EAAE,IAAI;YACnB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI;SACnC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,OAAe,EACf,GAAoH;IAEpH,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC;IACrF,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,KAAiC,CAAC;IAE1E,IAAI,MAAsC,CAAC;IAC3C,IAAI,MAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/C,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wCAAwC,EAAE,CAAC;IACzE,CAAC;IAED,0EAA0E;IAC1E,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;IAC5G,IAAI,CAAC,MAAM,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;IAEtE,uCAAuC;IACvC,IAAI,GAAkC,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;QACjG,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,0BAA0B,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;QAClF,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoD,CAAC;QAClF,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;QAC7E,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,4BAA4B,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QACpF,GAAG,GAAG,KAAK,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC;IAC7G,CAAC;IAED,qDAAqD;IACrD,IAAI,cAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAClD,KAAK,EACL,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,EAAG,GAAsB,CAAC,CAAC,EAAE,CAAC,EAAG,GAAsB,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EACrG,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,EAAE,EAC9C,KAAK,EACL,CAAC,QAAQ,CAAC,CACX,CAAC;QACF,cAAc,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CACpD,EAAE,IAAI,EAAE,mBAAmB,EAAE,EAC7B,GAAG,EACH,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EACjC,UAAU,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC,CAAC,CACnE,CAAC;IACJ,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,8BAA8B,EAAE,CAAC;IACnI,CAAC;IACD,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,4BAA4B,EAAE,CAAC;IAEhF,oBAAoB;IACpB,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,MAAM,CAAC,GAAG,gBAAgB,EAAE,CAAC;IAC5E,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,QAAQ,CAAC;IAC1G,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sCAAsC,EAAE,CAAC;IACjF,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QACrE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACnD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;QAC5E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;IAC1D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;IAEtE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC"}
|