@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 +1 -1
- package/dist/admin.d.ts +1 -1
- package/dist/context.d.mts +22 -3
- package/dist/context.d.ts +22 -3
- package/dist/context.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/composeResolvers-DQFDgtOL.mjs +1 -0
- package/dist/packem_shared/identity-groups-BnaBEyE_.mjs +1 -0
- package/dist/packem_shared/{types.d-D30o96f7.d.mts → types.d-Dp399TMk.d.mts} +25 -1
- package/dist/packem_shared/{types.d-D30o96f7.d.ts → types.d-Dp399TMk.d.ts} +25 -1
- package/package.json +2 -6
- package/dist/packem_shared/composeResolvers-JBsWQvv4.mjs +0 -1
- package/dist/packem_shared/identity-groups-CpehEdiX.mjs +0 -1
- package/dist/packem_shared/index.d-D7R8k7yf.d.mts +0 -21
- package/dist/packem_shared/index.d-D7R8k7yf.d.ts +0 -21
- package/dist/roles.d.mts +0 -57
- package/dist/roles.d.ts +0 -57
- package/dist/roles.mjs +0 -1
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-
|
|
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-
|
|
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 {
|
package/dist/context.d.mts
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
|
-
import {
|
|
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
|
-
* `
|
|
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 {
|
|
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
|
-
* `
|
|
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-
|
|
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-
|
|
2
|
-
export type { a as AccessJwtFallbackOptions, b as AccessKeySet, c as
|
|
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-
|
|
2
|
-
export type { a as AccessJwtFallbackOptions, b as AccessKeySet, c as
|
|
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-
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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};
|