@vantageos/cloud-identity 0.7.0 → 0.9.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/dist/index.d.ts CHANGED
@@ -17,4 +17,16 @@ export { humanAccountRoleSchema, normalizeVerifiedHumanSession, requireHumanRole
17
17
  export type { RequiredHumanRole, RoleRefusal, RoleRefusalReason, RoleResolution, HumanAccountRole, ClerkSessionLike, ResolvedHumanIdentity, } from "./human-path.js";
18
18
  export { validatePresentedBearer, requireAgentScopedIdentity, storedBearerRowSchema, } from "./presented-bearer.js";
19
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";
30
+ export { PERSON_ACTOR_PREFIX, PersonRefusalError, personActorName, isPersonActorName, personTokenRecordSchema, resolvePersonPrincipal, resolvePersonActingName, checkPersonCallShape, resolvePersonTenantAccess, resolveWriterRole, requireWriterRole, } from "./person-principal.js";
31
+ export type { PersonRefusal, PersonRefusalCode, PersonRefusalReason, PersonTokenRecord, PersonPrincipal, OrganisationState, PersonPrincipalDeps, PersonPrincipalResult, PersonActingNameResult, PersonAccessResult, } from "./person-principal.js";
20
32
  //# 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,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -18,4 +18,15 @@ export { humanAccountRoleSchema, normalizeVerifiedHumanSession, requireHumanRole
18
18
  // NON-MASTER credential. The package's single answer to "who is calling"
19
19
  // for a presented bearer; consumers must not keep a local variant.
20
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";
28
+ // 0.9.0 — The person principal: a verified sign-in (person + organisation) as
29
+ // pure data, the own-name actor rule, the tenant compare and the writer-role
30
+ // assertion. Every consumer wires a person the same way.
31
+ export { PERSON_ACTOR_PREFIX, PersonRefusalError, personActorName, isPersonActorName, personTokenRecordSchema, resolvePersonPrincipal, resolvePersonActingName, checkPersonCallShape, resolvePersonTenantAccess, resolveWriterRole, requireWriterRole, } from "./person-principal.js";
21
32
  //# 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,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"}
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;AAMjF,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,EACpB,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,GAClB,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":"AAAA,OAAO,EAEL,WAAW,EACX,OAAO,GACR,MAAM,uBAAuB,CAAC;AAkC/B,SAAS,aAAa,CAAC,KAAa,EAAE,YAAqB;IACzD,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACzC,OAAO,CACL,CAAC,YAAY;YACb,CAAC,CAAC,QAAQ,KAAK,OAAO;YACtB,CAAC,CAAC,CAAC,QAAQ,KAAK,WAAW,IAAI,CAAC,CAAC,QAAQ,KAAK,WAAW,CAAC,CAC3D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAuB;IAC5D,MAAM,GAAG,GAAoB,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;IAC/E,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE;QAAE,OAAO,GAAG,CAAC;IAC3D,MAAM,SAAS,GAAG;QAChB,MAAM,CAAC,qBAAqB;QAC5B,MAAM,CAAC,aAAa;QACpB,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,gBAAgB;QACvB,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KACtE,CAAC;IACF,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC;IAEhE,MAAM,QAAQ,GAAsB;QAClC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,sBAAsB,EAAE,MAAM,CAAC,qBAAqB;QACpD,cAAc,EAAE,MAAM,CAAC,aAAa;QACpC,QAAQ,EAAE,MAAM,CAAC,OAAO;QACxB,iBAAiB,EAAE,MAAM,CAAC,gBAAgB;QAC1C,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,wBAAwB,EAAE,CAAC,OAAO,CAAC;QACnC,qBAAqB,EAAE,CAAC,oBAAoB,CAAC;QAC7C,uBAAuB,EAAE,CAAC,QAAQ,CAAC;QACnC,gBAAgB,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;QACtE,gCAAgC,EAAE,CAAC,WAAW,CAAC;QAC/C,gBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,gBAAgB,CAAC;KACrD,CAAC;IACF,IAAI,MAAM,CAAC,oBAAoB,EAAE,CAAC;QAChC,QAAQ,CAAC,qBAAqB,GAAG,MAAM,CAAC,oBAAoB,CAAC;IAC/D,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC;AAuBD;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAoD,EACpD,IAAmB;IAEnB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,uBAAuB,CAAC,EAAE,CAAC;IAClE,CAAC;IACD,IAAI,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;IAC7D,CAAC;IACD,MAAM,QAAQ,GAAa,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;IAC5C,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CACtC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,qBAAqB,CAC3C,CAAC;QACF,IAAI,OAAO,EAAE,CAAC;YACZ,QAAQ,CAAC,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC;YACtC,QAAQ,CAAC,cAAc,GAAG,OAAO,CAAC,YAAY,EAAE,MAAM,KAAK,UAAU,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * @vantageos/cloud-identity 0.9.0 — the person principal.
3
+ *
4
+ * A PERSON signed in through an OAuth connector (Claude, ChatGPT, any MCP
5
+ * client) is not an agent. This module is the single, framework-agnostic
6
+ * answer to four questions a consumer must not re-implement:
7
+ *
8
+ * 1. Who is the person, and in which organisation?
9
+ * `resolvePersonPrincipal` — from the record of an access token that the
10
+ * consumer already looked up by its digest. Never from an argument.
11
+ * 2. Under what name does the person act?
12
+ * `resolvePersonActingName` / `checkPersonCallShape` — its OWN name,
13
+ * "user:<subject>", taken from the principal. An agent name needs that
14
+ * agent's own verified credential; another user's name is refused.
15
+ * 3. Is a row in the person's organisation?
16
+ * `resolvePersonTenantAccess` — a strict equality on the organisation.
17
+ * 4. May the person write?
18
+ * `resolveWriterRole` / `requireWriterRole` — the verified role must be on
19
+ * the writer allowlist the consumer holds as data. Fail closed.
20
+ *
21
+ * Pure functions over plain inputs: no Clerk SDK, no database, no network, no
22
+ * master or bypass flag anywhere in the input. The consumer performs the
23
+ * lookups (the token row, the organisation row, the writer list) and passes the
24
+ * results in; this module only decides. Every refusal is a typed value carrying
25
+ * its own code, reason and door, so a caller can branch on content, and an
26
+ * absence (no organisation, no role, no list) is a refusal, never a default.
27
+ *
28
+ * @security This module verifies NOTHING about the credential itself. The
29
+ * token record must come from a lookup of the bearer the person presented, made
30
+ * by the consumer's trusted transport layer. Passing a client-supplied object
31
+ * makes that object the source of subject, organisation and role.
32
+ */
33
+ import { z } from "zod";
34
+ export type PersonRefusalCode = "RBAC_DENIED" | "PERSON_ACTS_AS_ITSELF" | "AGENT_CREDENTIAL_REQUIRED" | "AGENT_IDENTITY_MISMATCH";
35
+ export type PersonRefusalReason = "person-token-not-live" | "not-a-person-token" | "person-token-no-org" | "org-not-active" | "person-acts-as-itself" | "agent-proof-on-person-path" | "agent-credential-required" | "agent-identity-mismatch" | "other-organisation" | "role-not-writer";
36
+ /** The typed refusal. A non-empty object carrying its own code and door. */
37
+ export type PersonRefusal = {
38
+ code: PersonRefusalCode;
39
+ reason: PersonRefusalReason;
40
+ /** The consumer's own name for the entry point that refused. */
41
+ door: string;
42
+ /** One sentence, safe to show to the caller. Never a credential. */
43
+ detail: string;
44
+ /** The acting name the caller typed, when the refusal is about a name. */
45
+ claimed?: string;
46
+ /** The person's own actor name, when the refusal is about a name. */
47
+ self?: string;
48
+ /** The verified role, when the refusal is about a role (null: none). */
49
+ role?: string | null;
50
+ /** The organisation the decision was taken in, when it applies. */
51
+ orgSlug?: string | null;
52
+ };
53
+ /** Thrown by the throwing forms. Carries the typed refusal untouched. */
54
+ export declare class PersonRefusalError extends Error {
55
+ readonly refusal: PersonRefusal;
56
+ constructor(refusal: PersonRefusal);
57
+ }
58
+ /** How a person is recorded as "who did it": "user:<verified subject>". */
59
+ export declare const PERSON_ACTOR_PREFIX = "user:";
60
+ /** The actor name of a verified subject. */
61
+ export declare function personActorName(subject: string): string;
62
+ /**
63
+ * True when `name` is spelled like a person's actor name. Such a name is
64
+ * RESERVED: a consumer refuses it at agent registration, so a stored actor
65
+ * "user:..." can only have been written from a verified subject.
66
+ */
67
+ export declare function isPersonActorName(name: string): boolean;
68
+ /**
69
+ * The record of a presented access token, in the package's vocabulary. The
70
+ * consumer maps its own row onto this shape (`subject` is the verified Clerk
71
+ * user id, `orgSlug` the organisation the token is bound to).
72
+ */
73
+ export declare const personTokenRecordSchema: z.ZodObject<{
74
+ principal: z.ZodOptional<z.ZodString>;
75
+ subject: z.ZodString;
76
+ orgSlug: z.ZodOptional<z.ZodString>;
77
+ orgRole: z.ZodOptional<z.ZodString>;
78
+ revokedAt: z.ZodOptional<z.ZodNumber>;
79
+ expiresAt: z.ZodNumber;
80
+ }, z.core.$strip>;
81
+ export type PersonTokenRecord = z.input<typeof personTokenRecordSchema>;
82
+ /** A verified person: who, in which organisation, with which role. */
83
+ export type PersonPrincipal = {
84
+ subject: string;
85
+ orgSlug: string;
86
+ /** Present only when the token carries a verified role. Never defaulted. */
87
+ orgRole?: string;
88
+ /** "user:<subject>" — the name this person acts under. */
89
+ actor: string;
90
+ };
91
+ /** The state of an organisation as the consumer's own mapping holds it. */
92
+ export type OrganisationState = {
93
+ isActive: boolean;
94
+ };
95
+ export type PersonPrincipalDeps<O extends OrganisationState> = {
96
+ /** Epoch milliseconds, supplied by the caller so the decision is pure. */
97
+ now: number;
98
+ /** Resolves the organisation row for a slug; null when unmapped. */
99
+ lookupOrganisation: (orgSlug: string) => Promise<O | null>;
100
+ };
101
+ export type PersonPrincipalResult<O extends OrganisationState> = {
102
+ ok: true;
103
+ principal: PersonPrincipal;
104
+ organisation: O;
105
+ } | {
106
+ ok: false;
107
+ refusal: PersonRefusal;
108
+ };
109
+ /**
110
+ * Resolves the person behind a presented token, or refuses. Checks, in order:
111
+ * the token is live (present, unrevoked, unexpired); it acts for a PERSON;
112
+ * it is bound to an organisation; that organisation exists and is active.
113
+ * The only organisation ever looked up is the token's own.
114
+ */
115
+ export declare function resolvePersonPrincipal<O extends OrganisationState>(token: PersonTokenRecord | null | undefined, deps: PersonPrincipalDeps<O>, door: string): Promise<PersonPrincipalResult<O>>;
116
+ export type PersonActingNameResult = {
117
+ ok: true;
118
+ actingAs: "person";
119
+ actor: string;
120
+ } | {
121
+ ok: true;
122
+ actingAs: "agent";
123
+ actor: string;
124
+ } | {
125
+ ok: false;
126
+ refusal: PersonRefusal;
127
+ };
128
+ /**
129
+ * Decides under which name a person acts. The acting identity comes from the
130
+ * principal, never from the typed name:
131
+ *
132
+ * - no name, or the person's own name -> acts as itself;
133
+ * - another user's name -> PERSON_ACTS_AS_ITSELF;
134
+ * - an agent's name -> only with that agent's own verified
135
+ * credential (`agentCredential`,
136
+ * resolved by the consumer): none ->
137
+ * AGENT_CREDENTIAL_REQUIRED, a
138
+ * different agent -> AGENT_IDENTITY_MISMATCH.
139
+ */
140
+ export declare function resolvePersonActingName(input: {
141
+ /** Only the actor name is read: the rule needs no organisation. */
142
+ principal: Pick<PersonPrincipal, "actor">;
143
+ claimedName?: string | null;
144
+ /** The agent the presented credential resolved to, when one was presented. */
145
+ agentCredential?: {
146
+ agentName: string;
147
+ } | null;
148
+ door: string;
149
+ }): PersonActingNameResult;
150
+ /**
151
+ * The shape of a call that reaches a backend door ON a person's behalf: it
152
+ * carries no acting name (the transport already stripped the person's own) and
153
+ * no agent proof (a person that presented an agent credential acts as that
154
+ * agent, on the agent path). Returns the refusal, or null when the shape is
155
+ * right.
156
+ */
157
+ export declare function checkPersonCallShape(input: {
158
+ door: string;
159
+ actingName?: string;
160
+ agentProof?: boolean;
161
+ }): PersonRefusal | null;
162
+ export type PersonAccessResult = {
163
+ ok: true;
164
+ } | {
165
+ ok: false;
166
+ refusal: PersonRefusal;
167
+ };
168
+ /**
169
+ * A row is reachable only when its organisation stamp EQUALS the person's
170
+ * organisation. An absent or empty stamp never equals a resolved organisation,
171
+ * so an unstamped row is refused.
172
+ */
173
+ export declare function resolvePersonTenantAccess(input: {
174
+ principal: PersonPrincipal;
175
+ rowOrgId: string | null | undefined;
176
+ door: string;
177
+ }): PersonAccessResult;
178
+ type WriterRoleInput = {
179
+ /** The VERIFIED role claim of the token. Absent or null is refused. */
180
+ role: string | null | undefined;
181
+ /** The writer allowlist the consumer holds as data. Empty refuses all. */
182
+ writerRoles: readonly string[];
183
+ door: string;
184
+ orgSlug?: string | null;
185
+ };
186
+ /**
187
+ * Non-throwing writer-role assertion. The role must appear EXACTLY in the
188
+ * allowlist; no hierarchy, no inheritance. An absent role, an absent list and
189
+ * an empty list all refuse: a missing list never means "all roles".
190
+ */
191
+ export declare function resolveWriterRole(input: WriterRoleInput): PersonAccessResult;
192
+ /** Throwing form of {@link resolveWriterRole}; throws `PersonRefusalError`. */
193
+ export declare function requireWriterRole(input: WriterRoleInput): void;
194
+ export {};
195
+ //# sourceMappingURL=person-principal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"person-principal.d.ts","sourceRoot":"","sources":["../src/person-principal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @vantageos/cloud-identity 0.9.0 — the person principal.
3
+ *
4
+ * A PERSON signed in through an OAuth connector (Claude, ChatGPT, any MCP
5
+ * client) is not an agent. This module is the single, framework-agnostic
6
+ * answer to four questions a consumer must not re-implement:
7
+ *
8
+ * 1. Who is the person, and in which organisation?
9
+ * `resolvePersonPrincipal` — from the record of an access token that the
10
+ * consumer already looked up by its digest. Never from an argument.
11
+ * 2. Under what name does the person act?
12
+ * `resolvePersonActingName` / `checkPersonCallShape` — its OWN name,
13
+ * "user:<subject>", taken from the principal. An agent name needs that
14
+ * agent's own verified credential; another user's name is refused.
15
+ * 3. Is a row in the person's organisation?
16
+ * `resolvePersonTenantAccess` — a strict equality on the organisation.
17
+ * 4. May the person write?
18
+ * `resolveWriterRole` / `requireWriterRole` — the verified role must be on
19
+ * the writer allowlist the consumer holds as data. Fail closed.
20
+ *
21
+ * Pure functions over plain inputs: no Clerk SDK, no database, no network, no
22
+ * master or bypass flag anywhere in the input. The consumer performs the
23
+ * lookups (the token row, the organisation row, the writer list) and passes the
24
+ * results in; this module only decides. Every refusal is a typed value carrying
25
+ * its own code, reason and door, so a caller can branch on content, and an
26
+ * absence (no organisation, no role, no list) is a refusal, never a default.
27
+ *
28
+ * @security This module verifies NOTHING about the credential itself. The
29
+ * token record must come from a lookup of the bearer the person presented, made
30
+ * by the consumer's trusted transport layer. Passing a client-supplied object
31
+ * makes that object the source of subject, organisation and role.
32
+ */
33
+ import { z } from "zod";
34
+ /** Thrown by the throwing forms. Carries the typed refusal untouched. */
35
+ export class PersonRefusalError extends Error {
36
+ refusal;
37
+ constructor(refusal) {
38
+ super(`${refusal.code}: ${refusal.detail} (${refusal.reason}, ${refusal.door})`);
39
+ this.name = "PersonRefusalError";
40
+ this.refusal = refusal;
41
+ }
42
+ }
43
+ function refuse(code, reason, door, detail, extra = {}) {
44
+ return { code, reason, door, detail, ...extra };
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Actor name
48
+ // ---------------------------------------------------------------------------
49
+ /** How a person is recorded as "who did it": "user:<verified subject>". */
50
+ export const PERSON_ACTOR_PREFIX = "user:";
51
+ /** NFC, lowercase, trim: the comparison form of an acting name. */
52
+ function normalizeName(name) {
53
+ return name.normalize("NFC").toLowerCase().trim();
54
+ }
55
+ /** The actor name of a verified subject. */
56
+ export function personActorName(subject) {
57
+ return `${PERSON_ACTOR_PREFIX}${subject}`;
58
+ }
59
+ /**
60
+ * True when `name` is spelled like a person's actor name. Such a name is
61
+ * RESERVED: a consumer refuses it at agent registration, so a stored actor
62
+ * "user:..." can only have been written from a verified subject.
63
+ */
64
+ export function isPersonActorName(name) {
65
+ return normalizeName(name).startsWith(PERSON_ACTOR_PREFIX);
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // 1. The principal
69
+ // ---------------------------------------------------------------------------
70
+ /**
71
+ * The record of a presented access token, in the package's vocabulary. The
72
+ * consumer maps its own row onto this shape (`subject` is the verified Clerk
73
+ * user id, `orgSlug` the organisation the token is bound to).
74
+ */
75
+ export const personTokenRecordSchema = z.object({
76
+ principal: z.string().optional(),
77
+ subject: z.string(),
78
+ orgSlug: z.string().optional(),
79
+ orgRole: z.string().optional(),
80
+ revokedAt: z.number().optional(),
81
+ expiresAt: z.number(),
82
+ });
83
+ /**
84
+ * Resolves the person behind a presented token, or refuses. Checks, in order:
85
+ * the token is live (present, unrevoked, unexpired); it acts for a PERSON;
86
+ * it is bound to an organisation; that organisation exists and is active.
87
+ * The only organisation ever looked up is the token's own.
88
+ */
89
+ export async function resolvePersonPrincipal(token, deps, door) {
90
+ const deny = (reason, detail) => ({
91
+ ok: false,
92
+ refusal: refuse("RBAC_DENIED", reason, door, detail),
93
+ });
94
+ const parsed = personTokenRecordSchema.safeParse(token);
95
+ if (!parsed.success ||
96
+ parsed.data.revokedAt !== undefined ||
97
+ parsed.data.expiresAt < deps.now) {
98
+ return deny("person-token-not-live", "verifiedPerson names no live access token");
99
+ }
100
+ const row = parsed.data;
101
+ if (row.principal !== "person" || row.subject === "") {
102
+ return deny("not-a-person-token", "verifiedPerson names a token that does not act for a person");
103
+ }
104
+ const orgSlug = row.orgSlug;
105
+ if (orgSlug === undefined || orgSlug === "") {
106
+ return deny("person-token-no-org", "the person's token is bound to no organisation");
107
+ }
108
+ const organisation = await deps.lookupOrganisation(orgSlug);
109
+ if (organisation === null || !organisation.isActive) {
110
+ return deny("org-not-active", `Org "${orgSlug}" not in client_org_mapping or inactive`);
111
+ }
112
+ return {
113
+ ok: true,
114
+ organisation,
115
+ principal: {
116
+ subject: row.subject,
117
+ orgSlug,
118
+ ...(row.orgRole !== undefined ? { orgRole: row.orgRole } : {}),
119
+ actor: personActorName(row.subject),
120
+ },
121
+ };
122
+ }
123
+ /**
124
+ * Decides under which name a person acts. The acting identity comes from the
125
+ * principal, never from the typed name:
126
+ *
127
+ * - no name, or the person's own name -> acts as itself;
128
+ * - another user's name -> PERSON_ACTS_AS_ITSELF;
129
+ * - an agent's name -> only with that agent's own verified
130
+ * credential (`agentCredential`,
131
+ * resolved by the consumer): none ->
132
+ * AGENT_CREDENTIAL_REQUIRED, a
133
+ * different agent -> AGENT_IDENTITY_MISMATCH.
134
+ */
135
+ export function resolvePersonActingName(input) {
136
+ const { principal, door } = input;
137
+ const claimed = input.claimedName;
138
+ if (claimed === undefined || claimed === null) {
139
+ return { ok: true, actingAs: "person", actor: principal.actor };
140
+ }
141
+ const name = normalizeName(claimed);
142
+ if (name === normalizeName(principal.actor)) {
143
+ return { ok: true, actingAs: "person", actor: principal.actor };
144
+ }
145
+ if (name.startsWith(PERSON_ACTOR_PREFIX)) {
146
+ return {
147
+ ok: false,
148
+ refusal: refuse("PERSON_ACTS_AS_ITSELF", "person-acts-as-itself", door, `a person acts only in its own name; this call names "${claimed}"`, { claimed, self: principal.actor }),
149
+ };
150
+ }
151
+ const credential = input.agentCredential;
152
+ if (credential === undefined || credential === null) {
153
+ return {
154
+ ok: false,
155
+ refusal: refuse("AGENT_CREDENTIAL_REQUIRED", "agent-credential-required", door, `this call names "${claimed}" but carried no credential of that agent`, { claimed, self: principal.actor }),
156
+ };
157
+ }
158
+ if (normalizeName(credential.agentName) !== name) {
159
+ return {
160
+ ok: false,
161
+ refusal: refuse("AGENT_IDENTITY_MISMATCH", "agent-identity-mismatch", door, `this call names "${claimed}" but the presented credential belongs to another agent`, { claimed, self: principal.actor }),
162
+ };
163
+ }
164
+ return { ok: true, actingAs: "agent", actor: credential.agentName };
165
+ }
166
+ /**
167
+ * The shape of a call that reaches a backend door ON a person's behalf: it
168
+ * carries no acting name (the transport already stripped the person's own) and
169
+ * no agent proof (a person that presented an agent credential acts as that
170
+ * agent, on the agent path). Returns the refusal, or null when the shape is
171
+ * right.
172
+ */
173
+ export function checkPersonCallShape(input) {
174
+ if (input.agentProof === true) {
175
+ return refuse("RBAC_DENIED", "agent-proof-on-person-path", input.door, "a person's call carries no agent credential and no verified actor");
176
+ }
177
+ if (input.actingName !== undefined) {
178
+ return refuse("PERSON_ACTS_AS_ITSELF", "person-acts-as-itself", input.door, `a person acts only in its own name; this call also names "${input.actingName}"`, { claimed: input.actingName });
179
+ }
180
+ return null;
181
+ }
182
+ /**
183
+ * A row is reachable only when its organisation stamp EQUALS the person's
184
+ * organisation. An absent or empty stamp never equals a resolved organisation,
185
+ * so an unstamped row is refused.
186
+ */
187
+ export function resolvePersonTenantAccess(input) {
188
+ const { principal, rowOrgId, door } = input;
189
+ if (typeof rowOrgId === "string" && rowOrgId !== "" && rowOrgId === principal.orgSlug) {
190
+ return { ok: true };
191
+ }
192
+ return {
193
+ ok: false,
194
+ refusal: refuse("RBAC_DENIED", "other-organisation", door, "the row is outside the person's organisation (tenant boundary)", { orgSlug: principal.orgSlug }),
195
+ };
196
+ }
197
+ /**
198
+ * Non-throwing writer-role assertion. The role must appear EXACTLY in the
199
+ * allowlist; no hierarchy, no inheritance. An absent role, an absent list and
200
+ * an empty list all refuse: a missing list never means "all roles".
201
+ */
202
+ export function resolveWriterRole(input) {
203
+ const role = input.role ?? null;
204
+ if (role !== null && input.writerRoles.includes(role))
205
+ return { ok: true };
206
+ return {
207
+ ok: false,
208
+ refusal: refuse("RBAC_DENIED", "role-not-writer", input.door, "member role is not a writer role", { role, orgSlug: input.orgSlug ?? null }),
209
+ };
210
+ }
211
+ /** Throwing form of {@link resolveWriterRole}; throws `PersonRefusalError`. */
212
+ export function requireWriterRole(input) {
213
+ const r = resolveWriterRole(input);
214
+ if (!r.ok)
215
+ throw new PersonRefusalError(r.refusal);
216
+ }
217
+ //# sourceMappingURL=person-principal.js.map