@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.
- package/CHANGELOG.md +121 -0
- package/README.md +147 -0
- package/dist/authorize-flow.d.ts +169 -0
- package/dist/authorize-flow.d.ts.map +1 -0
- package/dist/authorize-flow.js +262 -0
- package/dist/authorize-flow.js.map +1 -0
- package/dist/authorize-shared.d.ts +44 -0
- package/dist/authorize-shared.d.ts.map +1 -0
- package/dist/authorize-shared.js +134 -0
- package/dist/authorize-shared.js.map +1 -0
- package/dist/clerk-session.d.ts +52 -0
- package/dist/clerk-session.d.ts.map +1 -0
- package/dist/clerk-session.js +93 -0
- package/dist/clerk-session.js.map +1 -0
- package/dist/crypto.d.ts +12 -0
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +18 -0
- package/dist/crypto.js.map +1 -1
- package/dist/human-path.d.ts +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/oidc.d.ts +76 -0
- package/dist/oidc.d.ts.map +1 -0
- package/dist/oidc.js +83 -0
- package/dist/oidc.js.map +1 -0
- package/dist/presented-bearer.d.ts +205 -0
- package/dist/presented-bearer.d.ts.map +1 -0
- package/dist/presented-bearer.js +226 -0
- package/dist/presented-bearer.js.map +1 -0
- package/dist/tenancy-domain.d.ts +6 -3
- package/dist/tenancy-domain.d.ts.map +1 -1
- package/dist/tenancy-domain.js +6 -3
- package/dist/tenancy-domain.js.map +1 -1
- package/dist/token-exchange.d.ts +53 -0
- package/dist/token-exchange.d.ts.map +1 -0
- package/dist/token-exchange.js +60 -0
- package/dist/token-exchange.js.map +1 -0
- 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
|
package/dist/crypto.js.map
CHANGED
|
@@ -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"}
|
package/dist/human-path.d.ts
CHANGED
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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
|
package/dist/oidc.js.map
ADDED
|
@@ -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"}
|