@growth-labs/auth 0.1.3 → 0.2.1

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 (40) hide show
  1. package/README.md +132 -34
  2. package/dist/client/login-page.d.ts +16 -1
  3. package/dist/client/login-page.d.ts.map +1 -1
  4. package/dist/client/login-page.js +20 -1
  5. package/dist/client/login-page.js.map +1 -1
  6. package/dist/middleware/auth.js +2 -2
  7. package/dist/middleware/auth.js.map +1 -1
  8. package/dist/routes/callback.d.ts.map +1 -1
  9. package/dist/routes/callback.js +27 -4
  10. package/dist/routes/callback.js.map +1 -1
  11. package/dist/routes/forgot-password.d.ts +12 -1
  12. package/dist/routes/forgot-password.d.ts.map +1 -1
  13. package/dist/routes/forgot-password.js +14 -54
  14. package/dist/routes/forgot-password.js.map +1 -1
  15. package/dist/routes/login.d.ts.map +1 -1
  16. package/dist/routes/login.js +21 -27
  17. package/dist/routes/login.js.map +1 -1
  18. package/dist/routes/reset-password.d.ts +10 -0
  19. package/dist/routes/reset-password.d.ts.map +1 -1
  20. package/dist/routes/reset-password.js +12 -44
  21. package/dist/routes/reset-password.js.map +1 -1
  22. package/dist/types.d.ts +18 -14
  23. package/dist/types.d.ts.map +1 -1
  24. package/dist/utils/cookies.d.ts +18 -0
  25. package/dist/utils/cookies.d.ts.map +1 -1
  26. package/dist/utils/cookies.js +27 -0
  27. package/dist/utils/cookies.js.map +1 -1
  28. package/dist/utils/index.d.ts +4 -2
  29. package/dist/utils/index.d.ts.map +1 -1
  30. package/dist/utils/index.js +3 -1
  31. package/dist/utils/index.js.map +1 -1
  32. package/dist/utils/openauth.d.ts +50 -5
  33. package/dist/utils/openauth.d.ts.map +1 -1
  34. package/dist/utils/openauth.js +70 -18
  35. package/dist/utils/openauth.js.map +1 -1
  36. package/dist/utils/tokens.d.ts +5 -8
  37. package/dist/utils/tokens.d.ts.map +1 -1
  38. package/dist/utils/tokens.js +54 -24
  39. package/dist/utils/tokens.js.map +1 -1
  40. package/package.json +76 -58
package/README.md CHANGED
@@ -2,47 +2,94 @@
2
2
 
3
3
  OpenAuth **client** package for Astro + Cloudflare. Verifies JWTs, manages session cookies, gates protected paths, and injects login/logout/callback routes.
4
4
 
5
- **This is a client only.** It does NOT run an OpenAuth issuer. The issuer is a separate Worker — see `apps/auth-server/`.
5
+ **This is a client only.** The OpenAuth issuer is a separate Worker — point this package at any standards-compliant OpenAuth issuer.
6
6
 
7
7
  **Identity, not authorization.** Answers "who is this user?" — not "what can they access?" Roles, entitlements, and premium checks are consumer concerns (e.g. `@fulcrum/auth`).
8
8
 
9
+ ## Live issuer
10
+
11
+ The canonical issuer for Fulcrum and Growth Labs sites is the standalone identity-platform at `https://auth.fulcrum-labs.com`. Discovery is at `/.well-known/oauth-authorization-server`:
12
+
13
+ ```
14
+ issuer: https://auth.fulcrum-labs.com
15
+ authorization_endpoint: https://auth.fulcrum-labs.com/authorize
16
+ token_endpoint: https://auth.fulcrum-labs.com/token
17
+ revocation_endpoint: https://auth.fulcrum-labs.com/revoke
18
+ jwks_uri: https://auth.fulcrum-labs.com/.well-known/jwks.json
19
+ ```
20
+
21
+ The repo's `apps/auth-server/` is a **reference sample** for bootstrapping new issuer Workers — it is not the canonical issuer.
22
+
9
23
  ## Config
10
24
 
11
25
  ```typescript
12
26
  import auth from '@growth-labs/auth'
13
27
 
14
28
  auth({
15
- issuer: 'https://auth.fedweek.com', // OpenAuth issuer URL (separate Worker)
16
- cookiePrefix: 'fedweek', // → fedweek_at, fedweek_rt cookies
17
- sessionCookieDomain: '.fedweek.com', // Cross-subdomain sharing (optional)
18
- gatedPaths: ['/premium/*', '/account/*'], // Glob patterns for protected routes
19
- loginPath: '/login', // Redirect target for unauthenticated users
29
+ issuer: 'https://auth.fulcrum-labs.com', // OpenAuth issuer URL (separate Worker)
30
+ cookiePrefix: 'fronts', // → fronts_at, fronts_rt cookies
31
+ sessionCookieDomain: '.fronts.co', // Cross-subdomain sharing (optional)
32
+ gatedPaths: ['/premium/*', '/account/*'], // Glob patterns for protected routes
33
+ loginPath: '/login', // Redirect target for unauthenticated users
20
34
  logoutRedirect: '/',
21
35
  callbackPath: '/api/auth/callback',
36
+ forgotPasswordPath: '/forgot-password', // Auto-injected when password provider is enabled
37
+ resetPasswordPath: '/reset-password',
22
38
  providers: ['google', 'password', 'email-code'],
23
39
  loginPage: {
24
40
  title: 'Sign in',
25
- brandName: 'FEDweek',
26
- logoUrl: 'https://media.fedweek.com/logos/header.png',
41
+ brandName: 'fronts.co',
42
+ logoUrl: 'https://media.fronts.co/logo.png',
27
43
  },
28
- accessTokenMaxAge: 900, // 15 min (seconds)
29
- refreshTokenMaxAge: 2592000, // 30 days
44
+ accessTokenMaxAge: 900, // 15 min (seconds)
45
+ refreshTokenMaxAge: 2592000, // 30 days
30
46
  })
31
47
  ```
32
48
 
33
49
  ## What It Injects
34
50
 
35
51
  **Middleware** (runs on every request):
52
+
36
53
  1. Reads `{cookiePrefix}_at` and `{cookiePrefix}_rt` cookies
37
- 2. Verifies access token JWT against issuer's JWKS (`{issuer}/.well-known/jwks.json`, cached in Worker memory)
54
+ 2. Verifies access token JWT against issuer's JWKS (`{issuer}/.well-known/jwks.json`, cached/refreshed by `jose.createRemoteJWKSet`)
38
55
  3. If expired but refresh token valid → silent refresh, set new cookies
39
56
  4. Populates `context.locals.user` as `AuthenticatedUser | UnauthenticatedUser`
40
57
  5. If path matches `gatedPaths` and user unauthenticated → redirect to `loginPath`
41
58
 
42
59
  **Routes:**
43
- - `GET /login` — default login page with provider buttons; `email-code` redirects into the issuer-hosted code flow (overridable by consumer)
44
- - `GET /logout` — clears cookies, revokes refresh token at issuer, redirects
45
- - `GET /api/auth/callback` — OAuth callback handler (PKCE code exchange, cookie setting)
60
+
61
+ - `GET /login` — default login page with provider buttons. Each button is a link to `{issuer}/authorize?provider=...` (Google, password, and email-code via OpenAuth's `code` provider). Override by creating `src/pages/login.astro`.
62
+ - `GET /logout` — clears cookies, revokes refresh token at `{issuer}/revoke`, redirects.
63
+ - `GET /api/auth/callback` — OAuth callback handler (PKCE code exchange, cookie setting, analytics emission).
64
+ - `GET /forgot-password` — thin 302 to `{issuer}/forgot-password` (issuer owns the UI). Only injected when the `password` provider is enabled.
65
+ - `GET /reset-password?token=...` — thin 302 to `{issuer}/reset-password?token=...`. Only injected when the `password` provider is enabled.
66
+
67
+ ## Subject claims contract
68
+
69
+ The package expects an OpenAuth-shaped access token. The subject payload lives under `properties` in the JWT and contains:
70
+
71
+ | Claim | Type | Notes |
72
+ |-------------|---------|---------------------------------------------|
73
+ | `userId` | string | Stable user identifier |
74
+ | `email` | string | |
75
+ | `name` | string | Optional |
76
+ | `image` | string | Optional |
77
+ | `isNewUser` | boolean | Whether this user was just created |
78
+
79
+ The JWT's top-level `sub` is an OpenAuth-internal subject hash (`{type}:{sha1}`) and is **never** the user id.
80
+
81
+ ## /token response contract
82
+
83
+ ```jsonc
84
+ // POST {issuer}/token
85
+ {
86
+ "access_token": "<jwt>",
87
+ "refresh_token": "<opaque>",
88
+ "expires_in": 900
89
+ }
90
+ ```
91
+
92
+ The package does **not** read `is_new_user` or `provider` from the /token response — those are not part of the standalone identity-platform contract. `isNewUser` comes from the JWT subject claims; provider attribution comes from a `{cookiePrefix}_provider` cookie set when the user clicked a provider button.
46
93
 
47
94
  ## Using `context.locals.user`
48
95
 
@@ -61,43 +108,94 @@ if (user.authenticated) {
61
108
 
62
109
  ```typescript
63
110
  interface AuthenticatedUser {
64
- id: string; email: string; name?: string; image?: string; authenticated: true
111
+ id: string
112
+ email: string
113
+ name?: string
114
+ image?: string
115
+ authenticated: true
65
116
  }
66
117
  interface UnauthenticatedUser { authenticated: false }
67
118
  type User = AuthenticatedUser | UnauthenticatedUser
68
119
  // Available on context.locals.user (Astro.locals.user in .astro files)
69
120
  ```
70
121
 
71
- ## Wrangler Bindings
122
+ ## Wrangler bindings
72
123
 
73
- None required by this package. The issuer needs KV + D1 — see `apps/auth-server/README.md`.
124
+ None required by this package. The issuer needs KV + D1 — see `apps/auth-server/README.md` (reference sample) or your standalone identity-platform's deployment docs.
74
125
 
75
- ## Integration Order
126
+ ## Integration order
76
127
 
77
128
  Auth middleware MUST run before consent and analytics middleware. List `auth()` first in `astro.config.mjs` integrations array.
78
129
 
79
- ## Issuer Unreachable (Cold Start)
130
+ ## Issuer unreachable (cold start)
80
131
 
81
- JWKS cached in Worker memory. If issuer unreachable on cold start:
82
- - **Non-gated paths:** fail open — `user = { authenticated: false }`, page serves normally
83
- - **Gated paths:** fail closed — redirect to loginPath
132
+ JWKS is fetched lazily by `jose.createRemoteJWKSet` (cached + refreshed automatically when the key set rotates). If the issuer is unreachable on a first verification:
84
133
 
85
- ## OAuth Flow Summary
134
+ - **Non-gated paths:** fail open — `user = { authenticated: false }`, page serves normally.
135
+ - **Gated paths:** fail closed — middleware redirects to `loginPath`.
86
136
 
87
- 1. User clicks provider on login page → redirect to `{issuer}/authorize?response_type=code&...` with PKCE challenge
88
- 2. Issuer authenticates → redirects to `/api/auth/callback?code=...&state=...`
89
- 3. Callback exchanges code for tokens via PKCE, sets session cookies, redirects to original page
137
+ ## OAuth flow summary
90
138
 
91
- ## Analytics Integration
139
+ 1. User clicks a provider on the login page → browser navigates to `{issuer}/authorize?response_type=code&...` with PKCE challenge. The login page's inline JS records the chosen provider in `{cookiePrefix}_provider`.
140
+ 2. Issuer authenticates → redirects to `/api/auth/callback?code=...&state=...`.
141
+ 3. Callback exchanges code for tokens via PKCE, decodes the JWT to read subject claims, emits `signup_completed` (if `isNewUser`) or `login_completed`, sets session cookies, redirects to original page.
92
142
 
93
- Optional peer dependency on `@growth-labs/analytics`. If installed, emits `login_completed` / `signup_completed` events automatically. If not installed, silently skipped.
143
+ ## Password / forgot / reset
94
144
 
95
- ## Key Patterns
145
+ Password, forgot-password, and reset-password are **issuer-owned flows**. The package does NOT implement password verification locally:
96
146
 
97
- - Virtual module: `virtual:growth-labs/auth/config` (slash, not hyphen)
98
- - Cookie names: `{cookiePrefix}_at`, `{cookiePrefix}_rt`, `{cookiePrefix}_pkce`, `{cookiePrefix}_state`, `{cookiePrefix}_redirect`
147
+ - **Login with password** → the package's login page renders a button that 302s the user into `{issuer}/authorize?provider=password&...`. The issuer hosts the password UI.
148
+ - **Forgot password** → `/forgot-password` (consumer URL) → 302 → `{issuer}/forgot-password`.
149
+ - **Reset password** → reset email link → `/reset-password?token=...` (consumer URL) → 302 → `{issuer}/reset-password?token=...`.
150
+
151
+ Consumer URLs exist so emails and bookmarks remain stable across issuer changes.
152
+
153
+ ## Email code (magic link)
154
+
155
+ The `email-code` package alias maps to OpenAuth's `code` provider. The login page links into `{issuer}/authorize?provider=code&...`; the issuer hosts the email/code UI and (per its own configuration) sends the one-time code via whatever transport it's wired to.
156
+
157
+ ## Analytics integration
158
+
159
+ Optional peer dependency on `@growth-labs/analytics`. If installed, the callback emits:
160
+
161
+ - `signup_completed` when the JWT's `properties.isNewUser` is `true`
162
+ - `login_completed` otherwise
163
+ - Both include `{ provider, userId }` properties
164
+
165
+ If `@growth-labs/analytics` is not installed, analytics calls are silently skipped.
166
+
167
+ ## Custom login pages
168
+
169
+ Use the exported helpers to build branded login pages while keeping the package's PKCE/state plumbing:
170
+
171
+ ```typescript
172
+ import {
173
+ createAuthFlow,
174
+ getAuthorizationUrl,
175
+ getPasswordLoginUrl,
176
+ getMagicLinkUrl,
177
+ setAuthFlowCookies,
178
+ setProviderCookie,
179
+ } from '@growth-labs/auth/utils'
180
+
181
+ // In a custom /login page or API route:
182
+ const flow = await createAuthFlow(Astro)
183
+ const google = await getAuthorizationUrl(Astro, 'google', redirectPath, flow)
184
+ const password = await getPasswordLoginUrl(Astro, redirectPath, flow)
185
+ const emailCode = await getMagicLinkUrl(Astro, redirectPath, flow)
186
+
187
+ // On the response, set the flow cookies the callback handler needs:
188
+ setAuthFlowCookies(response, options, flow.verifier, flow.state, redirectPath)
189
+ // Optionally record provider attribution server-side instead of via JS:
190
+ setProviderCookie(response, options, 'google')
191
+ ```
192
+
193
+ ## Key patterns
194
+
195
+ - Virtual module: `globalThis.__GL_AUTH_CONFIG__` is set at build time via `vite.define` for runtime access; module-level `_setConfig` is the dev-time fallback.
196
+ - Cookie names: `{cookiePrefix}_at`, `{cookiePrefix}_rt`, `{cookiePrefix}_pkce`, `{cookiePrefix}_state`, `{cookiePrefix}_redirect`, `{cookiePrefix}_provider`
99
197
  - PKCE flow with `code_verifier` / `code_challenge` (S256)
100
- - `email-code` is a client-side alias for the issuer's OpenAuth `code` provider
101
- - CSRF state parameter verified on callback
198
+ - CSRF state parameter verified on callback (constant-time compare)
102
199
  - No `aud` claim validation in this package — single client per issuer assumed
103
- - `.astro` component files ship as source, not compiled
200
+ - `email-code` is a client-side alias for the issuer's OpenAuth `code` provider
201
+ - All token endpoint URLs are constructed via `new URL('/path', issuer)` so issuer values with or without trailing slashes both work
@@ -1,2 +1,17 @@
1
- export declare function loginPageScript(): string;
1
+ /**
2
+ * Inline JS for the package's default login page.
3
+ *
4
+ * On a provider click:
5
+ * 1. Records the provider in `{cookiePrefix}_provider` so the callback
6
+ * handler can attribute analytics correctly. The standalone
7
+ * identity-platform issuer does not echo the provider back in the
8
+ * /token response, so we must remember it client-side.
9
+ * 2. Emits a `login_clicked` analytics event if `window.glAnalytics`
10
+ * is wired up (via `@growth-labs/analytics`).
11
+ *
12
+ * The provider cookie is intentionally NOT httpOnly — it must be writable
13
+ * from JS. Provider attribution is non-sensitive (just an analytics
14
+ * label) so this is an acceptable tradeoff.
15
+ */
16
+ export declare function loginPageScript(cookiePrefix: string): string;
2
17
  //# sourceMappingURL=login-page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-page.d.ts","sourceRoot":"","sources":["../../src/client/login-page.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,IAAI,MAAM,CAWxC"}
1
+ {"version":3,"file":"login-page.d.ts","sourceRoot":"","sources":["../../src/client/login-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAe5D"}
@@ -1,8 +1,27 @@
1
- export function loginPageScript() {
1
+ /**
2
+ * Inline JS for the package's default login page.
3
+ *
4
+ * On a provider click:
5
+ * 1. Records the provider in `{cookiePrefix}_provider` so the callback
6
+ * handler can attribute analytics correctly. The standalone
7
+ * identity-platform issuer does not echo the provider back in the
8
+ * /token response, so we must remember it client-side.
9
+ * 2. Emits a `login_clicked` analytics event if `window.glAnalytics`
10
+ * is wired up (via `@growth-labs/analytics`).
11
+ *
12
+ * The provider cookie is intentionally NOT httpOnly — it must be writable
13
+ * from JS. Provider attribution is non-sensitive (just an analytics
14
+ * label) so this is an acceptable tradeoff.
15
+ */
16
+ export function loginPageScript(cookiePrefix) {
17
+ // JSON.stringify safely escapes the prefix into a JS string literal.
18
+ const prefixLiteral = JSON.stringify(cookiePrefix);
2
19
  return `
3
20
  document.querySelectorAll('[data-provider]').forEach(function(el) {
4
21
  el.addEventListener('click', function() {
5
22
  var provider = el.getAttribute('data-provider');
23
+ var prefix = ${prefixLiteral};
24
+ document.cookie = prefix + '_provider=' + encodeURIComponent(provider) + '; max-age=600; path=/; samesite=lax; secure';
6
25
  if (window.glAnalytics) {
7
26
  window.glAnalytics.track('login_clicked', { provider: provider });
8
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"login-page.js","sourceRoot":"","sources":["../../src/client/login-page.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe;IAC9B,OAAO;;;;;;;;;GASL,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"login-page.js","sourceRoot":"","sources":["../../src/client/login-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB;IACnD,qEAAqE;IACrE,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAA;IAClD,OAAO;;;;uBAIe,aAAa;;;;;;;GAOjC,CAAA;AACH,CAAC"}
@@ -14,7 +14,7 @@ export async function onRequest(context, next) {
14
14
  const verified = await verifyAccessToken(options.issuer, accessToken);
15
15
  if (verified) {
16
16
  user = {
17
- id: verified.sub,
17
+ id: verified.userId,
18
18
  email: verified.email,
19
19
  name: verified.name,
20
20
  image: verified.image,
@@ -27,7 +27,7 @@ export async function onRequest(context, next) {
27
27
  refreshed = await refreshTokens(options.issuer, refreshToken, clientId);
28
28
  if (refreshed) {
29
29
  user = {
30
- id: refreshed.claims.sub,
30
+ id: refreshed.claims.userId,
31
31
  email: refreshed.claims.email,
32
32
  name: refreshed.claims.name,
33
33
  image: refreshed.claims.image,
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"AAMA,UAAU,eAAe;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;IACR,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAC/C;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqDrE"}
1
+ {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"AAMA,UAAU,eAAe;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;IACR,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAC/C;AAYD,wBAAsB,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqErE"}
@@ -3,6 +3,16 @@ import { analyticsAvailable, emitAuthEvent } from '../utils/analytics.js';
3
3
  import { clearAuthFlowCookies, getCookie, setTokenCookies } from '../utils/cookies.js';
4
4
  import { exchangeCode } from '../utils/openauth.js';
5
5
  import { verifyState } from '../utils/state.js';
6
+ const ALLOWED_PROVIDERS = new Set(['google', 'password', 'email-code', 'code']);
7
+ function normalizeProvider(value) {
8
+ if (!value)
9
+ return 'unknown';
10
+ if (!ALLOWED_PROVIDERS.has(value))
11
+ return 'unknown';
12
+ // `code` is the OpenAuth provider name; the package surfaces it as
13
+ // `email-code` for consumer-facing analytics consistency.
14
+ return value === 'code' ? 'email-code' : value;
15
+ }
6
16
  export async function GET(context) {
7
17
  const options = getConfig();
8
18
  const { url, request } = context;
@@ -18,11 +28,24 @@ export async function GET(context) {
18
28
  if (!codeVerifier) {
19
29
  return new Response('Missing PKCE verifier', { status: 400 });
20
30
  }
21
- // Exchange code for tokens
31
+ if (!code) {
32
+ return new Response('Missing authorization code', { status: 400 });
33
+ }
34
+ // Exchange code for tokens. The /token response from the standalone
35
+ // identity-platform issuer is { access_token, refresh_token } — it does
36
+ // NOT include is_new_user or provider. Both come from elsewhere.
22
37
  const tokenResponse = await exchangeCode(url, code, codeVerifier);
23
38
  if (!tokenResponse) {
24
39
  return Response.redirect(new URL(`${options.loginPath}?error=auth_failed`, url.origin).toString(), 302);
25
40
  }
41
+ // Provider attribution: read from the client-side flow cookie set when
42
+ // the user clicked the provider button. Falls back to 'unknown' if the
43
+ // caller (e.g. a custom login page) didn't set it.
44
+ const provider = normalizeProvider(getCookie(request, `${options.cookiePrefix}_provider`));
45
+ // isNewUser: derive from the access token's subject claims, NOT from a
46
+ // top-level token response field. The standalone issuer encodes
47
+ // isNewUser inside the subject payload (`properties.isNewUser`).
48
+ const isNewUser = tokenResponse.claims.isNewUser === true;
26
49
  // Build redirect response
27
50
  const redirectPath = getCookie(request, `${options.cookiePrefix}_redirect`) || '/';
28
51
  const response = Response.redirect(new URL(redirectPath, url.origin).toString(), 302);
@@ -36,10 +59,10 @@ export async function GET(context) {
36
59
  clearAuthFlowCookies(mutableResponse, options);
37
60
  // Emit analytics event (non-blocking)
38
61
  if (analyticsAvailable(context)) {
39
- const eventName = tokenResponse.isNewUser ? 'signup_completed' : 'login_completed';
62
+ const eventName = isNewUser ? 'signup_completed' : 'login_completed';
40
63
  const promise = emitAuthEvent(context, eventName, {
41
- provider: tokenResponse.provider,
42
- userId: tokenResponse.claims.sub,
64
+ provider,
65
+ userId: tokenResponse.claims.userId,
43
66
  });
44
67
  context.waitUntil?.(promise);
45
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,17 @@ interface ForgotPasswordContext {
2
2
  request: Request;
3
3
  url: URL;
4
4
  }
5
- export declare function GET(context: ForgotPasswordContext): Promise<Response>;
5
+ /**
6
+ * Thin proxy into the issuer-hosted forgot-password flow.
7
+ *
8
+ * Password (and forgot/reset) are issuer-owned flows: the standalone
9
+ * identity-platform issuer hosts the UI. This route only exists so that
10
+ * consumer URLs are stable (e.g. emails can deep-link to
11
+ * `https://site.example/forgot-password`). The route 302s to the
12
+ * issuer's hosted page.
13
+ *
14
+ * Returns 404 when the `password` provider isn't configured.
15
+ */
16
+ export declare function GET(_context: ForgotPasswordContext): Promise<Response>;
6
17
  export {};
7
18
  //# sourceMappingURL=forgot-password.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"forgot-password.d.ts","sourceRoot":"","sources":["../../src/routes/forgot-password.ts"],"names":[],"mappings":"AAGA,UAAU,qBAAqB;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;CACR;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAc3E"}
1
+ {"version":3,"file":"forgot-password.d.ts","sourceRoot":"","sources":["../../src/routes/forgot-password.ts"],"names":[],"mappings":"AAGA,UAAU,qBAAqB;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;CACR;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,GAAG,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAS5E"}
@@ -1,62 +1,22 @@
1
1
  import { getConfig } from '../config-store.js';
2
2
  import { getForgotPasswordUrl } from '../utils/openauth.js';
3
- export async function GET(context) {
3
+ /**
4
+ * Thin proxy into the issuer-hosted forgot-password flow.
5
+ *
6
+ * Password (and forgot/reset) are issuer-owned flows: the standalone
7
+ * identity-platform issuer hosts the UI. This route only exists so that
8
+ * consumer URLs are stable (e.g. emails can deep-link to
9
+ * `https://site.example/forgot-password`). The route 302s to the
10
+ * issuer's hosted page.
11
+ *
12
+ * Returns 404 when the `password` provider isn't configured.
13
+ */
14
+ export async function GET(_context) {
4
15
  const options = getConfig();
5
16
  if (!options.providers.includes('password')) {
6
17
  return new Response('Not found', { status: 404 });
7
18
  }
8
- const sent = context.url.searchParams.get('sent') !== null;
9
- const error = context.url.searchParams.get('error');
10
- const html = renderForgotPasswordPage(options, context.url, { sent, error });
11
- return new Response(html, {
12
- headers: { 'Content-Type': 'text/html; charset=utf-8' },
13
- });
14
- }
15
- function renderForgotPasswordPage(options, url, data) {
16
- const { loginPage } = options;
17
- const actionUrl = getForgotPasswordUrl();
18
- const redirectUri = new URL(options.forgotPasswordPath, url.origin).toString();
19
- const resetUri = new URL(options.resetPasswordPath, url.origin).toString();
20
- return `<!DOCTYPE html>
21
- <html lang="en">
22
- <head>
23
- <meta charset="utf-8">
24
- <meta name="viewport" content="width=device-width, initial-scale=1">
25
- <title>Forgot password</title>
26
- <style>
27
- body { font-family: system-ui, sans-serif; max-width: 400px; margin: 80px auto; padding: 0 20px; }
28
- h1 { text-align: center; }
29
- .logo { display: block; margin: 0 auto 16px; max-height: 48px; }
30
- .btn { display: block; width: 100%; padding: 12px; margin: 8px 0; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; cursor: pointer; text-align: center; text-decoration: none; box-sizing: border-box; }
31
- input[type="email"] { display: block; width: 100%; padding: 10px; margin: 8px 0; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; box-sizing: border-box; }
32
- .error { color: #c00; text-align: center; margin: 12px 0; }
33
- .success { color: #080; text-align: center; margin: 12px 0; }
34
- .brand { text-align: center; color: #666; margin-bottom: 4px; }
35
- .back-link { display: block; text-align: center; margin-top: 16px; font-size: 14px; color: #555; }
36
- p.hint { text-align: center; color: #666; font-size: 14px; }
37
- </style>
38
- </head>
39
- <body>
40
- ${loginPage.logoUrl ? `<img src="${loginPage.logoUrl}" alt="${loginPage.brandName || ''}" class="logo">` : ''}
41
- ${loginPage.brandName ? `<p class="brand">${loginPage.brandName}</p>` : ''}
42
- <h1>Forgot password</h1>
43
- ${data.error ? '<p class="error">Something went wrong. Please try again.</p>' : ''}
44
- ${data.sent
45
- ? `
46
- <p class="success">Check your email for a reset link.</p>
47
- <p class="hint">If you don't see it, check your spam folder.</p>
48
- `
49
- : `
50
- <p class="hint">Enter your email and we'll send you a link to reset your password.</p>
51
- <form method="POST" action="${actionUrl}">
52
- <input type="email" name="email" placeholder="Email address" required>
53
- <input type="hidden" name="redirect_uri" value="${redirectUri}">
54
- <input type="hidden" name="reset_uri" value="${resetUri}">
55
- <button type="submit" class="btn">Send reset link</button>
56
- </form>
57
- `}
58
- <a href="${options.loginPath}" class="back-link">Back to sign in</a>
59
- </body>
60
- </html>`;
19
+ const target = getForgotPasswordUrl();
20
+ return Response.redirect(target, 302);
61
21
  }
62
22
  //# sourceMappingURL=forgot-password.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"forgot-password.js","sourceRoot":"","sources":["../../src/routes/forgot-password.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAO3D,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,OAA8B;IACvD,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAE3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,CAAA;IAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IACnD,MAAM,IAAI,GAAG,wBAAwB,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAE5E,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACzB,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE;KACvD,CAAC,CAAA;AACH,CAAC;AAOD,SAAS,wBAAwB,CAChC,OAAqC,EACrC,GAAQ,EACR,IAA4B;IAE5B,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAC7B,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAA;IACxC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC9E,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAA;IAE1E,OAAO;;;;;;;;;;;;;;;;;;;;IAoBJ,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,SAAS,CAAC,OAAO,UAAU,SAAS,CAAC,SAAS,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE;IAC3G,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,oBAAoB,SAAS,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC,EAAE;;IAExE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,8DAA8D,CAAC,CAAC,CAAC,EAAE;IAElF,IAAI,CAAC,IAAI;QACR,CAAC,CAAC;;;GAGF;QACA,CAAC,CAAC;;kCAE6B,SAAS;;wDAEa,WAAW;qDACd,QAAQ;;;GAI5D;aACY,OAAO,CAAC,SAAS;;QAEtB,CAAA;AACR,CAAC"}
1
+ {"version":3,"file":"forgot-password.js","sourceRoot":"","sources":["../../src/routes/forgot-password.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAO3D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,QAA+B;IACxD,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAE3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACtC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/routes/login.ts"],"names":[],"mappings":"AAUA,UAAU,YAAY;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;CACR;AAMD,wBAAsB,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBlE"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/routes/login.ts"],"names":[],"mappings":"AAKA,UAAU,YAAY;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;CACR;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiClE"}
@@ -1,10 +1,7 @@
1
1
  import { loginPageScript } from '../client/login-page.js';
2
2
  import { getConfig } from '../config-store.js';
3
3
  import { setAuthFlowCookies } from '../utils/cookies.js';
4
- import { createAuthFlow, getAuthorizationUrl, getPasswordLoginUrl, } from '../utils/openauth.js';
5
- function _shouldShowForgotLink(options) {
6
- return options.providers.includes('password');
7
- }
4
+ import { createAuthFlow, getAuthorizationUrl } from '../utils/openauth.js';
8
5
  export async function GET(context) {
9
6
  const options = getConfig();
10
7
  const redirectPath = context.url.searchParams.get('redirect') || '/';
@@ -14,10 +11,21 @@ export async function GET(context) {
14
11
  const google = options.providers.includes('google')
15
12
  ? await getAuthorizationUrl(context, 'google', redirectPath, authFlow)
16
13
  : null;
14
+ const password = options.providers.includes('password')
15
+ ? await getAuthorizationUrl(context, 'password', redirectPath, authFlow)
16
+ : null;
17
17
  const emailCode = options.providers.includes('email-code')
18
18
  ? await getAuthorizationUrl(context, 'code', redirectPath, authFlow)
19
19
  : null;
20
- const html = renderLoginPage(options, { google, emailCode, redirectPath, error, reset, authFlow });
20
+ const html = renderLoginPage(options, {
21
+ google,
22
+ password,
23
+ emailCode,
24
+ redirectPath,
25
+ error,
26
+ reset,
27
+ authFlow,
28
+ });
21
29
  const response = new Response(html, {
22
30
  headers: { 'Content-Type': 'text/html; charset=utf-8' },
23
31
  });
@@ -26,8 +34,7 @@ export async function GET(context) {
26
34
  }
27
35
  function renderLoginPage(options, data) {
28
36
  const { loginPage, providers } = options;
29
- const hasPassword = providers.includes('password');
30
- const passwordUrl = getPasswordLoginUrl({ url: new URL(options.issuer) });
37
+ const cookiePrefix = options.cookiePrefix;
31
38
  return `<!DOCTYPE html>
32
39
  <html lang="en">
33
40
  <head>
@@ -41,11 +48,10 @@ function renderLoginPage(options, data) {
41
48
  .btn { display: block; width: 100%; padding: 12px; margin: 8px 0; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; cursor: pointer; text-align: center; text-decoration: none; box-sizing: border-box; }
42
49
  .btn-google { background: #4285f4; color: white; border: none; }
43
50
  .divider { text-align: center; margin: 20px 0; color: #888; }
44
- input[type="email"], input[type="password"] { display: block; width: 100%; padding: 10px; margin: 8px 0; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; box-sizing: border-box; }
45
51
  .error { color: #c00; text-align: center; margin: 12px 0; }
46
52
  .success { color: #080; text-align: center; margin: 12px 0; }
47
53
  .brand { text-align: center; color: #666; margin-bottom: 4px; }
48
- .forgot-link { display: block; text-align: right; font-size: 14px; margin: -4px 0 8px; color: #555; }
54
+ .forgot-link { display: block; text-align: center; font-size: 14px; margin: 12px 0; color: #555; }
49
55
  </style>
50
56
  </head>
51
57
  <body>
@@ -55,25 +61,13 @@ function renderLoginPage(options, data) {
55
61
  ${data.reset ? '<p class="success">Password reset successfully. Sign in with your new password.</p>' : ''}
56
62
  ${data.error ? '<p class="error">Authentication failed. Please try again.</p>' : ''}
57
63
  ${data.google ? `<a href="${data.google.url}" class="btn btn-google" data-provider="google">Sign in with Google</a>` : ''}
58
- ${data.google && (hasPassword || data.emailCode) ? '<div class="divider">or</div>' : ''}
59
- ${data.emailCode
60
- ? `<a href="${data.emailCode.url}" class="btn" data-provider="email-code">Sign in with email</a>`
61
- : ''}
62
- ${hasPassword
63
- ? `
64
- <form method="POST" action="${passwordUrl}">
65
- <input type="email" name="email" placeholder="Email address" required>
66
- <input type="password" name="password" placeholder="Password" required>
67
- <a href="${options.forgotPasswordPath}" class="forgot-link">Forgot password?</a>
68
- <input type="hidden" name="redirect_uri" value="${data.authFlow.redirectUri}">
69
- <input type="hidden" name="client_id" value="${data.authFlow.clientId}">
70
- <input type="hidden" name="state" value="${data.authFlow.state}">
71
- <input type="hidden" name="code_challenge" value="${data.authFlow.challenge}">
72
- <input type="hidden" name="code_challenge_method" value="S256">
73
- <button type="submit" class="btn" data-provider="password">Sign in with password</button>
74
- </form>`
64
+ ${data.google && (data.password || data.emailCode) ? '<div class="divider">or</div>' : ''}
65
+ ${data.password ? `<a href="${data.password.url}" class="btn" data-provider="password">Sign in with password</a>` : ''}
66
+ ${data.emailCode ? `<a href="${data.emailCode.url}" class="btn" data-provider="email-code">Sign in with email</a>` : ''}
67
+ ${providers.includes('password')
68
+ ? `<a href="${options.forgotPasswordPath}" class="forgot-link">Forgot password?</a>`
75
69
  : ''}
76
- <script>${loginPageScript()}</script>
70
+ <script>${loginPageScript(cookiePrefix)}</script>
77
71
  </body>
78
72
  </html>`;
79
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/routes/login.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/routes/login.ts"],"names":[],"mappings":"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"}