@lunora/cloudflare-access 1.0.0-alpha.100 → 1.0.0-alpha.102

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/admin.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AccessClaims, a as AccessJwtFallbackOptions, E as ExecutionContextLike } from "./packem_shared/types.d-D30o96f7.mjs";
1
+ import { A as AccessClaims, a as AccessJwtFallbackOptions, E as ExecutionContextLike } from "./packem_shared/types.d-Dp399TMk.mjs";
2
2
  import 'jose';
3
3
  /** Options for {@link accessAdminGate}; extends {@link AccessJwtFallbackOptions}. */
4
4
  interface AccessAdminGateOptions extends AccessJwtFallbackOptions {
package/dist/admin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AccessClaims, a as AccessJwtFallbackOptions, E as ExecutionContextLike } from "./packem_shared/types.d-D30o96f7.js";
1
+ import { A as AccessClaims, a as AccessJwtFallbackOptions, E as ExecutionContextLike } from "./packem_shared/types.d-Dp399TMk.js";
2
2
  import 'jose';
3
3
  /** Options for {@link accessAdminGate}; extends {@link AccessJwtFallbackOptions}. */
4
4
  interface AccessAdminGateOptions extends AccessJwtFallbackOptions {
@@ -1,6 +1,25 @@
1
- import { M as Middleware } from "./packem_shared/index.d-D7R8k7yf.mjs";
2
- import { A as AccessClaims } from "./packem_shared/types.d-D30o96f7.mjs";
1
+ import { A as AccessClaims } from "./packem_shared/types.d-Dp399TMk.mjs";
3
2
  import 'jose';
3
+ /**
4
+ * `next()` advances the middleware chain. Called with no argument it forwards
5
+ * the current context unchanged; called with `{ ctx }` it shallow-merges the
6
+ * extension, and the result type reflects the widened context.
7
+ */
8
+ interface MiddlewareNext<ContextIn> {
9
+ (): Promise<ContextIn>;
10
+ <Extension extends Record<string, unknown>>(options: {
11
+ ctx: Extension;
12
+ }): Promise<ContextIn & Extension>;
13
+ }
14
+ /**
15
+ * A middleware receives the current context and a `next` continuation. Its
16
+ * return type becomes the builder's new context, so `return next({ ctx })`
17
+ * propagates the extension into every downstream `.use()` and the handler.
18
+ */
19
+ type Middleware<ContextIn, ContextOut> = (options: {
20
+ ctx: ContextIn;
21
+ next: MiddlewareNext<ContextIn>;
22
+ }) => ContextOut | Promise<ContextOut>;
4
23
  /**
5
24
  * The slice of context {@link accessContext} reads: the `auth` facade every
6
25
  * Lunora ctx carries. `getIdentity()` returns the verified identity envelope —
@@ -63,7 +82,7 @@ declare const accessFacade: (identity: Record<string, unknown> | null | undefine
63
82
  * fail closed.
64
83
  *
65
84
  * It does not gate the request; pair it with `rls(...)` (or
66
- * `accessRoles(...)` → `rls(...)`) when you need enforcement. It only surfaces
85
+ * `rls(...)`) when you need enforcement. It only surfaces
67
86
  * the identity for branching inside a handler.
68
87
  *
69
88
  * ```ts
package/dist/context.d.ts CHANGED
@@ -1,6 +1,25 @@
1
- import { M as Middleware } from "./packem_shared/index.d-D7R8k7yf.js";
2
- import { A as AccessClaims } from "./packem_shared/types.d-D30o96f7.js";
1
+ import { A as AccessClaims } from "./packem_shared/types.d-Dp399TMk.js";
3
2
  import 'jose';
3
+ /**
4
+ * `next()` advances the middleware chain. Called with no argument it forwards
5
+ * the current context unchanged; called with `{ ctx }` it shallow-merges the
6
+ * extension, and the result type reflects the widened context.
7
+ */
8
+ interface MiddlewareNext<ContextIn> {
9
+ (): Promise<ContextIn>;
10
+ <Extension extends Record<string, unknown>>(options: {
11
+ ctx: Extension;
12
+ }): Promise<ContextIn & Extension>;
13
+ }
14
+ /**
15
+ * A middleware receives the current context and a `next` continuation. Its
16
+ * return type becomes the builder's new context, so `return next({ ctx })`
17
+ * propagates the extension into every downstream `.use()` and the handler.
18
+ */
19
+ type Middleware<ContextIn, ContextOut> = (options: {
20
+ ctx: ContextIn;
21
+ next: MiddlewareNext<ContextIn>;
22
+ }) => ContextOut | Promise<ContextOut>;
4
23
  /**
5
24
  * The slice of context {@link accessContext} reads: the `auth` facade every
6
25
  * Lunora ctx carries. `getIdentity()` returns the verified identity envelope —
@@ -63,7 +82,7 @@ declare const accessFacade: (identity: Record<string, unknown> | null | undefine
63
82
  * fail closed.
64
83
  *
65
84
  * It does not gate the request; pair it with `rls(...)` (or
66
- * `accessRoles(...)` → `rls(...)`) when you need enforcement. It only surfaces
85
+ * `rls(...)`) when you need enforcement. It only surfaces
67
86
  * the identity for branching inside a handler.
68
87
  *
69
88
  * ```ts
package/dist/context.mjs CHANGED
@@ -1 +1 @@
1
- import{i as r,r as m}from"./packem_shared/identity-groups-CpehEdiX.mjs";const e=s=>typeof s=="string"?s:void 0,t={authenticated:!1,claims:void 0,commonName:void 0,email:void 0,groups:[],hasGroup:()=>!1,userId:void 0},i=(s,a)=>{if(!r(s))return t;const o=s.access,c=m(s)??[];return{authenticated:!0,claims:o,commonName:e(s.commonName)??e(o.common_name),email:e(s.email)??e(o.email),groups:c,hasGroup:n=>c.includes(n),userId:a}},u=(s,a)=>s?i(s,a??void 0):t,l=()=>async({ctx:s,next:a})=>{const o=await s.auth?.getIdentity?.(),c=u(o,s.auth?.userId);return a({ctx:{access:c}})};export{l as accessContext,u as accessFacade};
1
+ import{i as r,r as m}from"./packem_shared/identity-groups-BnaBEyE_.mjs";const e=s=>typeof s=="string"?s:void 0,t={authenticated:!1,claims:void 0,commonName:void 0,email:void 0,groups:[],hasGroup:()=>!1,userId:void 0},i=(s,a)=>{if(!r(s))return t;const o=s.access,c=m(s)??[];return{authenticated:!0,claims:o,commonName:e(s.commonName)??e(o.common_name),email:e(s.email)??e(o.email),groups:c,hasGroup:n=>c.includes(n),userId:a}},u=(s,a)=>s?i(s,a??void 0):t,l=()=>async({ctx:s,next:a})=>{const o=await s.auth?.getIdentity?.(),c=u(o,s.auth?.userId);return a({ctx:{access:c}})};export{l as accessContext,u as accessFacade};
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as ResolveIdentityFunction, C as CreateAccessResolverOptions, V as VerifyAccessJwtOptions, A as AccessClaims } from "./packem_shared/types.d-D30o96f7.mjs";
2
- export type { a as AccessJwtFallbackOptions, b as AccessKeySet, c as ResolvedAccessIdentity, d as ResolvedIdentityLike } from "./packem_shared/types.d-D30o96f7.mjs";
1
+ import { R as ResolveIdentityFunction, C as CreateAccessResolverOptions, V as VerifyAccessJwtOptions, A as AccessClaims } from "./packem_shared/types.d-Dp399TMk.mjs";
2
+ export type { a as AccessJwtFallbackOptions, b as AccessKeySet, c as AccessRoleMap, d as ResolvedAccessIdentity, e as ResolvedIdentityLike } from "./packem_shared/types.d-Dp399TMk.mjs";
3
3
  import 'jose';
4
4
  /**
5
5
  * Create a `resolveIdentity` adapter for Cloudflare Access, so a verified Access
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as ResolveIdentityFunction, C as CreateAccessResolverOptions, V as VerifyAccessJwtOptions, A as AccessClaims } from "./packem_shared/types.d-D30o96f7.js";
2
- export type { a as AccessJwtFallbackOptions, b as AccessKeySet, c as ResolvedAccessIdentity, d as ResolvedIdentityLike } from "./packem_shared/types.d-D30o96f7.js";
1
+ import { R as ResolveIdentityFunction, C as CreateAccessResolverOptions, V as VerifyAccessJwtOptions, A as AccessClaims } from "./packem_shared/types.d-Dp399TMk.js";
2
+ export type { a as AccessJwtFallbackOptions, b as AccessKeySet, c as AccessRoleMap, d as ResolvedAccessIdentity, e as ResolvedIdentityLike } from "./packem_shared/types.d-Dp399TMk.js";
3
3
  import 'jose';
4
4
  /**
5
5
  * Create a `resolveIdentity` adapter for Cloudflare Access, so a verified Access
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{composeResolvers as r,createAccessResolver as c}from"./packem_shared/composeResolvers-JBsWQvv4.mjs";import{accessIssuer as t,verifyAccessJwt as f}from"./packem_shared/accessIssuer-CLO6p5MF.mjs";export{t as accessIssuer,r as composeResolvers,c as createAccessResolver,f as verifyAccessJwt};
1
+ import{composeResolvers as r,createAccessResolver as c}from"./packem_shared/composeResolvers-DQFDgtOL.mjs";import{accessIssuer as t,verifyAccessJwt as f}from"./packem_shared/accessIssuer-CLO6p5MF.mjs";export{t as accessIssuer,r as composeResolvers,c as createAccessResolver,f as verifyAccessJwt};
@@ -0,0 +1 @@
1
+ import{a as c,r as v}from"./identity-groups-BnaBEyE_.mjs";import{r as u}from"./platform-identity-Dy6G1EoF.mjs";import{assertJwtFallbackOptions as f,verifyRequest as y}from"./accessIssuer-CLO6p5MF.mjs";const d=null,i=o=>typeof o=="string"&&o.length>0?o:void 0,I=o=>i(o.sub)??i(o.email)??i(o.common_name),p=(o,e)=>{const r=e?.mapClaims?.(o)??{},n=i(r.userId)??I(o);if(n===void 0)return d;const t=c(v(o),e?.roles);return{access:o,...o.common_name===void 0?{}:{commonName:o.common_name},...o.email===void 0?{}:{email:o.email},...o.exp===void 0?{}:{exp:o.exp},...o.groups===void 0?{}:{groups:o.groups},...t===void 0?{}:{roles:t},...r,userId:n}},x=o=>{const e=f(o);return async(r,n,t)=>{const s=await u(t),a=s===void 0?d:p(s,o);if(a!==d)return a;const m=e===void 0?void 0:await y(r,e);return m===void 0?d:p(m,o)}},O=(...o)=>async(e,r,n)=>{for(const t of o){const s=await t(e,r,n);if(s)return s}return d};export{O as composeResolvers,x as createAccessResolver};
@@ -0,0 +1 @@
1
+ const c=r=>{const{access:o}=r;return typeof o=="object"&&o!==null},i=r=>{const{access:o}=r,s=typeof o=="object"&&o!==null?o.groups:void 0,t=r.groups??s;return Array.isArray(t)?t.filter(e=>typeof e=="string"):void 0},n=(r,o)=>{const s=typeof o=="function"?o(r):o[r];return s===void 0?[]:Array.isArray(s)?s:[s]},d=(r,o)=>{if(o===void 0||r===void 0||r.length===0)return;const s=new Set;for(const t of r)for(const e of n(t,o))s.add(e);return s.size===0?void 0:[...s]};export{d as a,c as i,i as r};
@@ -80,6 +80,8 @@ interface AccessIdentityLike {
80
80
  interface AccessContextLike {
81
81
  getIdentity: () => AccessIdentityLike | null | undefined | Promise<AccessIdentityLike | null | undefined>;
82
82
  }
83
+ /** A group→role(s) lookup table, or a function returning the role(s) for one group. */
84
+ type AccessRoleMap = ((group: string) => string | string[] | undefined) | Record<string, string | string[]>;
83
85
  /**
84
86
  * The verified claims of a Cloudflare Access caller — from the
85
87
  * `Cf-Access-Jwt-Assertion` JWT (hostname-scoped Access applications) or from the
@@ -239,8 +241,30 @@ interface CreateAccessResolverOptions extends AccessJwtFallbackOptions {
239
241
  * Remap verified claims into the resolved identity. Return an object to
240
242
  * shallow-merge over the defaults; return a `userId` to override the derived
241
243
  * caller id. Runs only after signature/issuer/audience/expiry are verified.
244
+ *
245
+ * It is merged OVER the `roles` claim {@link CreateAccessResolverOptions.roles}
246
+ * mints, so returning `roles` here replaces the mapped set outright.
242
247
  */
243
248
  mapClaims?: (claims: AccessClaims) => Record<string, unknown>;
249
+ /**
250
+ * Map the verified Access `groups` claim onto RLS role names, minted as the
251
+ * identity's `roles` claim — the list `rls()` unions permissions over.
252
+ *
253
+ * A table (`{ "idp-admins": "admin", "idp-eng": ["editor", "viewer"] }`) or a
254
+ * function; either may return one role, an array, or `undefined` to drop the
255
+ * group. Omit the option and no `roles` claim is minted at all — granting
256
+ * every group name as a role by default would hand existing deployments
257
+ * permissions their policies never intended. Pass `(group) => group` to opt
258
+ * into group names as role names verbatim.
259
+ *
260
+ * Roles live ON THE IDENTITY rather than being derived per-procedure so that
261
+ * every consumer sees the same set. A middleware that writes `ctx.auth.roles`
262
+ * reaches queries and mutations but NOT live shapes — a shape runs no
263
+ * procedure, so no middleware fires — and a role-gated policy then resolves
264
+ * differently for a query and the subscription that mirrors it. The identity
265
+ * is the one place both paths read.
266
+ */
267
+ roles?: AccessRoleMap;
244
268
  }
245
269
  /**
246
270
  * A `resolveIdentity`-shaped function: maps an inbound request to a verified
@@ -253,4 +277,4 @@ interface CreateAccessResolverOptions extends AccessJwtFallbackOptions {
253
277
  * have no context to give, so a resolver must handle its absence.
254
278
  */
255
279
  type ResolveIdentityFunction = (request: Request, env?: unknown, context?: ExecutionContextLike) => (ResolvedIdentityLike | null) | Promise<ResolvedIdentityLike | null>;
256
- export { AccessClaims as A, CreateAccessResolverOptions as C, ExecutionContextLike as E, ResolveIdentityFunction as R, VerifyAccessJwtOptions as V, AccessJwtFallbackOptions as a, AccessKeySet as b, ResolvedAccessIdentity as c, ResolvedIdentityLike as d };
280
+ export { AccessClaims as A, CreateAccessResolverOptions as C, ExecutionContextLike as E, ResolveIdentityFunction as R, VerifyAccessJwtOptions as V, AccessJwtFallbackOptions as a, AccessKeySet as b, AccessRoleMap as c, ResolvedAccessIdentity as d, ResolvedIdentityLike as e };
@@ -80,6 +80,8 @@ interface AccessIdentityLike {
80
80
  interface AccessContextLike {
81
81
  getIdentity: () => AccessIdentityLike | null | undefined | Promise<AccessIdentityLike | null | undefined>;
82
82
  }
83
+ /** A group→role(s) lookup table, or a function returning the role(s) for one group. */
84
+ type AccessRoleMap = ((group: string) => string | string[] | undefined) | Record<string, string | string[]>;
83
85
  /**
84
86
  * The verified claims of a Cloudflare Access caller — from the
85
87
  * `Cf-Access-Jwt-Assertion` JWT (hostname-scoped Access applications) or from the
@@ -239,8 +241,30 @@ interface CreateAccessResolverOptions extends AccessJwtFallbackOptions {
239
241
  * Remap verified claims into the resolved identity. Return an object to
240
242
  * shallow-merge over the defaults; return a `userId` to override the derived
241
243
  * caller id. Runs only after signature/issuer/audience/expiry are verified.
244
+ *
245
+ * It is merged OVER the `roles` claim {@link CreateAccessResolverOptions.roles}
246
+ * mints, so returning `roles` here replaces the mapped set outright.
242
247
  */
243
248
  mapClaims?: (claims: AccessClaims) => Record<string, unknown>;
249
+ /**
250
+ * Map the verified Access `groups` claim onto RLS role names, minted as the
251
+ * identity's `roles` claim — the list `rls()` unions permissions over.
252
+ *
253
+ * A table (`{ "idp-admins": "admin", "idp-eng": ["editor", "viewer"] }`) or a
254
+ * function; either may return one role, an array, or `undefined` to drop the
255
+ * group. Omit the option and no `roles` claim is minted at all — granting
256
+ * every group name as a role by default would hand existing deployments
257
+ * permissions their policies never intended. Pass `(group) => group` to opt
258
+ * into group names as role names verbatim.
259
+ *
260
+ * Roles live ON THE IDENTITY rather than being derived per-procedure so that
261
+ * every consumer sees the same set. A middleware that writes `ctx.auth.roles`
262
+ * reaches queries and mutations but NOT live shapes — a shape runs no
263
+ * procedure, so no middleware fires — and a role-gated policy then resolves
264
+ * differently for a query and the subscription that mirrors it. The identity
265
+ * is the one place both paths read.
266
+ */
267
+ roles?: AccessRoleMap;
244
268
  }
245
269
  /**
246
270
  * A `resolveIdentity`-shaped function: maps an inbound request to a verified
@@ -253,4 +277,4 @@ interface CreateAccessResolverOptions extends AccessJwtFallbackOptions {
253
277
  * have no context to give, so a resolver must handle its absence.
254
278
  */
255
279
  type ResolveIdentityFunction = (request: Request, env?: unknown, context?: ExecutionContextLike) => (ResolvedIdentityLike | null) | Promise<ResolvedIdentityLike | null>;
256
- export { AccessClaims as A, CreateAccessResolverOptions as C, ExecutionContextLike as E, ResolveIdentityFunction as R, VerifyAccessJwtOptions as V, AccessJwtFallbackOptions as a, AccessKeySet as b, ResolvedAccessIdentity as c, ResolvedIdentityLike as d };
280
+ export { AccessClaims as A, CreateAccessResolverOptions as C, ExecutionContextLike as E, ResolveIdentityFunction as R, VerifyAccessJwtOptions as V, AccessJwtFallbackOptions as a, AccessKeySet as b, AccessRoleMap as c, ResolvedAccessIdentity as d, ResolvedIdentityLike as e };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cloudflare-access",
3
- "version": "1.0.0-alpha.100",
3
+ "version": "1.0.0-alpha.102",
4
4
  "description": "Cloudflare Access (Zero Trust) identity for Lunora — verify the Cf-Access-Jwt-Assertion JWT against your team JWKS and feed the verified identity into ctx.auth / RLS via a resolveIdentity adapter",
5
5
  "keywords": [
6
6
  "access",
@@ -44,17 +44,13 @@
44
44
  "types": "./dist/context.d.ts",
45
45
  "import": "./dist/context.mjs"
46
46
  },
47
- "./roles": {
48
- "types": "./dist/roles.d.ts",
49
- "import": "./dist/roles.mjs"
50
- },
51
47
  "./package.json": "./package.json"
52
48
  },
53
49
  "publishConfig": {
54
50
  "access": "public"
55
51
  },
56
52
  "dependencies": {
57
- "@lunora/errors": "1.0.0-alpha.31",
53
+ "@lunora/errors": "1.0.0-alpha.32",
58
54
  "jose": "^6.2.8"
59
55
  },
60
56
  "peerDependencies": {
@@ -1 +0,0 @@
1
- import{r as c}from"./platform-identity-Dy6G1EoF.mjs";import{assertJwtFallbackOptions as m,verifyRequest as p}from"./accessIssuer-CLO6p5MF.mjs";const d=null,s=o=>typeof o=="string"&&o.length>0?o:void 0,v=o=>s(o.sub)??s(o.email)??s(o.common_name),a=(o,e)=>{const t=e?.(o)??{},n=s(t.userId)??v(o);return n===void 0?d:{access:o,...o.common_name===void 0?{}:{commonName:o.common_name},...o.email===void 0?{}:{email:o.email},...o.exp===void 0?{}:{exp:o.exp},...o.groups===void 0?{}:{groups:o.groups},...t,userId:n}},y=o=>{const e=m(o);return async(t,n,i)=>{const r=await c(i)??(e===void 0?void 0:await p(t,e));return r===void 0?d:a(r,o?.mapClaims)}},g=(...o)=>async(e,t,n)=>{for(const i of o){const r=await i(e,t,n);if(r)return r}return d};export{g as composeResolvers,y as createAccessResolver};
@@ -1 +0,0 @@
1
- const c=t=>{const{access:s}=t;return typeof s=="object"&&s!==null},n=t=>{const{access:s}=t,r=typeof s=="object"&&s!==null?s.groups:void 0,o=t.groups??r;return Array.isArray(o)?o.filter(e=>typeof e=="string"):void 0};export{c as i,n as r};
@@ -1,21 +0,0 @@
1
- /**
2
- * `next()` advances the middleware chain. Called with no argument it forwards
3
- * the current context unchanged; called with `{ ctx }` it shallow-merges the
4
- * extension, and the result type reflects the widened context.
5
- */
6
- interface MiddlewareNext<ContextIn> {
7
- (): Promise<ContextIn>;
8
- <Extension extends Record<string, unknown>>(options: {
9
- ctx: Extension;
10
- }): Promise<ContextIn & Extension>;
11
- }
12
- /**
13
- * A middleware receives the current context and a `next` continuation. Its
14
- * return type becomes the builder's new context, so `return next({ ctx })`
15
- * propagates the extension into every downstream `.use()` and the handler.
16
- */
17
- type Middleware<ContextIn, ContextOut> = (options: {
18
- ctx: ContextIn;
19
- next: MiddlewareNext<ContextIn>;
20
- }) => ContextOut | Promise<ContextOut>;
21
- export { Middleware as M };
@@ -1,21 +0,0 @@
1
- /**
2
- * `next()` advances the middleware chain. Called with no argument it forwards
3
- * the current context unchanged; called with `{ ctx }` it shallow-merges the
4
- * extension, and the result type reflects the widened context.
5
- */
6
- interface MiddlewareNext<ContextIn> {
7
- (): Promise<ContextIn>;
8
- <Extension extends Record<string, unknown>>(options: {
9
- ctx: Extension;
10
- }): Promise<ContextIn & Extension>;
11
- }
12
- /**
13
- * A middleware receives the current context and a `next` continuation. Its
14
- * return type becomes the builder's new context, so `return next({ ctx })`
15
- * propagates the extension into every downstream `.use()` and the handler.
16
- */
17
- type Middleware<ContextIn, ContextOut> = (options: {
18
- ctx: ContextIn;
19
- next: MiddlewareNext<ContextIn>;
20
- }) => ContextOut | Promise<ContextOut>;
21
- export { Middleware as M };
package/dist/roles.d.mts DELETED
@@ -1,57 +0,0 @@
1
- import { M as Middleware } from "./packem_shared/index.d-D7R8k7yf.mjs";
2
- /**
3
- * The slice of context {@link accessRoles} reads and augments: the `auth` facade
4
- * every Lunora ctx carries. `getIdentity()` returns the verified identity
5
- * envelope (`createAccessResolver`'s output, including `groups`); `roles` is the
6
- * per-request role list `rls()` unions permissions over.
7
- */
8
- interface AccessRolesContext {
9
- auth?: {
10
- getIdentity?: () => (Record<string, unknown> | null) | Promise<Record<string, unknown> | null>;
11
- roles?: ReadonlyArray<string>;
12
- userId?: string | null;
13
- };
14
- }
15
- /** A group→role(s) lookup table, or a function returning the role(s) for one group. */
16
- type AccessRoleMap = ((group: string) => string | string[] | undefined) | Record<string, string | string[]>;
17
- /** Options for {@link accessRoles}. */
18
- interface AccessRolesOptions {
19
- /**
20
- * Map verified Access group names to RLS role names. A table
21
- * (`{ "idp-admins": "admin", "idp-eng": ["editor", "viewer"] }`) or a
22
- * function; either may return one role, an array, or `undefined` to drop the
23
- * group. Omit to use each group name verbatim as a role.
24
- */
25
- map?: AccessRoleMap;
26
- /**
27
- * Read the group list off the resolved identity. Defaults to the `groups`
28
- * claim (`string[]`). Override when your IdP nests groups elsewhere.
29
- */
30
- readGroups?: (identity: Record<string, unknown>) => ReadonlyArray<string> | undefined;
31
- }
32
- /**
33
- * Middleware that lifts the verified Cloudflare Access `groups` claim into
34
- * `ctx.auth.roles` so `rls()` policies can authorize by role. Place it **before**
35
- * `rls(...)` in the `.use(...)` chain — `rls()` reads `ctx.auth.roles` to union
36
- * the permissions a request carries.
37
- *
38
- * It reads the resolved identity via `ctx.auth.getIdentity()` (the output of
39
- * `createAccessResolver`), maps each group to role name(s), and unions them with
40
- * any roles already on `ctx.auth.roles` (so a role set by an earlier middleware
41
- * is preserved). When there is no identity or no groups it forwards the context
42
- * unchanged — anonymous requests stay role-less (fail-closed under RLS).
43
- *
44
- * Only a **verified Access identity** contributes roles: an envelope without the
45
- * claim set under `access` (a session resolved by another adapter under
46
- * `composeResolvers`, e.g. better-auth) is forwarded unchanged, so a `groups`
47
- * field on a foreign identity can never become an RLS role.
48
- *
49
- * ```ts
50
- * export const listInvoices = query
51
- * .use(accessRoles({ map: { "idp-admins": "admin", "idp-billing": ["billing", "viewer"] } }))
52
- * .use(rls(policies, { roles }))
53
- * .query(async ({ ctx }) => ...);
54
- * ```
55
- */
56
- declare const accessRoles: <Context extends AccessRolesContext>(options?: AccessRolesOptions) => Middleware<Context, Context>;
57
- export { type AccessRoleMap, type AccessRolesContext, type AccessRolesOptions, accessRoles };
package/dist/roles.d.ts DELETED
@@ -1,57 +0,0 @@
1
- import { M as Middleware } from "./packem_shared/index.d-D7R8k7yf.js";
2
- /**
3
- * The slice of context {@link accessRoles} reads and augments: the `auth` facade
4
- * every Lunora ctx carries. `getIdentity()` returns the verified identity
5
- * envelope (`createAccessResolver`'s output, including `groups`); `roles` is the
6
- * per-request role list `rls()` unions permissions over.
7
- */
8
- interface AccessRolesContext {
9
- auth?: {
10
- getIdentity?: () => (Record<string, unknown> | null) | Promise<Record<string, unknown> | null>;
11
- roles?: ReadonlyArray<string>;
12
- userId?: string | null;
13
- };
14
- }
15
- /** A group→role(s) lookup table, or a function returning the role(s) for one group. */
16
- type AccessRoleMap = ((group: string) => string | string[] | undefined) | Record<string, string | string[]>;
17
- /** Options for {@link accessRoles}. */
18
- interface AccessRolesOptions {
19
- /**
20
- * Map verified Access group names to RLS role names. A table
21
- * (`{ "idp-admins": "admin", "idp-eng": ["editor", "viewer"] }`) or a
22
- * function; either may return one role, an array, or `undefined` to drop the
23
- * group. Omit to use each group name verbatim as a role.
24
- */
25
- map?: AccessRoleMap;
26
- /**
27
- * Read the group list off the resolved identity. Defaults to the `groups`
28
- * claim (`string[]`). Override when your IdP nests groups elsewhere.
29
- */
30
- readGroups?: (identity: Record<string, unknown>) => ReadonlyArray<string> | undefined;
31
- }
32
- /**
33
- * Middleware that lifts the verified Cloudflare Access `groups` claim into
34
- * `ctx.auth.roles` so `rls()` policies can authorize by role. Place it **before**
35
- * `rls(...)` in the `.use(...)` chain — `rls()` reads `ctx.auth.roles` to union
36
- * the permissions a request carries.
37
- *
38
- * It reads the resolved identity via `ctx.auth.getIdentity()` (the output of
39
- * `createAccessResolver`), maps each group to role name(s), and unions them with
40
- * any roles already on `ctx.auth.roles` (so a role set by an earlier middleware
41
- * is preserved). When there is no identity or no groups it forwards the context
42
- * unchanged — anonymous requests stay role-less (fail-closed under RLS).
43
- *
44
- * Only a **verified Access identity** contributes roles: an envelope without the
45
- * claim set under `access` (a session resolved by another adapter under
46
- * `composeResolvers`, e.g. better-auth) is forwarded unchanged, so a `groups`
47
- * field on a foreign identity can never become an RLS role.
48
- *
49
- * ```ts
50
- * export const listInvoices = query
51
- * .use(accessRoles({ map: { "idp-admins": "admin", "idp-billing": ["billing", "viewer"] } }))
52
- * .use(rls(policies, { roles }))
53
- * .query(async ({ ctx }) => ...);
54
- * ```
55
- */
56
- declare const accessRoles: <Context extends AccessRolesContext>(options?: AccessRolesOptions) => Middleware<Context, Context>;
57
- export { type AccessRoleMap, type AccessRolesContext, type AccessRolesOptions, accessRoles };
package/dist/roles.mjs DELETED
@@ -1 +0,0 @@
1
- import{r as a,i as c}from"./packem_shared/identity-groups-CpehEdiX.mjs";const f=(o,t)=>{if(t===void 0)return[o];const r=typeof t=="function"?t(o):t[o];return r===void 0?[]:Array.isArray(r)?r:[r]},y=(o={})=>{const t=o.readGroups??a;return async({ctx:r,next:n})=>{const e=await r.auth?.getIdentity?.()??void 0,s=e!==void 0&&c(e)?t(e):void 0;if(s===void 0||s.length===0)return n();const i=new Set(r.auth?.roles);for(const d of s)for(const u of f(d,o.map))i.add(u);return n({ctx:{auth:{...r.auth,roles:[...i]}}})}};export{y as accessRoles};