@lunora/cloudflare-access 1.0.0-alpha.20 → 1.0.0-alpha.22

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.mjs CHANGED
@@ -1,8 +1,11 @@
1
- import { verifyRequest } from './packem_shared/accessIssuer-83KYwaCp.mjs';
1
+ import { assertVerifyOptions, verifyRequest } from './packem_shared/accessIssuer-C69kkiYc.mjs';
2
2
 
3
- const accessAdminGate = (options) => async (request) => {
4
- const claims = await verifyRequest(request, options);
5
- return claims === void 0 ? false : options.isAdmin(claims);
3
+ const accessAdminGate = (options) => {
4
+ assertVerifyOptions(options);
5
+ return async (request) => {
6
+ const claims = await verifyRequest(request, options);
7
+ return claims === void 0 ? false : options.isAdmin(claims);
8
+ };
6
9
  };
7
10
 
8
11
  export { accessAdminGate };
package/dist/context.mjs CHANGED
@@ -1,4 +1,5 @@
1
- const stringList = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1
+ import { r as readIdentityGroups } from './packem_shared/identity-groups-CONuLY61.mjs';
2
+
2
3
  const stringClaim = (value) => typeof value === "string" ? value : void 0;
3
4
  const ANONYMOUS_FACADE = {
4
5
  authenticated: false,
@@ -15,7 +16,7 @@ const facadeFor = (identity, userId) => {
15
16
  return ANONYMOUS_FACADE;
16
17
  }
17
18
  const claims = raw;
18
- const groups = stringList(identity["groups"] ?? claims.groups);
19
+ const groups = readIdentityGroups(identity) ?? [];
19
20
  return {
20
21
  authenticated: true,
21
22
  claims,
package/dist/index.d.mts CHANGED
@@ -40,6 +40,17 @@ declare const composeResolvers: (...resolvers: ResolveIdentityFunction[]) => Res
40
40
  * bare name with no dot is expanded to the `cloudflareaccess.com` host.
41
41
  */
42
42
  declare const accessIssuer: (teamDomain: string) => string;
43
+ /**
44
+ * Eagerly validate the static, construction-time verify options so a misconfigured
45
+ * deployment fails fast at factory build time instead of degrading to
46
+ * silent-anonymous on every request. `createAccessResolver` / `accessAdminGate`
47
+ * call this once when built: `teamDomain` must resolve to a valid Access issuer
48
+ * and `aud` must be a non-empty tag. The per-request catch in {@link verifyRequest}
49
+ * then covers only genuine token-verification failures, not config mistakes — a
50
+ * broken deployment throws here at startup rather than resolving every caller to
51
+ * anonymous with zero signal.
52
+ */
53
+
43
54
  /**
44
55
  * Verify a Cloudflare Access JWT and return its claims.
45
56
  *
package/dist/index.d.ts CHANGED
@@ -40,6 +40,17 @@ declare const composeResolvers: (...resolvers: ResolveIdentityFunction[]) => Res
40
40
  * bare name with no dot is expanded to the `cloudflareaccess.com` host.
41
41
  */
42
42
  declare const accessIssuer: (teamDomain: string) => string;
43
+ /**
44
+ * Eagerly validate the static, construction-time verify options so a misconfigured
45
+ * deployment fails fast at factory build time instead of degrading to
46
+ * silent-anonymous on every request. `createAccessResolver` / `accessAdminGate`
47
+ * call this once when built: `teamDomain` must resolve to a valid Access issuer
48
+ * and `aud` must be a non-empty tag. The per-request catch in {@link verifyRequest}
49
+ * then covers only genuine token-verification failures, not config mistakes — a
50
+ * broken deployment throws here at startup rather than resolving every caller to
51
+ * anonymous with zero signal.
52
+ */
53
+
43
54
  /**
44
55
  * Verify a Cloudflare Access JWT and return its claims.
45
56
  *
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export { composeResolvers, createAccessResolver } from './packem_shared/composeResolvers-BI3envaN.mjs';
2
- export { accessIssuer, verifyAccessJwt } from './packem_shared/accessIssuer-83KYwaCp.mjs';
1
+ export { composeResolvers, createAccessResolver } from './packem_shared/composeResolvers-BlVswT75.mjs';
2
+ export { accessIssuer, verifyAccessJwt } from './packem_shared/accessIssuer-C69kkiYc.mjs';
@@ -52,17 +52,23 @@ const remoteJwks = (issuer) => {
52
52
  }
53
53
  return getter;
54
54
  };
55
- const verifyAccessJwt = async (token, options) => {
56
- const issuer = accessIssuer(options.teamDomain);
57
- const audiences = (Array.isArray(options.aud) ? options.aud : [options.aud]).filter(
58
- (entry) => typeof entry === "string" && entry.length > 0
59
- );
55
+ const normalizeAudiences = (aud) => {
56
+ const audiences = (Array.isArray(aud) ? aud : [aud]).filter((entry) => typeof entry === "string" && entry.length > 0);
60
57
  if (audiences.length === 0) {
61
58
  throw new LunoraError(
62
59
  "INTERNAL",
63
60
  "@lunora/cloudflare-access: `aud` is required and must be a non-empty Access AUD tag — refusing to verify a token without an audience to scope it to your application"
64
61
  );
65
62
  }
63
+ return audiences;
64
+ };
65
+ const assertVerifyOptions = (options) => {
66
+ accessIssuer(options.teamDomain);
67
+ normalizeAudiences(options.aud);
68
+ };
69
+ const verifyAccessJwt = async (token, options) => {
70
+ const issuer = accessIssuer(options.teamDomain);
71
+ const audiences = normalizeAudiences(options.aud);
66
72
  const keySet = options.keySet ?? remoteJwks(issuer);
67
73
  const { payload } = await jwtVerify(token, keySet, {
68
74
  algorithms: ["RS256"],
@@ -90,4 +96,4 @@ const verifyRequest = async (request, options) => {
90
96
  }
91
97
  };
92
98
 
93
- export { accessIssuer, verifyAccessJwt, verifyRequest };
99
+ export { accessIssuer, assertVerifyOptions, verifyAccessJwt, verifyRequest };
@@ -1,13 +1,11 @@
1
- import { verifyRequest } from './accessIssuer-83KYwaCp.mjs';
1
+ import { assertVerifyOptions, verifyRequest } from './accessIssuer-C69kkiYc.mjs';
2
2
 
3
3
  const ANONYMOUS = null;
4
- const deriveUserId = (claims) => {
5
- const sub = typeof claims.sub === "string" && claims.sub.length > 0 ? claims.sub : void 0;
6
- return sub ?? claims.email ?? claims.common_name;
7
- };
4
+ const nonEmpty = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
5
+ const deriveUserId = (claims) => nonEmpty(claims.sub) ?? nonEmpty(claims.email) ?? nonEmpty(claims.common_name);
8
6
  const toIdentity = (claims, mapClaims) => {
9
7
  const overrides = mapClaims?.(claims) ?? {};
10
- const userId = typeof overrides.userId === "string" ? overrides.userId : deriveUserId(claims);
8
+ const userId = nonEmpty(overrides.userId) ?? deriveUserId(claims);
11
9
  if (userId === void 0) {
12
10
  return ANONYMOUS;
13
11
  }
@@ -21,9 +19,12 @@ const toIdentity = (claims, mapClaims) => {
21
19
  userId
22
20
  };
23
21
  };
24
- const createAccessResolver = (options) => async (request) => {
25
- const claims = await verifyRequest(request, options);
26
- return claims === void 0 ? ANONYMOUS : toIdentity(claims, options.mapClaims);
22
+ const createAccessResolver = (options) => {
23
+ assertVerifyOptions(options);
24
+ return async (request) => {
25
+ const claims = await verifyRequest(request, options);
26
+ return claims === void 0 ? ANONYMOUS : toIdentity(claims, options.mapClaims);
27
+ };
27
28
  };
28
29
  const composeResolvers = (...resolvers) => async (request, env) => {
29
30
  for (const resolve of resolvers) {
@@ -0,0 +1,8 @@
1
+ const readIdentityGroups = (identity) => {
2
+ const { access } = identity;
3
+ const nested = typeof access === "object" && access !== null ? access.groups : void 0;
4
+ const groups = identity["groups"] ?? nested;
5
+ return Array.isArray(groups) ? groups.filter((group) => typeof group === "string") : void 0;
6
+ };
7
+
8
+ export { readIdentityGroups as r };
package/dist/roles.mjs CHANGED
@@ -1,9 +1,5 @@
1
- const defaultReadGroups = (identity) => {
2
- const { access } = identity;
3
- const nested = typeof access === "object" && access !== null ? access.groups : void 0;
4
- const groups = identity["groups"] ?? nested;
5
- return Array.isArray(groups) ? groups.filter((group) => typeof group === "string") : void 0;
6
- };
1
+ import { r as readIdentityGroups } from './packem_shared/identity-groups-CONuLY61.mjs';
2
+
7
3
  const rolesForGroup = (group, map) => {
8
4
  if (map === void 0) {
9
5
  return [group];
@@ -15,7 +11,7 @@ const rolesForGroup = (group, map) => {
15
11
  return Array.isArray(resolved) ? resolved : [resolved];
16
12
  };
17
13
  const accessRoles = (options = {}) => {
18
- const readGroups = options.readGroups ?? defaultReadGroups;
14
+ const readGroups = options.readGroups ?? readIdentityGroups;
19
15
  return async ({ ctx, next }) => {
20
16
  const identity = await ctx.auth?.getIdentity?.() ?? void 0;
21
17
  const groups = identity ? readGroups(identity) : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cloudflare-access",
3
- "version": "1.0.0-alpha.20",
3
+ "version": "1.0.0-alpha.22",
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",
@@ -54,11 +54,11 @@
54
54
  "access": "public"
55
55
  },
56
56
  "dependencies": {
57
- "@lunora/errors": "1.0.0-alpha.3",
57
+ "@lunora/errors": "1.0.0-alpha.4",
58
58
  "jose": "^6.2.3"
59
59
  },
60
60
  "peerDependencies": {
61
- "@lunora/server": "1.0.0-alpha.22"
61
+ "@lunora/server": "1.0.0-alpha.24"
62
62
  },
63
63
  "peerDependenciesMeta": {
64
64
  "@lunora/server": {