@growth-labs/auth 0.10.0 → 0.10.3

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.
Files changed (95) hide show
  1. package/README.md +17 -5
  2. package/dist/config-store.d.ts.map +1 -1
  3. package/dist/config-store.js +12 -1
  4. package/dist/config-store.js.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +8 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/middleware/auth.d.ts.map +1 -1
  9. package/dist/middleware/auth.js +23 -3
  10. package/dist/middleware/auth.js.map +1 -1
  11. package/dist/options.d.ts +4 -3
  12. package/dist/options.d.ts.map +1 -1
  13. package/dist/options.js +15 -1
  14. package/dist/options.js.map +1 -1
  15. package/dist/renderers.d.ts +8 -0
  16. package/dist/renderers.d.ts.map +1 -1
  17. package/dist/renderers.js +27 -2
  18. package/dist/renderers.js.map +1 -1
  19. package/dist/routes/api/code-request.d.ts.map +1 -1
  20. package/dist/routes/api/code-request.js +5 -1
  21. package/dist/routes/api/code-request.js.map +1 -1
  22. package/dist/routes/api/code-verify.d.ts.map +1 -1
  23. package/dist/routes/api/code-verify.js +5 -1
  24. package/dist/routes/api/code-verify.js.map +1 -1
  25. package/dist/routes/api/password-reset-confirm.d.ts.map +1 -1
  26. package/dist/routes/api/password-reset-confirm.js +5 -1
  27. package/dist/routes/api/password-reset-confirm.js.map +1 -1
  28. package/dist/routes/api/password-reset-request.d.ts.map +1 -1
  29. package/dist/routes/api/password-reset-request.js +5 -1
  30. package/dist/routes/api/password-reset-request.js.map +1 -1
  31. package/dist/routes/api/password-verify.d.ts.map +1 -1
  32. package/dist/routes/api/password-verify.js +5 -1
  33. package/dist/routes/api/password-verify.js.map +1 -1
  34. package/dist/routes/api/shared.d.ts +1 -0
  35. package/dist/routes/api/shared.d.ts.map +1 -1
  36. package/dist/routes/api/shared.js +26 -2
  37. package/dist/routes/api/shared.js.map +1 -1
  38. package/dist/routes/callback.d.ts.map +1 -1
  39. package/dist/routes/callback.js +12 -3
  40. package/dist/routes/callback.js.map +1 -1
  41. package/dist/routes/login.js +4 -4
  42. package/dist/routes/login.js.map +1 -1
  43. package/dist/routes/logout.d.ts.map +1 -1
  44. package/dist/routes/logout.js +35 -6
  45. package/dist/routes/logout.js.map +1 -1
  46. package/dist/server.d.ts +2 -0
  47. package/dist/server.d.ts.map +1 -1
  48. package/dist/server.js +15 -4
  49. package/dist/server.js.map +1 -1
  50. package/dist/utils/client-secret-literal.d.ts +13 -0
  51. package/dist/utils/client-secret-literal.d.ts.map +1 -0
  52. package/dist/utils/client-secret-literal.js +50 -0
  53. package/dist/utils/client-secret-literal.js.map +1 -0
  54. package/dist/utils/client-secret.d.ts +12 -1
  55. package/dist/utils/client-secret.d.ts.map +1 -1
  56. package/dist/utils/client-secret.js +108 -14
  57. package/dist/utils/client-secret.js.map +1 -1
  58. package/dist/utils/csp.d.ts +83 -0
  59. package/dist/utils/csp.d.ts.map +1 -0
  60. package/dist/utils/csp.js +149 -0
  61. package/dist/utils/csp.js.map +1 -0
  62. package/dist/utils/fail-closed.d.ts +14 -0
  63. package/dist/utils/fail-closed.d.ts.map +1 -0
  64. package/dist/utils/fail-closed.js +53 -0
  65. package/dist/utils/fail-closed.js.map +1 -0
  66. package/dist/utils/index.d.ts +5 -1
  67. package/dist/utils/index.d.ts.map +1 -1
  68. package/dist/utils/index.js +4 -1
  69. package/dist/utils/index.js.map +1 -1
  70. package/dist/utils/openauth.d.ts +7 -1
  71. package/dist/utils/openauth.d.ts.map +1 -1
  72. package/dist/utils/openauth.js +14 -5
  73. package/dist/utils/openauth.js.map +1 -1
  74. package/package.json +2 -2
  75. package/src/config-store.ts +13 -1
  76. package/src/index.ts +11 -1
  77. package/src/middleware/auth.ts +30 -3
  78. package/src/options.ts +16 -1
  79. package/src/renderers.ts +28 -2
  80. package/src/routes/api/code-request.ts +5 -0
  81. package/src/routes/api/code-verify.ts +5 -0
  82. package/src/routes/api/password-reset-confirm.ts +5 -0
  83. package/src/routes/api/password-reset-request.ts +5 -0
  84. package/src/routes/api/password-verify.ts +5 -0
  85. package/src/routes/api/shared.ts +33 -2
  86. package/src/routes/callback.ts +12 -2
  87. package/src/routes/login.ts +4 -4
  88. package/src/routes/logout.ts +35 -6
  89. package/src/server.ts +21 -4
  90. package/src/utils/client-secret-literal.ts +71 -0
  91. package/src/utils/client-secret.ts +132 -18
  92. package/src/utils/csp.ts +167 -0
  93. package/src/utils/fail-closed.ts +74 -0
  94. package/src/utils/index.ts +22 -1
  95. package/src/utils/openauth.ts +29 -5
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Self-contained Content-Security-Policy for auth renderer pages.
3
+ *
4
+ * WHY THIS EXISTS. The auth integration serves its UI surfaces (`/login`,
5
+ * `/verify-code`, `/password`, `/forgot-password`, `/reset-password`) from
6
+ * `injectRoute`-registered Astro endpoints that return a raw `Response` built
7
+ * from an HTML string. Astro's native hash-based `security.csp` only rewrites
8
+ * *component-rendered* pages — it scans the rendered document, hashes inline
9
+ * `<script>`/`<style>`, and injects a `<meta http-equiv>` CSP. An endpoint that
10
+ * returns its own `Response` bypasses that pass entirely, so on a site with an
11
+ * enforced `security.csp` the auth pages ship transport headers but NO CSP and
12
+ * their inline theme-bootstrap script is un-hashed (fulcrum-labs/warfronts
13
+ * verified live: `/login` 200, no `Content-Security-Policy` header or meta).
14
+ *
15
+ * This is the auth-side analogue of the `@growth-labs/consent@0.7.1`
16
+ * `context.csp.insertScriptHash` fix. Consent registers its banner hash and
17
+ * lets Astro render the CSP meta into the `.astro` page; auth has no Astro page
18
+ * to carry a meta, so it computes the sha256 hashes of its own inline scripts
19
+ * and styles and emits the `<meta http-equiv="Content-Security-Policy">` itself.
20
+ * Browsers combine a CSP meta with any CSP response header by intersection, so a
21
+ * site keeps owning the transport-header CSP (or none) while auth owns the meta
22
+ * on its own surfaces — exactly the model warfronts documents (CSP lives in the
23
+ * meta on SSR pages; the transport middleware never emits a CSP header).
24
+ */
25
+ /** A sha256 CSP source expression, e.g. `sha256-<base64>`. */
26
+ export type CspHash = `sha256-${string}`;
27
+ /**
28
+ * Compute the CSP sha256 hash of an inline `<script>`/`<style>` body. Hashes the
29
+ * exact bytes the browser hashes — the raw text between the element's open and
30
+ * close tags, with no trimming — so the emitted `'sha256-…'` matches what the
31
+ * browser recomputes for the served content. Mirrors the consent package's
32
+ * `sha256CspHash` (SHA-256 → base64) so the two packages stay wire-compatible.
33
+ */
34
+ export declare function sha256CspHash(content: string): Promise<CspHash>;
35
+ /** Inner text of every inline `<script>` (no `src=`) and `<style>` element. */
36
+ interface InlineSources {
37
+ scripts: string[];
38
+ styles: string[];
39
+ }
40
+ /**
41
+ * Extract the inner bodies of inline `<script>` and `<style>` elements. A
42
+ * `<script src=…>` reference has no hashable body (its bytes live at another
43
+ * URL, covered by `script-src 'self'`) and is skipped. Empty bodies are skipped
44
+ * — an empty inline script needs no hash. De-duplicates identical bodies so an
45
+ * element repeated on a page contributes a single hash.
46
+ */
47
+ export declare function extractInlineSources(html: string): InlineSources;
48
+ /** True when the document already carries a CSP `<meta http-equiv>` tag. */
49
+ export declare function hasCspMeta(html: string): boolean;
50
+ /**
51
+ * Build the CSP policy string for an auth page from its inline-source hashes.
52
+ *
53
+ * - `default-src 'self'` is the floor; anything not otherwise named falls here.
54
+ * - `script-src 'self' <hashes>` allows same-origin scripts plus the exact
55
+ * inline scripts on the page. No `'unsafe-inline'`: every inline script is
56
+ * hashed, so the policy stays strict.
57
+ * - `style-src 'self' <hashes>` does the same for inline `<style>` blocks.
58
+ * - `style-src-attr 'unsafe-inline'` permits inline `style="…"` attributes,
59
+ * which are NOT hashable (Astro's own CSP takes the same allowance for
60
+ * attribute styles). Element styles remain hash-pinned.
61
+ * - `img-src 'self' data: https:` covers the brand logo and inline data URIs.
62
+ * - `connect-src 'self'` bounds fetch/XHR (the code-request/verify POSTs are
63
+ * same-origin). `form-action 'self'` bounds the login/verify form posts.
64
+ * - `object-src 'none'`, `base-uri 'self'`, `frame-ancestors 'none'` are the
65
+ * standard hardening trio (no plugins, no base-tag hijack, no framing).
66
+ */
67
+ export declare function buildAuthCspPolicy(sources: {
68
+ scriptHashes: CspHash[];
69
+ styleHashes: CspHash[];
70
+ }): string;
71
+ /**
72
+ * Compute the auth CSP for `html` and return the document with a
73
+ * `<meta http-equiv="Content-Security-Policy">` inserted at the top of `<head>`.
74
+ *
75
+ * Idempotent: if the document already carries a CSP meta (a consumer renderer
76
+ * that emits its own), the input is returned unchanged so we never emit two
77
+ * conflicting policies (browsers would intersect them and likely break the
78
+ * page). Every inline `<script>`/`<style>` the page ships is hash-pinned, so the
79
+ * emitted policy is strict — no `'unsafe-inline'` for scripts or element styles.
80
+ */
81
+ export declare function injectAuthCsp(html: string): Promise<string>;
82
+ export {};
83
+ //# sourceMappingURL=csp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../src/utils/csp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,OAAO,GAAG,UAAU,MAAM,EAAE,CAAA;AAExC;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAIrE;AAED,+EAA+E;AAC/E,UAAU,aAAa;IACtB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;CAChB;AAMD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAmBhE;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC3C,YAAY,EAAE,OAAO,EAAE,CAAA;IACvB,WAAW,EAAE,OAAO,EAAE,CAAA;CACtB,GAAG,MAAM,CAeT;AA6BD;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQjE"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Self-contained Content-Security-Policy for auth renderer pages.
3
+ *
4
+ * WHY THIS EXISTS. The auth integration serves its UI surfaces (`/login`,
5
+ * `/verify-code`, `/password`, `/forgot-password`, `/reset-password`) from
6
+ * `injectRoute`-registered Astro endpoints that return a raw `Response` built
7
+ * from an HTML string. Astro's native hash-based `security.csp` only rewrites
8
+ * *component-rendered* pages — it scans the rendered document, hashes inline
9
+ * `<script>`/`<style>`, and injects a `<meta http-equiv>` CSP. An endpoint that
10
+ * returns its own `Response` bypasses that pass entirely, so on a site with an
11
+ * enforced `security.csp` the auth pages ship transport headers but NO CSP and
12
+ * their inline theme-bootstrap script is un-hashed (fulcrum-labs/warfronts
13
+ * verified live: `/login` 200, no `Content-Security-Policy` header or meta).
14
+ *
15
+ * This is the auth-side analogue of the `@growth-labs/consent@0.7.1`
16
+ * `context.csp.insertScriptHash` fix. Consent registers its banner hash and
17
+ * lets Astro render the CSP meta into the `.astro` page; auth has no Astro page
18
+ * to carry a meta, so it computes the sha256 hashes of its own inline scripts
19
+ * and styles and emits the `<meta http-equiv="Content-Security-Policy">` itself.
20
+ * Browsers combine a CSP meta with any CSP response header by intersection, so a
21
+ * site keeps owning the transport-header CSP (or none) while auth owns the meta
22
+ * on its own surfaces — exactly the model warfronts documents (CSP lives in the
23
+ * meta on SSR pages; the transport middleware never emits a CSP header).
24
+ */
25
+ /**
26
+ * Compute the CSP sha256 hash of an inline `<script>`/`<style>` body. Hashes the
27
+ * exact bytes the browser hashes — the raw text between the element's open and
28
+ * close tags, with no trimming — so the emitted `'sha256-…'` matches what the
29
+ * browser recomputes for the served content. Mirrors the consent package's
30
+ * `sha256CspHash` (SHA-256 → base64) so the two packages stay wire-compatible.
31
+ */
32
+ export async function sha256CspHash(content) {
33
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(content));
34
+ const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));
35
+ return `sha256-${base64}`;
36
+ }
37
+ const INLINE_SCRIPT_RE = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
38
+ const INLINE_STYLE_RE = /<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi;
39
+ const HAS_SRC_RE = /\bsrc\s*=/i;
40
+ /**
41
+ * Extract the inner bodies of inline `<script>` and `<style>` elements. A
42
+ * `<script src=…>` reference has no hashable body (its bytes live at another
43
+ * URL, covered by `script-src 'self'`) and is skipped. Empty bodies are skipped
44
+ * — an empty inline script needs no hash. De-duplicates identical bodies so an
45
+ * element repeated on a page contributes a single hash.
46
+ */
47
+ export function extractInlineSources(html) {
48
+ const scripts = new Set();
49
+ const styles = new Set();
50
+ for (const match of html.matchAll(INLINE_SCRIPT_RE)) {
51
+ const attrs = match[1] ?? '';
52
+ const body = match[2] ?? '';
53
+ if (HAS_SRC_RE.test(attrs))
54
+ continue;
55
+ if (body.length === 0)
56
+ continue;
57
+ scripts.add(body);
58
+ }
59
+ for (const match of html.matchAll(INLINE_STYLE_RE)) {
60
+ const body = match[1] ?? '';
61
+ if (body.length === 0)
62
+ continue;
63
+ styles.add(body);
64
+ }
65
+ return { scripts: [...scripts], styles: [...styles] };
66
+ }
67
+ /** True when the document already carries a CSP `<meta http-equiv>` tag. */
68
+ export function hasCspMeta(html) {
69
+ return /<meta\b[^>]*\bhttp-equiv\s*=\s*["']?content-security-policy["']?/i.test(html);
70
+ }
71
+ /**
72
+ * Build the CSP policy string for an auth page from its inline-source hashes.
73
+ *
74
+ * - `default-src 'self'` is the floor; anything not otherwise named falls here.
75
+ * - `script-src 'self' <hashes>` allows same-origin scripts plus the exact
76
+ * inline scripts on the page. No `'unsafe-inline'`: every inline script is
77
+ * hashed, so the policy stays strict.
78
+ * - `style-src 'self' <hashes>` does the same for inline `<style>` blocks.
79
+ * - `style-src-attr 'unsafe-inline'` permits inline `style="…"` attributes,
80
+ * which are NOT hashable (Astro's own CSP takes the same allowance for
81
+ * attribute styles). Element styles remain hash-pinned.
82
+ * - `img-src 'self' data: https:` covers the brand logo and inline data URIs.
83
+ * - `connect-src 'self'` bounds fetch/XHR (the code-request/verify POSTs are
84
+ * same-origin). `form-action 'self'` bounds the login/verify form posts.
85
+ * - `object-src 'none'`, `base-uri 'self'`, `frame-ancestors 'none'` are the
86
+ * standard hardening trio (no plugins, no base-tag hijack, no framing).
87
+ */
88
+ export function buildAuthCspPolicy(sources) {
89
+ const scriptSrc = ["script-src 'self'", ...sources.scriptHashes.map((h) => `'${h}'`)].join(' ');
90
+ const styleSrc = ["style-src 'self'", ...sources.styleHashes.map((h) => `'${h}'`)].join(' ');
91
+ return [
92
+ "default-src 'self'",
93
+ scriptSrc,
94
+ styleSrc,
95
+ "style-src-attr 'unsafe-inline'",
96
+ "img-src 'self' data: https:",
97
+ "connect-src 'self'",
98
+ "form-action 'self'",
99
+ "object-src 'none'",
100
+ "base-uri 'self'",
101
+ "frame-ancestors 'none'",
102
+ ].join('; ');
103
+ }
104
+ function escapeHtmlAttribute(value) {
105
+ return value
106
+ .replace(/&/g, '&amp;')
107
+ .replace(/"/g, '&quot;')
108
+ .replace(/'/g, '&#39;')
109
+ .replace(/</g, '&lt;')
110
+ .replace(/>/g, '&gt;');
111
+ }
112
+ /**
113
+ * Insert `tag` immediately after the opening `<head>` (or, absent a head, after
114
+ * `<html>`, or as a prefix) so the policy is parsed before any inline content.
115
+ */
116
+ function injectIntoHead(html, tag) {
117
+ const headOpen = /<head\b[^>]*>/i.exec(html);
118
+ if (headOpen) {
119
+ const at = headOpen.index + headOpen[0].length;
120
+ return `${html.slice(0, at)}${tag}${html.slice(at)}`;
121
+ }
122
+ const htmlOpen = /<html\b[^>]*>/i.exec(html);
123
+ if (htmlOpen) {
124
+ const at = htmlOpen.index + htmlOpen[0].length;
125
+ return `${html.slice(0, at)}${tag}${html.slice(at)}`;
126
+ }
127
+ return `${tag}${html}`;
128
+ }
129
+ /**
130
+ * Compute the auth CSP for `html` and return the document with a
131
+ * `<meta http-equiv="Content-Security-Policy">` inserted at the top of `<head>`.
132
+ *
133
+ * Idempotent: if the document already carries a CSP meta (a consumer renderer
134
+ * that emits its own), the input is returned unchanged so we never emit two
135
+ * conflicting policies (browsers would intersect them and likely break the
136
+ * page). Every inline `<script>`/`<style>` the page ships is hash-pinned, so the
137
+ * emitted policy is strict — no `'unsafe-inline'` for scripts or element styles.
138
+ */
139
+ export async function injectAuthCsp(html) {
140
+ if (hasCspMeta(html))
141
+ return html;
142
+ const { scripts, styles } = extractInlineSources(html);
143
+ const scriptHashes = await Promise.all(scripts.map(sha256CspHash));
144
+ const styleHashes = await Promise.all(styles.map(sha256CspHash));
145
+ const policy = buildAuthCspPolicy({ scriptHashes, styleHashes });
146
+ const tag = `<meta http-equiv="Content-Security-Policy" content="${escapeHtmlAttribute(policy)}">`;
147
+ return injectIntoHead(html, tag);
148
+ }
149
+ //# sourceMappingURL=csp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp.js","sourceRoot":"","sources":["../../src/utils/csp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ClientSecretResolutionErrorCode } from './client-secret.js';
2
+ export type ClientSecretFailureSurface = 'middleware_refresh' | 'callback_exchange' | 'logout_revocation' | 'route_preflight' | 'route_exchange' | 'server_auth_client';
3
+ export interface ClientSecretFailureSignal {
4
+ readonly surface: ClientSecretFailureSurface;
5
+ readonly code: ClientSecretResolutionErrorCode;
6
+ }
7
+ export declare function reportClientSecretResolutionFailure(signal: ClientSecretFailureSignal): void;
8
+ export declare function secretResolutionFailureResponse(request?: Request, signal?: ClientSecretFailureSignal): Response;
9
+ export declare class ClientSecretResolutionError extends Error {
10
+ readonly code: ClientSecretResolutionErrorCode;
11
+ readonly name = "ClientSecretResolutionError";
12
+ constructor(code: ClientSecretResolutionErrorCode);
13
+ }
14
+ //# sourceMappingURL=fail-closed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fail-closed.d.ts","sourceRoot":"","sources":["../../src/utils/fail-closed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAA;AAEzE,MAAM,MAAM,0BAA0B,GACnC,oBAAoB,GACpB,mBAAmB,GACnB,mBAAmB,GACnB,iBAAiB,GACjB,gBAAgB,GAChB,oBAAoB,CAAA;AAEvB,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,OAAO,EAAE,0BAA0B,CAAA;IAC5C,QAAQ,CAAC,IAAI,EAAE,+BAA+B,CAAA;CAC9C;AAoBD,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,yBAAyB,GAAG,IAAI,CAS3F;AAED,wBAAgB,+BAA+B,CAC9C,OAAO,CAAC,EAAE,OAAO,EACjB,MAAM,CAAC,EAAE,yBAAyB,GAChC,QAAQ,CAgBV;AAKD,qBAAa,2BAA4B,SAAQ,KAAK;aAEzB,IAAI,EAAE,+BAA+B;IADjE,SAAkB,IAAI,iCAAgC;gBAC1B,IAAI,EAAE,+BAA+B;CAGjE"}
@@ -0,0 +1,53 @@
1
+ // Shared no-store headers for all secret-resolution failure responses.
2
+ const SECURITY_HEADERS = {
3
+ 'Cache-Control': 'private, no-store',
4
+ Pragma: 'no-cache',
5
+ Expires: '0',
6
+ };
7
+ function acceptsJson(request) {
8
+ const accept = request?.headers.get('accept') ?? '';
9
+ return accept
10
+ .split(',')
11
+ .some((value) => value.trim().toLowerCase().split(';')[0] === 'application/json');
12
+ }
13
+ // Fixed-vocabulary 503 for secret-resolution failures on all surfaces.
14
+ // Browser callers receive bounded plain text; JSON callers receive one fixed
15
+ // JSON error. Neither class reflects a path, provider, error code, raw error,
16
+ // secret value, or stack. Callers must invoke this before issuer I/O.
17
+ export function reportClientSecretResolutionFailure(signal) {
18
+ try {
19
+ console.error('[growth-labs/auth] client_secret_resolution_failed', {
20
+ surface: signal.surface,
21
+ code: signal.code,
22
+ });
23
+ }
24
+ catch {
25
+ // Observability must never replace the bounded fail-closed response.
26
+ }
27
+ }
28
+ export function secretResolutionFailureResponse(request, signal) {
29
+ if (signal)
30
+ reportClientSecretResolutionFailure(signal);
31
+ if (acceptsJson(request)) {
32
+ return Response.json({ error: 'auth_service_unavailable' }, { status: 503, headers: new Headers(SECURITY_HEADERS) });
33
+ }
34
+ return new Response('Service unavailable', {
35
+ status: 503,
36
+ headers: new Headers({
37
+ ...SECURITY_HEADERS,
38
+ 'Content-Type': 'text/plain; charset=UTF-8',
39
+ }),
40
+ });
41
+ }
42
+ // Typed error thrown by server-helper headersFor() on secret binding failure.
43
+ // Exported so consumers can distinguish secret-resolution failure from
44
+ // network/credential failure in custom server-side auth flows.
45
+ export class ClientSecretResolutionError extends Error {
46
+ code;
47
+ name = 'ClientSecretResolutionError';
48
+ constructor(code) {
49
+ super('client_secret_binding_unavailable');
50
+ this.code = code;
51
+ }
52
+ }
53
+ //# sourceMappingURL=fail-closed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fail-closed.js","sourceRoot":"","sources":["../../src/utils/fail-closed.ts"],"names":[],"mappings":"AAeA,uEAAuE;AACvE,MAAM,gBAAgB,GAA2B;IAChD,eAAe,EAAE,mBAAmB;IACpC,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,GAAG;CACZ,CAAA;AAED,SAAS,WAAW,CAAC,OAA4B;IAChD,MAAM,MAAM,GAAG,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IACnD,OAAO,MAAM;SACX,KAAK,CAAC,GAAG,CAAC;SACV,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAA;AACnF,CAAC;AAED,uEAAuE;AACvE,6EAA6E;AAC7E,8EAA8E;AAC9E,sEAAsE;AACtE,MAAM,UAAU,mCAAmC,CAAC,MAAiC;IACpF,IAAI,CAAC;QACJ,OAAO,CAAC,KAAK,CAAC,oDAAoD,EAAE;YACnE,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;SACjB,CAAC,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACR,qEAAqE;IACtE,CAAC;AACF,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC9C,OAAiB,EACjB,MAAkC;IAElC,IAAI,MAAM;QAAE,mCAAmC,CAAC,MAAM,CAAC,CAAA;IACvD,IAAI,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,OAAO,QAAQ,CAAC,IAAI,CACnB,EAAE,KAAK,EAAE,0BAA0B,EAAE,EACrC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,OAAO,CAAC,gBAA+B,CAAC,EAAE,CACtE,CAAA;IACF,CAAC;IAED,OAAO,IAAI,QAAQ,CAAC,qBAAqB,EAAE;QAC1C,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,IAAI,OAAO,CAAC;YACpB,GAAG,gBAAgB;YACnB,cAAc,EAAE,2BAA2B;SAC3C,CAAC;KACF,CAAC,CAAA;AACH,CAAC;AAED,8EAA8E;AAC9E,uEAAuE;AACvE,+DAA+D;AAC/D,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IAEzB;IADV,IAAI,GAAG,6BAA6B,CAAA;IACtD,YAA4B,IAAqC;QAChE,KAAK,CAAC,mCAAmC,CAAC,CAAA;QADf,SAAI,GAAJ,IAAI,CAAiC;IAEjE,CAAC;CACD"}
@@ -1,7 +1,11 @@
1
1
  export { type AuthAnalyticsProvider, type AuthFailureEventInput, type AuthSuccessEventInput, authSuccessEventName, emitAuthEvent, emitAuthFailureEvent, emitAuthSuccessEvent, normalizeAuthFailureReason, normalizeAuthProvider, trackAuthEvent, } from './analytics.js';
2
+ export type { ClientSecretLiteral, ClientSecretResolutionErrorCode, ClientSecretResult, } from './client-secret.js';
3
+ export { isClientSecretLiteral, literalClientSecret } from './client-secret.js';
4
+ export { buildAuthCspPolicy, type CspHash, extractInlineSources, hasCspMeta, injectAuthCsp, sha256CspHash, } from './csp.js';
5
+ export { ClientSecretResolutionError } from './fail-closed.js';
2
6
  export { type BridgeMigrationOptions, type BridgeMigrationStatement, buildLegacyIdentitySubjectCanary, buildLegacyIdentitySubjectMigration, canonicalIdentitySubject, extractIdentityId, findLegacyIdentitySubjects, IDENTITY_SUB_FORMAT_DRIFT, IDENTITY_SUBJECT_PREFIX, IdentitySubFormatDriftError, normalizeIdentitySubject, rewriteLegacyIdentitySubjects, } from './identity.js';
3
7
  export { signIdentityCookie, verifyIdentityCookie } from './identity-cookie.js';
4
- export { getAuthorizationUrl, getMagicLinkUrl, getPasswordLoginUrl } from './openauth.js';
8
+ export { EXCHANGE_SECRET_FAILURE, type ExchangeSecretFailure, getAuthorizationUrl, getMagicLinkUrl, getPasswordLoginUrl, } from './openauth.js';
5
9
  export { createPKCEChallenge, generatePKCE } from './pkce.js';
6
10
  export { generateState, verifyState } from './state.js';
7
11
  export type { VerifiedClaims } from './tokens.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACN,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AACvB,YAAY,EACX,mBAAmB,EACnB,+BAA+B,EAC/B,kBAAkB,GAClB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACN,kBAAkB,EAClB,KAAK,OAAO,EACZ,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,GACb,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EACN,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EACN,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,eAAe,EACf,mBAAmB,GACnB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
@@ -1,7 +1,10 @@
1
1
  export { authSuccessEventName, emitAuthEvent, emitAuthFailureEvent, emitAuthSuccessEvent, normalizeAuthFailureReason, normalizeAuthProvider, trackAuthEvent, } from './analytics.js';
2
+ export { isClientSecretLiteral, literalClientSecret } from './client-secret.js';
3
+ export { buildAuthCspPolicy, extractInlineSources, hasCspMeta, injectAuthCsp, sha256CspHash, } from './csp.js';
4
+ export { ClientSecretResolutionError } from './fail-closed.js';
2
5
  export { buildLegacyIdentitySubjectCanary, buildLegacyIdentitySubjectMigration, canonicalIdentitySubject, extractIdentityId, findLegacyIdentitySubjects, IDENTITY_SUB_FORMAT_DRIFT, IDENTITY_SUBJECT_PREFIX, IdentitySubFormatDriftError, normalizeIdentitySubject, rewriteLegacyIdentitySubjects, } from './identity.js';
3
6
  export { signIdentityCookie, verifyIdentityCookie } from './identity-cookie.js';
4
- export { getAuthorizationUrl, getMagicLinkUrl, getPasswordLoginUrl } from './openauth.js';
7
+ export { EXCHANGE_SECRET_FAILURE, getAuthorizationUrl, getMagicLinkUrl, getPasswordLoginUrl, } from './openauth.js';
5
8
  export { createPKCEChallenge, generatePKCE } from './pkce.js';
6
9
  export { generateState, verifyState } from './state.js';
7
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAGN,gCAAgC,EAChC,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AAMvB,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACN,kBAAkB,EAElB,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,GACb,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAGN,gCAAgC,EAChC,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EACN,uBAAuB,EAEvB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,GACnB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import type { TokenExchangeResult } from '../types.js';
2
+ import { type ClientSecretResolutionErrorCode } from './client-secret.js';
2
3
  export interface AuthFlow {
3
4
  verifier: string;
4
5
  challenge: string;
@@ -24,6 +25,11 @@ export declare function getMagicLinkUrl(_context: {
24
25
  }): string;
25
26
  export declare function getForgotPasswordUrl(): string;
26
27
  export declare function getResetPasswordUrl(): string;
27
- export declare function exchangeCode(callbackUrl: URL, code: string, codeVerifier: string, providerFromCookie?: TokenExchangeResult['provider']): Promise<TokenExchangeResult | null>;
28
+ export interface ExchangeSecretFailure {
29
+ readonly secretFailed: true;
30
+ readonly code: ClientSecretResolutionErrorCode;
31
+ }
32
+ export declare const EXCHANGE_SECRET_FAILURE: ExchangeSecretFailure;
33
+ export declare function exchangeCode(callbackUrl: URL, code: string, codeVerifier: string, providerFromCookie?: TokenExchangeResult['provider'], request?: Request): Promise<TokenExchangeResult | ExchangeSecretFailure | null>;
28
34
  export declare function revokeToken(refreshToken: string, clientId?: string, clientSecret?: string): Promise<void>;
29
35
  //# sourceMappingURL=openauth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openauth.d.ts","sourceRoot":"","sources":["../../src/utils/openauth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAOtD,MAAM,WAAW,QAAQ;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAY7E;AAED,wBAAsB,mBAAmB,CACxC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,EACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAqB/D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAGlE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAG9D;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAG7C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAG5C;AAYD,wBAAsB,YAAY,CACjC,WAAW,EAAE,GAAG,EAChB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,kBAAkB,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAClD,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAmDrC;AAED,wBAAsB,WAAW,CAChC,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
1
+ {"version":3,"file":"openauth.d.ts","sourceRoot":"","sources":["../../src/utils/openauth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EACN,KAAK,+BAA+B,EAGpC,MAAM,oBAAoB,CAAA;AAM3B,MAAM,WAAW,QAAQ;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAY7E;AAED,wBAAsB,mBAAmB,CACxC,OAAO,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,EACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAqB/D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAGlE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAG9D;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAG7C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAG5C;AAgBD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,+BAA+B,CAAA;CAC9C;AAID,eAAO,MAAM,uBAAuB,EAAE,qBAGpC,CAAA;AAEF,wBAAsB,YAAY,CACjC,WAAW,EAAE,GAAG,EAChB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,kBAAkB,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,EACpD,OAAO,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,mBAAmB,GAAG,qBAAqB,GAAG,IAAI,CAAC,CAsD7D;AAED,wBAAsB,WAAW,CAChC,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -1,5 +1,5 @@
1
1
  import { getConfig } from '../config-store.js';
2
- import { createClientSecretBasicAuthHeader, resolveClientSecret } from './client-secret.js';
2
+ import { createClientSecretBasicAuthHeader, resolveClientSecret, } from './client-secret.js';
3
3
  import { resolveInternalIssuer } from './issuer.js';
4
4
  import { generatePKCE } from './pkce.js';
5
5
  import { generateState } from './state.js';
@@ -58,17 +58,26 @@ function tokenResponseHasTokens(data) {
58
58
  return (typeof data.access_token === 'string' &&
59
59
  typeof data.refresh_token === 'string');
60
60
  }
61
- export async function exchangeCode(callbackUrl, code, codeVerifier, providerFromCookie) {
61
+ // Backward-compatible exported sentinel for callers that only need a bounded
62
+ // representative failure. Runtime exchangeCode results carry the actual code.
63
+ export const EXCHANGE_SECRET_FAILURE = Object.freeze({
64
+ secretFailed: true,
65
+ code: 'BINDING_REJECTED',
66
+ });
67
+ export async function exchangeCode(callbackUrl, code, codeVerifier, providerFromCookie, request) {
62
68
  const options = getConfig();
63
69
  const issuer = resolveInternalIssuer(options);
64
70
  const clientId = options.clientId || callbackUrl.hostname;
65
71
  const redirectUri = new URL(options.callbackPath, callbackUrl.origin).toString();
66
- const clientSecret = await resolveClientSecret(options.clientSecret);
72
+ const secretResult = await resolveClientSecret(options.clientSecret, request);
73
+ if (!secretResult.ok) {
74
+ return Object.freeze({ secretFailed: true, code: secretResult.code });
75
+ }
67
76
  const headers = {
68
77
  'Content-Type': 'application/x-www-form-urlencoded',
69
78
  };
70
- if (clientSecret) {
71
- headers.Authorization = createClientSecretBasicAuthHeader(clientId, clientSecret);
79
+ if (secretResult.secret) {
80
+ headers.Authorization = createClientSecretBasicAuthHeader(clientId, secretResult.secret);
72
81
  }
73
82
  const body = new URLSearchParams({
74
83
  grant_type: 'authorization_code',
@@ -1 +1 @@
1
- {"version":3,"file":"openauth.js","sourceRoot":"","sources":["../../src/utils/openauth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openauth.js","sourceRoot":"","sources":["../../src/utils/openauth.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-labs/auth",
3
- "version": "0.10.0",
3
+ "version": "0.10.3",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {
@@ -57,7 +57,7 @@
57
57
  "typescript": "^5.8.0",
58
58
  "vite": "^7.3.5",
59
59
  "vitest": "^4.1.8",
60
- "@growth-labs/analytics": "0.14.6"
60
+ "@growth-labs/analytics": "0.15.0"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsc",
@@ -1,4 +1,5 @@
1
1
  import type { ResolvedAuthOptions } from './options.js'
2
+ import { rehydrateClientSecretLiteral } from './utils/client-secret-literal.js'
2
3
  import { isEnvMarker, resolveEnvMarker } from './utils/env-marker.js'
3
4
 
4
5
  let _config: ResolvedAuthOptions | null = null
@@ -13,7 +14,7 @@ export function getConfig(): ResolvedAuthOptions {
13
14
  __GL_AUTH_CONFIG__?: ResolvedAuthOptions
14
15
  }
15
16
  ).__GL_AUTH_CONFIG__
16
- const base = runtimeConfig ?? _config
17
+ const base = runtimeConfig ? rehydrateRuntimeConfig(runtimeConfig) : _config
17
18
  if (!base) {
18
19
  throw new Error(
19
20
  '@growth-labs/auth: integration not configured. Add auth() to your astro.config.mjs integrations array.',
@@ -22,6 +23,17 @@ export function getConfig(): ResolvedAuthOptions {
22
23
  return resolveEnvMarkers(base)
23
24
  }
24
25
 
26
+ function rehydrateRuntimeConfig(base: ResolvedAuthOptions): ResolvedAuthOptions {
27
+ // The Vite global is emitted only after authOptionsSchema accepts a
28
+ // WeakSet-issued literal at build time. Treat the structural wire shape as
29
+ // transport encoding here, then re-issue the runtime object; the resolver
30
+ // itself never trusts the structural marker.
31
+ const clientSecret = rehydrateClientSecretLiteral(base.clientSecret)
32
+ return clientSecret === base.clientSecret
33
+ ? base
34
+ : ({ ...base, clientSecret } as ResolvedAuthOptions)
35
+ }
36
+
25
37
  /**
26
38
  * Resolves any `"env:VAR_NAME"` markers on auth-tuple fields against the
27
39
  * runtime env stashed by `src/runtime-bridge.ts`. The bridge is loaded
package/src/index.ts CHANGED
@@ -2,6 +2,10 @@ import { _setConfig } from './config-store.js'
2
2
  import type { AuthOptions } from './options.js'
3
3
  import { authOptionsSchema } from './options.js'
4
4
  import type { AuthRenderers } from './types.js'
5
+ import {
6
+ isClientSecretLiteral,
7
+ serializeClientSecretLiteral,
8
+ } from './utils/client-secret-literal.js'
5
9
 
6
10
  interface AstroIntegration {
7
11
  name: string
@@ -72,7 +76,13 @@ function isValidRendererSpec(value: unknown): value is { module: string; export:
72
76
  export default function auth(userOptions: AuthOptions): AstroIntegration {
73
77
  const options = authOptionsSchema.parse(userOptions)
74
78
  _setConfig(options)
75
- const serializableOptions = { ...options, renderers: {} }
79
+ const serializableOptions = {
80
+ ...options,
81
+ clientSecret: isClientSecretLiteral(options.clientSecret)
82
+ ? serializeClientSecretLiteral(options.clientSecret)
83
+ : options.clientSecret,
84
+ renderers: {},
85
+ }
76
86
 
77
87
  return {
78
88
  name: '@growth-labs/auth',
@@ -3,6 +3,7 @@ import '../runtime-bridge.js'
3
3
  import type { User } from '../types.js'
4
4
  import { resolveClientSecret } from '../utils/client-secret.js'
5
5
  import { getCookie, setTokenCookies } from '../utils/cookies.js'
6
+ import { secretResolutionFailureResponse } from '../utils/fail-closed.js'
6
7
  import { resolveInternalIssuer } from '../utils/issuer.js'
7
8
  import { refreshTokens, verifyAccessToken } from '../utils/tokens.js'
8
9
 
@@ -21,6 +22,27 @@ export async function onRequest(
21
22
  // 2. Attempt to authenticate
22
23
  let user: User | null = null
23
24
  let refreshed: Awaited<ReturnType<typeof refreshTokens>> = null
25
+ let resolvedClientSecret: string | undefined
26
+
27
+ // A request carrying a refresh token can enter the confidential-client
28
+ // refresh path if access-token verification fails. Resolve that request's
29
+ // configured secret before verification so a cold JWKS lookup can never
30
+ // precede discovery of a broken binding. The module-private Request-keyed
31
+ // resolver cache makes this the single read, and the exact bytes are reused
32
+ // below. A refresh token explicitly opts the request into this fail-closed
33
+ // confidential-session invariant even when its access token would verify;
34
+ // an access-token-only request never needs or reads the client secret.
35
+ if (refreshToken && options.clientSecret !== undefined) {
36
+ const secretResult = await resolveClientSecret(options.clientSecret, request)
37
+ if (!secretResult.ok) {
38
+ context.locals.user = null
39
+ return secretResolutionFailureResponse(request, {
40
+ surface: 'middleware_refresh',
41
+ code: secretResult.code,
42
+ })
43
+ }
44
+ resolvedClientSecret = secretResult.secret ?? undefined
45
+ }
24
46
 
25
47
  if (accessToken) {
26
48
  const verified = await verifyAccessToken(issuer, accessToken, { audience: options.resource })
@@ -39,8 +61,13 @@ export async function onRequest(
39
61
 
40
62
  if (!user && refreshToken) {
41
63
  const clientId = options.clientId || context.url.hostname
42
- const clientSecret = await resolveClientSecret(options.clientSecret)
43
- refreshed = await refreshTokens(issuer, refreshToken, clientId, clientSecret, options.resource)
64
+ refreshed = await refreshTokens(
65
+ issuer,
66
+ refreshToken,
67
+ clientId,
68
+ resolvedClientSecret,
69
+ options.resource,
70
+ )
44
71
  if (refreshed) {
45
72
  user = {
46
73
  id: refreshed.claims.sub,
@@ -66,7 +93,7 @@ export async function onRequest(
66
93
  // 5. Continue to the page
67
94
  const response = await next()
68
95
 
69
- // 6. If tokens were refreshed or response must be modified, clone and apply changes
96
+ // 6. If tokens were refreshed or the response must carry no-store, clone and apply.
70
97
  if (refreshed || context.locals.synthetic === true) {
71
98
  const modifiedResponse = new Response(response.body, {
72
99
  status: response.status,
package/src/options.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import type { AuthRenderers } from './types.js'
3
+ import type { ClientSecretLiteral } from './utils/client-secret-literal.js'
4
+ import { isClientSecretLiteral } from './utils/client-secret-literal.js'
3
5
 
4
6
  // String accepted as either a literal URL or an "env:VAR_NAME" marker that
5
7
  // gets resolved against the Worker runtime env at request time. See
@@ -27,7 +29,20 @@ export const authOptionsSchema = z.object({
27
29
 
28
30
  // ─── Client identity ───
29
31
  clientId: stringOrEnvMarker.optional(),
30
- clientSecret: z.string().optional(),
32
+ // clientSecret accepts either a binding name (string resolved at request time
33
+ // against the Worker runtime env) or an explicit literalClientSecret() value
34
+ // (local-dev only; embedded in the serialized config). A bare string is
35
+ // always treated as a binding name — fail closed when the binding is absent.
36
+ // An empty or whitespace-only binding name is schema-rejected; it can never
37
+ // resolve to a usable secret and must not silently become a public client.
38
+ clientSecret: z
39
+ .union([
40
+ z.string().refine((v) => v.trim().length > 0, {
41
+ message: 'clientSecret binding name must not be empty or whitespace-only',
42
+ }),
43
+ z.custom<ClientSecretLiteral>(isClientSecretLiteral),
44
+ ])
45
+ .optional(),
31
46
  resource: urlOrEnvMarker,
32
47
 
33
48
  // ─── Cookie configuration ───