@vantageos/cloud-identity 0.6.0 → 0.8.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/README.md +147 -0
  3. package/dist/authorize-flow.d.ts +169 -0
  4. package/dist/authorize-flow.d.ts.map +1 -0
  5. package/dist/authorize-flow.js +262 -0
  6. package/dist/authorize-flow.js.map +1 -0
  7. package/dist/authorize-shared.d.ts +44 -0
  8. package/dist/authorize-shared.d.ts.map +1 -0
  9. package/dist/authorize-shared.js +134 -0
  10. package/dist/authorize-shared.js.map +1 -0
  11. package/dist/clerk-session.d.ts +52 -0
  12. package/dist/clerk-session.d.ts.map +1 -0
  13. package/dist/clerk-session.js +93 -0
  14. package/dist/clerk-session.js.map +1 -0
  15. package/dist/crypto.d.ts +12 -0
  16. package/dist/crypto.d.ts.map +1 -1
  17. package/dist/crypto.js +18 -0
  18. package/dist/crypto.js.map +1 -1
  19. package/dist/human-path.d.ts +1 -1
  20. package/dist/index.d.ts +13 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +12 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/oidc.d.ts +76 -0
  25. package/dist/oidc.d.ts.map +1 -0
  26. package/dist/oidc.js +83 -0
  27. package/dist/oidc.js.map +1 -0
  28. package/dist/presented-bearer.d.ts +205 -0
  29. package/dist/presented-bearer.d.ts.map +1 -0
  30. package/dist/presented-bearer.js +226 -0
  31. package/dist/presented-bearer.js.map +1 -0
  32. package/dist/tenancy-domain.d.ts +6 -3
  33. package/dist/tenancy-domain.d.ts.map +1 -1
  34. package/dist/tenancy-domain.js +6 -3
  35. package/dist/tenancy-domain.js.map +1 -1
  36. package/dist/token-exchange.d.ts +53 -0
  37. package/dist/token-exchange.d.ts.map +1 -0
  38. package/dist/token-exchange.js +60 -0
  39. package/dist/token-exchange.js.map +1 -0
  40. package/package.json +21 -1
package/dist/crypto.js CHANGED
@@ -29,4 +29,22 @@ export async function timingSafeEqual(a, b) {
29
29
  }
30
30
  return diff === 0;
31
31
  }
32
+ /**
33
+ * SHA-256 of a UTF-8 string as a lower-case hex digest.
34
+ *
35
+ * Exported because a consumer that STORES a bearer row must produce the digest
36
+ * the same way the resolver will recompute it. Without this, each consumer
37
+ * hashes its own way and the mismatch surfaces as "correct token refused".
38
+ *
39
+ * @security Hash the token once, store the digest, discard the token. This
40
+ * function is one-way; it is not a substitute for a comparison — compare
41
+ * digests with `timingSafeEqual`, never with `===`.
42
+ */
43
+ export async function sha256Hex(input) {
44
+ const data = new TextEncoder().encode(input);
45
+ const digest = await crypto.subtle.digest("SHA-256", data);
46
+ return Array.from(new Uint8Array(digest))
47
+ .map((byte) => byte.toString(16).padStart(2, "0"))
48
+ .join("");
49
+ }
32
50
  //# sourceMappingURL=crypto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,CAAa,EACb,CAAa;IAEb,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,6EAA6E;QAC7E,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACxC,KAAK,EACL,WAAsC,EACtC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;QACF,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAgC,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,CAAa,EACb,CAAa;IAEb,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,6EAA6E;QAC7E,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACxC,KAAK,EACL,WAAsC,EACtC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;QACF,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAgC,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAa;IAC3C,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;SACtC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SACjD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
@@ -20,10 +20,10 @@
20
20
  */
21
21
  import { z } from "zod";
22
22
  export declare const humanAccountRoleSchema: z.ZodEnum<{
23
+ client: "client";
23
24
  owner: "owner";
24
25
  admin: "admin";
25
26
  member: "member";
26
- client: "client";
27
27
  }>;
28
28
  export type HumanAccountRole = z.infer<typeof humanAccountRoleSchema>;
29
29
  /**
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * tenant resolution, membership requirement, row filtering and bearer
6
6
  * validation.
7
7
  */
8
- export { timingSafeEqual } from "./crypto.js";
8
+ export { timingSafeEqual, sha256Hex } from "./crypto.js";
9
9
  export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, LEGACY_WILDCARD_CTX, type ScopeFilterable, } from "./scope-filter.js";
10
10
  export { validateMasterBearer } from "./bearer-validation.js";
11
11
  export type { OAuthCtx, ScopeProfile, NamespacePrefix, FromAllowListEntry, ValidateMasterBearerResult, } from "./types.js";
@@ -15,4 +15,16 @@ export { requireTenantId, resolveTenantIdOrAbsent } from "./org-guard.js";
15
15
  export type { TenantAbsence, TenantAbsenceReason, TenantResolution, SessionIdentity, TenantSource, DeploymentMode, } from "./org-guard.js";
16
16
  export { humanAccountRoleSchema, normalizeVerifiedHumanSession, requireHumanRole, resolveHumanRoleOrRefusal, } from "./human-path.js";
17
17
  export type { RequiredHumanRole, RoleRefusal, RoleRefusalReason, RoleResolution, HumanAccountRole, ClerkSessionLike, ResolvedHumanIdentity, } from "./human-path.js";
18
+ export { validatePresentedBearer, requireAgentScopedIdentity, storedBearerRowSchema, } from "./presented-bearer.js";
19
+ export type { StoredBearerRow, AgentPresence, ResolvedBearerIdentity, AgentScopedIdentity, ValidatePresentedBearerResult, RequireAgentScopedResult, PresentedBearerDeps, PresentedBearerInternalReason, } from "./presented-bearer.js";
20
+ export { startAuthorize, resumeAuthorize, } from "./authorize-flow.js";
21
+ export type { AuthorizeClient, ClerkOrgMembership, AuthorizationCodeRecord, AuthorizationCodeStore, ConsumeCodeResult, AuthorizeConfig, AuthorizeDeps, AuthorizeOutcome, OrgPickerModel, ResumeInput, } from "./authorize-flow.js";
22
+ export { exchangeAuthorizationCode } from "./token-exchange.js";
23
+ export type { AuthorizedTokenClaims, ExchangeInput, ExchangeDeps, ExchangeResult, } from "./token-exchange.js";
24
+ export { verifyClerkSessionToken } from "./clerk-session.js";
25
+ export type { ClerkJwk, ClerkJwks, ClerkSessionVerifierConfig, VerifiedClerkSession, VerifyClerkSessionResult, } from "./clerk-session.js";
26
+ export { buildDiscoveryDocument, buildUserInfo } from "./oidc.js";
27
+ export type { DiscoveryConfig, DiscoveryDocument, DiscoveryResult, ClerkUserLike, UserInfo, UserInfoResult, } from "./oidc.js";
28
+ export { oauthErrorFor, pkceChallengeFromVerifier } from "./authorize-shared.js";
29
+ export type { AuthorizeRefusal, AuthorizeRefusalReason, } from "./authorize-shared.js";
18
30
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,eAAe,EACf,aAAa,EACb,sBAAsB,EACtB,mBAAmB,EACnB,6BAA6B,EAC7B,wBAAwB,EACxB,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EACL,cAAc,EACd,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EACV,qBAAqB,EACrB,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EACV,QAAQ,EACR,SAAS,EACT,0BAA0B,EAC1B,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAClE,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,QAAQ,EACR,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EACV,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * tenant resolution, membership requirement, row filtering and bearer
6
6
  * validation.
7
7
  */
8
- export { timingSafeEqual } from "./crypto.js";
8
+ export { timingSafeEqual, sha256Hex } from "./crypto.js";
9
9
  export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, LEGACY_WILDCARD_CTX, } from "./scope-filter.js";
10
10
  export { validateMasterBearer } from "./bearer-validation.js";
11
11
  // 0.2.0 — Domain-tenancy layer (additive)
@@ -14,4 +14,15 @@ export { workspaceSchema, workspaceMemberSchema, workspaceRoleSchema, tenantCont
14
14
  export { requireTenantId, resolveTenantIdOrAbsent } from "./org-guard.js";
15
15
  // 0.4.0 — Human-path resolver (Clerk-shaped session -> { tenant, subject, role })
16
16
  export { humanAccountRoleSchema, normalizeVerifiedHumanSession, requireHumanRole, resolveHumanRoleOrRefusal, } from "./human-path.js";
17
+ // Presented-bearer resolution — the opaque-token trust boundary for a
18
+ // NON-MASTER credential. The package's single answer to "who is calling"
19
+ // for a presented bearer; consumers must not keep a local variant.
20
+ export { validatePresentedBearer, requireAgentScopedIdentity, storedBearerRowSchema, } from "./presented-bearer.js";
21
+ // 0.8.0 — Authorize a PERSON: Clerk session -> verified organisation -> a code
22
+ // bound to both; the exchange that redeems it; the OIDC documents.
23
+ export { startAuthorize, resumeAuthorize, } from "./authorize-flow.js";
24
+ export { exchangeAuthorizationCode } from "./token-exchange.js";
25
+ export { verifyClerkSessionToken } from "./clerk-session.js";
26
+ export { buildDiscoveryDocument, buildUserInfo } from "./oidc.js";
27
+ export { oauthErrorFor, pkceChallengeFromVerifier } from "./authorize-shared.js";
17
28
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAS9D,0CAA0C;AAC1C,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAU7B,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAU1E,kFAAkF;AAClF,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAS9D,0CAA0C;AAC1C,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAU7B,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAU1E,kFAAkF;AAClF,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAWzB,sEAAsE;AACtE,yEAAyE;AACzE,mEAAmE;AACnE,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAY/B,+EAA+E;AAC/E,mEAAmE;AACnE,OAAO,EACL,cAAc,EACd,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAa7B,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAOhE,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAQ7D,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AASlE,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/oidc.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { type AuthorizeRefusal } from "./authorize-shared.js";
2
+ import type { AuthorizedTokenClaims } from "./token-exchange.js";
3
+ /** Inputs of the discovery document. Every endpoint is an absolute URL. */
4
+ export interface DiscoveryConfig {
5
+ issuer: string;
6
+ authorizationEndpoint: string;
7
+ tokenEndpoint: string;
8
+ jwksUri: string;
9
+ userinfoEndpoint: string;
10
+ registrationEndpoint?: string;
11
+ scopesSupported?: readonly string[];
12
+ }
13
+ export interface DiscoveryDocument {
14
+ issuer: string;
15
+ authorization_endpoint: string;
16
+ token_endpoint: string;
17
+ jwks_uri: string;
18
+ userinfo_endpoint: string;
19
+ registration_endpoint?: string;
20
+ response_types_supported: string[];
21
+ response_modes_supported: string[];
22
+ grant_types_supported: string[];
23
+ subject_types_supported: string[];
24
+ scopes_supported: string[];
25
+ code_challenge_methods_supported: string[];
26
+ claims_supported: string[];
27
+ }
28
+ export type DiscoveryResult = {
29
+ ok: true;
30
+ document: DiscoveryDocument;
31
+ } | {
32
+ ok: false;
33
+ refusal: AuthorizeRefusal;
34
+ };
35
+ /**
36
+ * Builds the discovery document (OAuth authorization-server metadata with the
37
+ * OIDC fields this package honours). It advertises only what is implemented:
38
+ * the code flow, PKCE, the `authorization_code` grant, UserInfo. It does NOT
39
+ * advertise id_tokens, refresh tokens or client-authentication methods; the
40
+ * consumer may add those keys to the returned object if it implements them. The issuer must be an https URL with no query or fragment (plain
41
+ * http only for loopback); every endpoint must be an absolute URL.
42
+ */
43
+ export declare function buildDiscoveryDocument(config: DiscoveryConfig): DiscoveryResult;
44
+ /** The subset of a Clerk user the UserInfo helper reads. */
45
+ export interface ClerkUserLike {
46
+ id: string;
47
+ primaryEmailAddressId?: string | null;
48
+ emailAddresses: readonly {
49
+ id: string;
50
+ emailAddress: string;
51
+ verification?: {
52
+ status?: string | null;
53
+ } | null;
54
+ }[];
55
+ }
56
+ export interface UserInfo {
57
+ sub: string;
58
+ email?: string;
59
+ email_verified?: boolean;
60
+ }
61
+ export type UserInfoResult = {
62
+ ok: true;
63
+ userinfo: UserInfo;
64
+ } | {
65
+ ok: false;
66
+ refusal: AuthorizeRefusal;
67
+ };
68
+ /**
69
+ * Builds the UserInfo response from the Clerk user and the claims of the
70
+ * presented token. The token must carry `openid`; the user must be the token's
71
+ * subject; `email` and `email_verified` appear only when the token also
72
+ * carries `email` AND the user has a primary address. `email_verified` is true
73
+ * only for a Clerk verification status of exactly `verified`.
74
+ */
75
+ export declare function buildUserInfo(claims: Pick<AuthorizedTokenClaims, "sub" | "scope">, user: ClerkUserLike): UserInfoResult;
76
+ //# sourceMappingURL=oidc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc.d.ts","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAGtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,gCAAgC,EAAE,MAAM,EAAE,CAAC;IAC3C,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAgB7C;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAgC/E;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,cAAc,EAAE,SAAS;QACvB,EAAE,EAAE,MAAM,CAAC;QACX,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;KAClD,EAAE,CAAC;CACL;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE7C;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,GAAG,OAAO,CAAC,EACpD,IAAI,EAAE,aAAa,GAClB,cAAc,CAmBhB"}
package/dist/oidc.js ADDED
@@ -0,0 +1,83 @@
1
+ import { PKCE_METHOD, refusal, } from "./authorize-shared.js";
2
+ function isAbsoluteUrl(value, requireHttps) {
3
+ try {
4
+ const u = new URL(value);
5
+ if (u.protocol === "https:")
6
+ return true;
7
+ return (!requireHttps &&
8
+ u.protocol === "http:" &&
9
+ (u.hostname === "localhost" || u.hostname === "127.0.0.1"));
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ /**
16
+ * Builds the discovery document (OAuth authorization-server metadata with the
17
+ * OIDC fields this package honours). It advertises only what is implemented:
18
+ * the code flow, PKCE, the `authorization_code` grant, UserInfo. It does NOT
19
+ * advertise id_tokens, refresh tokens or client-authentication methods; the
20
+ * consumer may add those keys to the returned object if it implements them. The issuer must be an https URL with no query or fragment (plain
21
+ * http only for loopback); every endpoint must be an absolute URL.
22
+ */
23
+ export function buildDiscoveryDocument(config) {
24
+ const bad = { ok: false, refusal: refusal("issuer-invalid") };
25
+ if (!isAbsoluteUrl(config.issuer, true))
26
+ return bad;
27
+ const issuer = new URL(config.issuer);
28
+ if (issuer.search !== "" || issuer.hash !== "")
29
+ return bad;
30
+ const endpoints = [
31
+ config.authorizationEndpoint,
32
+ config.tokenEndpoint,
33
+ config.jwksUri,
34
+ config.userinfoEndpoint,
35
+ ...(config.registrationEndpoint ? [config.registrationEndpoint] : []),
36
+ ];
37
+ if (!endpoints.every((e) => isAbsoluteUrl(e, true)))
38
+ return bad;
39
+ const document = {
40
+ issuer: config.issuer,
41
+ authorization_endpoint: config.authorizationEndpoint,
42
+ token_endpoint: config.tokenEndpoint,
43
+ jwks_uri: config.jwksUri,
44
+ userinfo_endpoint: config.userinfoEndpoint,
45
+ response_types_supported: ["code"],
46
+ response_modes_supported: ["query"],
47
+ grant_types_supported: ["authorization_code"],
48
+ subject_types_supported: ["public"],
49
+ scopes_supported: [...(config.scopesSupported ?? ["openid", "email"])],
50
+ code_challenge_methods_supported: [PKCE_METHOD],
51
+ claims_supported: ["sub", "email", "email_verified"],
52
+ };
53
+ if (config.registrationEndpoint) {
54
+ document.registration_endpoint = config.registrationEndpoint;
55
+ }
56
+ return { ok: true, document };
57
+ }
58
+ /**
59
+ * Builds the UserInfo response from the Clerk user and the claims of the
60
+ * presented token. The token must carry `openid`; the user must be the token's
61
+ * subject; `email` and `email_verified` appear only when the token also
62
+ * carries `email` AND the user has a primary address. `email_verified` is true
63
+ * only for a Clerk verification status of exactly `verified`.
64
+ */
65
+ export function buildUserInfo(claims, user) {
66
+ const scopes = claims.scope.split(/\s+/);
67
+ if (!scopes.includes("openid")) {
68
+ return { ok: false, refusal: refusal("openid-scope-required") };
69
+ }
70
+ if (user.id !== claims.sub) {
71
+ return { ok: false, refusal: refusal("subject-mismatch") };
72
+ }
73
+ const userinfo = { sub: user.id };
74
+ if (scopes.includes("email")) {
75
+ const primary = user.emailAddresses.find((a) => a.id === user.primaryEmailAddressId);
76
+ if (primary) {
77
+ userinfo.email = primary.emailAddress;
78
+ userinfo.email_verified = primary.verification?.status === "verified";
79
+ }
80
+ }
81
+ return { ok: true, userinfo };
82
+ }
83
+ //# sourceMappingURL=oidc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc.js","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Presented-bearer resolution — the opaque-token trust boundary.
3
+ * (See package.json "version" for the release this shipped in.)
4
+ *
5
+ * Closes the gap between the two credential paths the package already had:
6
+ *
7
+ * - `validateMasterBearer` covers the MASTER credential only (one configured
8
+ * secret, no datastore, no identity beyond "it was the master").
9
+ * - `decodeUnverifiedBearer` says in its own name that it verifies nothing;
10
+ * its payload is attacker-controlled and is not a trust boundary.
11
+ *
12
+ * Between them there was no function taking a PRESENTED, NON-MASTER bearer,
13
+ * hashing it, looking the row up and returning a resolved identity or a typed
14
+ * refusal. Consumers therefore wrote that primitive locally — a second
15
+ * authority answering "who is calling", which is exactly what must not exist.
16
+ * This module is the single one.
17
+ *
18
+ * DIVISION OF LABOUR. The package cannot query a datastore, so the row lookup
19
+ * arrives as a caller-supplied callback. Everything that can be got wrong
20
+ * stays here: the header parse, the hashing, the constant-time digest compare,
21
+ * revocation, expiry, row-shape validation, and the SHAPE of the refusal. The
22
+ * caller supplies one indexed read and nothing else.
23
+ *
24
+ * Errors are coarse-grained on purpose — callers should not surface the exact
25
+ * reason to remote clients beyond a generic 401/403. The vocabulary is
26
+ * `validateMasterBearer`'s, unchanged: "missing", "malformed", "mismatch".
27
+ *
28
+ * @security The callback is invoked with the SHA-256 HEX DIGEST of the
29
+ * presented token, never the token. A consumer's datastore query, query log
30
+ * and slow-query log therefore never see a usable secret. The raw token does
31
+ * not appear in any returned value, any refusal, or any logging callback.
32
+ */
33
+ import { z } from "zod";
34
+ /**
35
+ * Shape of the row a consumer's datastore returns for a given digest.
36
+ *
37
+ * `agentId` ABSENT (undefined or null) means an ORGANIZATION-WIDE token: it
38
+ * carries a tenant but no agent identity. Such a row is valid and resolves
39
+ * successfully; what it may NOT do is stand in for an agent. See
40
+ * `requireAgentScopedIdentity`.
41
+ *
42
+ * `secretHash` is the digest as stored by the consumer. It is re-compared here
43
+ * against the digest computed from the presented token, in constant time, so a
44
+ * lookup that performs a loose match (prefix, LIKE, case-folded index) or that
45
+ * returns the wrong row cannot become an authentication.
46
+ */
47
+ export declare const storedBearerRowSchema: z.ZodObject<{
48
+ tenantId: z.ZodString;
49
+ secretHash: z.ZodString;
50
+ agentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
51
+ revoked: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
52
+ expiresAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
53
+ scopes: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
54
+ }, z.core.$strip>;
55
+ export type StoredBearerRow = z.infer<typeof storedBearerRowSchema>;
56
+ /**
57
+ * The agent, present or explicitly absent. A DISCRIMINATED UNION rather than a
58
+ * nullable `agentId?: string`, because a nullable field is a duty to remember
59
+ * and this one was being forgotten: `identity.agent.agentId` does not type-check
60
+ * until the caller has narrowed on `present`.
61
+ */
62
+ export type AgentPresence = {
63
+ present: true;
64
+ agentId: string;
65
+ } | {
66
+ present: false;
67
+ code: "AGENT_ABSENT";
68
+ reason: "organization-wide-token";
69
+ };
70
+ /**
71
+ * A resolved, verified caller. Carries the tenant always, and the agent as a
72
+ * presence-or-absence. Carries NO secret and no digest.
73
+ */
74
+ export type ResolvedBearerIdentity = {
75
+ tenantId: string;
76
+ agent: AgentPresence;
77
+ scopes: readonly string[];
78
+ };
79
+ /**
80
+ * Brand for `AgentScopedIdentity`. Declared and NOT exported, so the branded
81
+ * type cannot be constructed anywhere outside this module: a per-agent surface
82
+ * that types its parameter as `AgentScopedIdentity` can only be reached through
83
+ * `requireAgentScopedIdentity`. That is what makes the organization-wide-token
84
+ * refusal structural rather than a convention a caller may skip.
85
+ */
86
+ declare const agentScopedBrand: unique symbol;
87
+ /**
88
+ * A resolved caller that HAS an agent. `agentId` is a plain non-optional
89
+ * string, so per-agent code needs no narrowing and has nothing to forget.
90
+ */
91
+ export type AgentScopedIdentity = {
92
+ readonly [agentScopedBrand]: true;
93
+ tenantId: string;
94
+ agentId: string;
95
+ scopes: readonly string[];
96
+ };
97
+ /**
98
+ * Result of `validatePresentedBearer`.
99
+ *
100
+ * - `ok: true` → the row was found, its digest matched in constant time, it
101
+ * is neither revoked nor expired, and its shape is valid.
102
+ * - `error: "missing"` → no Authorization header.
103
+ * - `error: "malformed"` → header present but not parseable as
104
+ * `Bearer <non-empty-token>`.
105
+ * - `error: "mismatch"` → the single, COLLAPSED refusal. An unknown digest,
106
+ * a revoked row, an expired row, a row of invalid shape and a row whose
107
+ * stored digest does not match all produce this value and nothing else, so
108
+ * a prober cannot learn from the response whether a token was ever real.
109
+ * - `error: "unavailable"` → the lookup callback itself threw or rejected.
110
+ * Deliberately NOT in the collapsed bucket: a caller must be able to
111
+ * answer 503 rather than 401 when its datastore is down, and an attacker
112
+ * cannot induce this state for one chosen token rather than another, so it
113
+ * discloses nothing about any token. It is a REFUSAL like the others — a
114
+ * lookup failure never falls through to a grant.
115
+ */
116
+ export type ValidatePresentedBearerResult = {
117
+ ok: true;
118
+ identity: ResolvedBearerIdentity;
119
+ error?: undefined;
120
+ } | {
121
+ ok: false;
122
+ error: "missing" | "malformed" | "mismatch" | "unavailable";
123
+ identity?: undefined;
124
+ };
125
+ /**
126
+ * Result of `requireAgentScopedIdentity`. The refusal is distinguishable from
127
+ * `validatePresentedBearer`'s refusals on purpose: the credential here is
128
+ * known-good, and the caller owes the remote client a 403 (wrong surface for
129
+ * this token), not a 401 (bad token).
130
+ */
131
+ export type RequireAgentScopedResult = {
132
+ ok: true;
133
+ identity: AgentScopedIdentity;
134
+ error?: undefined;
135
+ } | {
136
+ ok: false;
137
+ error: "no-agent-on-token";
138
+ identity?: undefined;
139
+ };
140
+ /**
141
+ * Why a refusal happened, for the consumer's own observability. Reported
142
+ * through `deps.onRefusal`; NEVER present in a returned value, because a
143
+ * returned distinction is a returned disclosure.
144
+ *
145
+ * @security Carries no token, no digest and no row. Only a reason word.
146
+ */
147
+ export type PresentedBearerInternalReason = "no-header" | "malformed-header" | "unknown-token" | "revoked-token" | "expired-token" | "row-shape-invalid" | "stored-digest-mismatch" | "lookup-failed";
148
+ export type PresentedBearerDeps = {
149
+ /**
150
+ * Indexed read of one row by the SHA-256 HEX DIGEST of the presented token.
151
+ * Returns the row, or null/undefined when no row carries that digest.
152
+ *
153
+ * @security The argument is a digest, never the token. Do not log it, and do
154
+ * not widen this to accept a raw secret.
155
+ */
156
+ lookupBySecretHash: (sha256HexDigest: string) => Promise<StoredBearerRow | null | undefined>;
157
+ /** Injectable clock. Never `Date.now()` inline, so expiry is testable. */
158
+ now?: () => number;
159
+ /** Observability sink for the collapsed refusals. Must not throw; if it does, the throw is swallowed and the refusal stands. */
160
+ onRefusal?: (reason: PresentedBearerInternalReason) => void;
161
+ };
162
+ /**
163
+ * Resolve an `Authorization: Bearer <token>` header presented by a NON-MASTER
164
+ * caller into a verified identity, or into a typed refusal.
165
+ *
166
+ * Order of operations, and why:
167
+ * 1. Parse the header. A missing or malformed header is refused WITHOUT
168
+ * calling the lookup, so a garbage request costs no datastore read.
169
+ * 2. Hash the token to a SHA-256 hex digest. The raw token goes no further.
170
+ * 3. Look the row up BY THAT DIGEST.
171
+ * 4. Re-compare the row's stored digest against the computed one with
172
+ * `timingSafeEqual` — never `!==` on a secret or a digest.
173
+ * 5. Validate the row's shape, then revocation, then expiry.
174
+ * 6. Resolve the tenant, and the agent as a presence or an explicit absence.
175
+ *
176
+ * Steps 3–5 all refuse with the SAME frozen value. A right is never granted by
177
+ * absence or by failure: there is no branch that returns `ok: true` without
178
+ * having passed every check above.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * const result = await validatePresentedBearer(req.headers.get("authorization"), {
183
+ * lookupBySecretHash: (digest) => db.tokens.byDigest(digest),
184
+ * });
185
+ * if (!result.ok) return respond(result.error === "unavailable" ? 503 : 401);
186
+ * const tenantId = result.identity.tenantId;
187
+ * ```
188
+ */
189
+ export declare function validatePresentedBearer(authHeader: string | null | undefined, deps: PresentedBearerDeps): Promise<ValidatePresentedBearerResult>;
190
+ /**
191
+ * Narrow a resolved identity to a PER-AGENT surface, refusing when the token
192
+ * carries no agent.
193
+ *
194
+ * An organization-wide token is a legitimate credential that is simply not an
195
+ * agent; treating it as one would silently widen it into whichever agent the
196
+ * surface happened to be about. So this is a second, mandatory call, and its
197
+ * output type is branded: a per-agent handler declares `AgentScopedIdentity`
198
+ * and the only way to obtain one is here.
199
+ *
200
+ * Grants nothing on its own — it can only ever narrow an identity
201
+ * `validatePresentedBearer` already resolved.
202
+ */
203
+ export declare function requireAgentScopedIdentity(identity: ResolvedBearerIdentity): RequireAgentScopedResult;
204
+ export {};
205
+ //# sourceMappingURL=presented-bearer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presented-bearer.d.ts","sourceRoot":"","sources":["../src/presented-bearer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB;;;;;;;iBAOhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAMpE;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAEhF;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF;;;;;;GAMG;AACH,OAAO,CAAC,MAAM,gBAAgB,EAAE,OAAO,MAAM,CAAC;AAE9C;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC;AAMF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,6BAA6B,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,sBAAsB,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GACjE;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,aAAa,CAAC;IAC5D,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEN;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,mBAAmB,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GAC9D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AAMpE;;;;;;GAMG;AACH,MAAM,MAAM,6BAA6B,GACrC,WAAW,GACX,kBAAkB,GAClB,eAAe,GACf,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,wBAAwB,GACxB,eAAe,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;;OAMG;IACH,kBAAkB,EAAE,CAClB,eAAe,EAAE,MAAM,KACpB,OAAO,CAAC,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACjD,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,gIAAgI;IAChI,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,6BAA6B,KAAK,IAAI,CAAC;CAC7D,CAAC;AAyDF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACrC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,6BAA6B,CAAC,CAkFxC;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,sBAAsB,GAC/B,wBAAwB,CAY1B"}