@growth-labs/auth 0.2.1 → 0.2.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.
- package/README.md +37 -131
- package/dist/client/login-page.d.ts +1 -16
- package/dist/client/login-page.d.ts.map +1 -1
- package/dist/client/login-page.js +1 -20
- package/dist/client/login-page.js.map +1 -1
- package/dist/middleware/auth.js +2 -2
- package/dist/middleware/auth.js.map +1 -1
- package/dist/routes/callback.d.ts.map +1 -1
- package/dist/routes/callback.js +8 -28
- package/dist/routes/callback.js.map +1 -1
- package/dist/routes/forgot-password.d.ts +1 -12
- package/dist/routes/forgot-password.d.ts.map +1 -1
- package/dist/routes/forgot-password.js +4 -14
- package/dist/routes/forgot-password.js.map +1 -1
- package/dist/routes/login.d.ts.map +1 -1
- package/dist/routes/login.js +39 -32
- package/dist/routes/login.js.map +1 -1
- package/dist/routes/reset-password.d.ts +0 -10
- package/dist/routes/reset-password.d.ts.map +1 -1
- package/dist/routes/reset-password.js +3 -16
- package/dist/routes/reset-password.js.map +1 -1
- package/dist/types.d.ts +14 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/cookies.d.ts +1 -19
- package/dist/utils/cookies.d.ts.map +1 -1
- package/dist/utils/cookies.js +4 -27
- package/dist/utils/cookies.js.map +1 -1
- package/dist/utils/index.d.ts +2 -4
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/openauth.d.ts +6 -51
- package/dist/utils/openauth.d.ts.map +1 -1
- package/dist/utils/openauth.js +20 -72
- package/dist/utils/openauth.js.map +1 -1
- package/dist/utils/tokens.d.ts +12 -5
- package/dist/utils/tokens.d.ts.map +1 -1
- package/dist/utils/tokens.js +34 -51
- package/dist/utils/tokens.js.map +1 -1
- package/dist/vite-plugin.d.ts +4 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +18 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +7 -21
package/README.md
CHANGED
|
@@ -2,94 +2,48 @@
|
|
|
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.**
|
|
5
|
+
**This is a client only.** It does NOT run an OpenAuth issuer. The canonical issuer runtime lives in `growth-labs/identity-platform`; `apps/auth-server/` in this repo is only a minimal sample Worker.
|
|
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
|
-
|
|
23
9
|
## Config
|
|
24
10
|
|
|
25
11
|
```typescript
|
|
26
12
|
import auth from '@growth-labs/auth'
|
|
27
13
|
|
|
28
14
|
auth({
|
|
29
|
-
issuer: 'https://auth.
|
|
30
|
-
cookiePrefix: '
|
|
31
|
-
sessionCookieDomain: '.
|
|
32
|
-
gatedPaths: ['/premium/*', '/account/*'],
|
|
33
|
-
loginPath: '/login',
|
|
15
|
+
issuer: 'https://auth.fedweek.com', // OpenAuth issuer URL (separate Worker)
|
|
16
|
+
cookiePrefix: 'fedweek', // → fedweek_at, fedweek_rt, fedweek_provider 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
|
|
34
20
|
logoutRedirect: '/',
|
|
35
21
|
callbackPath: '/api/auth/callback',
|
|
36
|
-
forgotPasswordPath: '/forgot-password', // Auto-injected when password provider is enabled
|
|
37
|
-
resetPasswordPath: '/reset-password',
|
|
38
22
|
providers: ['google', 'password', 'email-code'],
|
|
39
23
|
loginPage: {
|
|
40
24
|
title: 'Sign in',
|
|
41
|
-
brandName: '
|
|
42
|
-
logoUrl: 'https://media.
|
|
25
|
+
brandName: 'FEDweek',
|
|
26
|
+
logoUrl: 'https://media.fedweek.com/logos/header.png',
|
|
43
27
|
},
|
|
44
|
-
accessTokenMaxAge: 900,
|
|
45
|
-
refreshTokenMaxAge: 2592000,
|
|
28
|
+
accessTokenMaxAge: 900, // 15 min (seconds)
|
|
29
|
+
refreshTokenMaxAge: 2592000, // 30 days
|
|
46
30
|
})
|
|
47
31
|
```
|
|
48
32
|
|
|
49
33
|
## What It Injects
|
|
50
34
|
|
|
51
35
|
**Middleware** (runs on every request):
|
|
52
|
-
|
|
53
36
|
1. Reads `{cookiePrefix}_at` and `{cookiePrefix}_rt` cookies
|
|
54
|
-
2. Verifies access token JWT against issuer's JWKS (`{issuer}/.well-known/jwks.json`, cached
|
|
37
|
+
2. Verifies access token JWT against issuer's JWKS (`{issuer}/.well-known/jwks.json`, cached in Worker memory)
|
|
55
38
|
3. If expired but refresh token valid → silent refresh, set new cookies
|
|
56
39
|
4. Populates `context.locals.user` as `AuthenticatedUser | UnauthenticatedUser`
|
|
57
40
|
5. If path matches `gatedPaths` and user unauthenticated → redirect to `loginPath`
|
|
58
41
|
|
|
59
42
|
**Routes:**
|
|
60
|
-
|
|
61
|
-
- `GET /
|
|
62
|
-
- `GET /
|
|
63
|
-
- `GET /
|
|
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.
|
|
43
|
+
- `GET /login` — provider-selection page; `?provider=google|password|email-code` starts auth locally and redirects into the issuer's generic `/authorize` endpoint
|
|
44
|
+
- `GET /logout` — clears cookies, revokes refresh token at issuer, redirects
|
|
45
|
+
- `GET /api/auth/callback` — OAuth callback handler (PKCE code exchange, cookie setting)
|
|
46
|
+
- `GET /forgot-password` / `GET /reset-password` — local redirects to issuer-owned password UI
|
|
93
47
|
|
|
94
48
|
## Using `context.locals.user`
|
|
95
49
|
|
|
@@ -108,94 +62,46 @@ if (user.authenticated) {
|
|
|
108
62
|
|
|
109
63
|
```typescript
|
|
110
64
|
interface AuthenticatedUser {
|
|
111
|
-
id: string
|
|
112
|
-
email: string
|
|
113
|
-
name?: string
|
|
114
|
-
image?: string
|
|
115
|
-
authenticated: true
|
|
65
|
+
id: string; email: string; name?: string; image?: string; authenticated: true
|
|
116
66
|
}
|
|
117
67
|
interface UnauthenticatedUser { authenticated: false }
|
|
118
68
|
type User = AuthenticatedUser | UnauthenticatedUser
|
|
119
69
|
// Available on context.locals.user (Astro.locals.user in .astro files)
|
|
120
70
|
```
|
|
121
71
|
|
|
122
|
-
## Wrangler
|
|
72
|
+
## Wrangler Bindings
|
|
123
73
|
|
|
124
|
-
None required by this package. The issuer needs KV + D1 — see `apps/auth-server/README.md`
|
|
74
|
+
None required by this package. The issuer needs KV + D1 — see `growth-labs/identity-platform` for the production runtime shape, or `apps/auth-server/README.md` here for the minimal sample.
|
|
125
75
|
|
|
126
|
-
## Integration
|
|
76
|
+
## Integration Order
|
|
127
77
|
|
|
128
78
|
Auth middleware MUST run before consent and analytics middleware. List `auth()` first in `astro.config.mjs` integrations array.
|
|
129
79
|
|
|
130
|
-
## Issuer
|
|
80
|
+
## Issuer Unreachable (Cold Start)
|
|
131
81
|
|
|
132
|
-
JWKS
|
|
82
|
+
JWKS cached in Worker memory. If issuer unreachable on cold start:
|
|
83
|
+
- **Non-gated paths:** fail open — `user = { authenticated: false }`, page serves normally
|
|
84
|
+
- **Gated paths:** fail closed — redirect to loginPath
|
|
133
85
|
|
|
134
|
-
|
|
135
|
-
- **Gated paths:** fail closed — middleware redirects to `loginPath`.
|
|
86
|
+
## OAuth Flow Summary
|
|
136
87
|
|
|
137
|
-
|
|
88
|
+
1. User clicks a provider on the local login page → browser hits `/login?provider=...`
|
|
89
|
+
2. The local `/login` start route stores PKCE/state/redirect/provider cookies and redirects to `{issuer}/authorize?provider=...`
|
|
90
|
+
3. Issuer authenticates → redirects to `/api/auth/callback?code=...&state=...`
|
|
91
|
+
4. Callback exchanges code for tokens via PKCE, reads `properties.isNewUser` from the JWT payload with fallback to top-level `isNewUser` for legacy issuers, uses the `{cookiePrefix}_provider` auth-flow cookie set at login start for provider attribution, sets session cookies, and redirects to the original page
|
|
138
92
|
|
|
139
|
-
|
|
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.
|
|
93
|
+
## Analytics Integration
|
|
142
94
|
|
|
143
|
-
|
|
95
|
+
Optional peer dependency on `@growth-labs/analytics`. If installed, emits `login_completed` / `signup_completed` events automatically. If not installed, silently skipped. The event name is derived from the access-token JWT `isNewUser` claim, preferring `payload.properties.isNewUser` and falling back to top-level `payload.isNewUser` for legacy issuers. The event payload provider comes from the `{cookiePrefix}_provider` auth-flow cookie.
|
|
144
96
|
|
|
145
|
-
|
|
97
|
+
## Key Patterns
|
|
146
98
|
|
|
147
|
-
-
|
|
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.
|
|
99
|
+
- Virtual module: `virtual:growth-labs/auth/config` (slash, not hyphen)
|
|
196
100
|
- Cookie names: `{cookiePrefix}_at`, `{cookiePrefix}_rt`, `{cookiePrefix}_pkce`, `{cookiePrefix}_state`, `{cookiePrefix}_redirect`, `{cookiePrefix}_provider`
|
|
197
101
|
- PKCE flow with `code_verifier` / `code_challenge` (S256)
|
|
198
|
-
- CSRF state parameter verified on callback (constant-time compare)
|
|
199
|
-
- No `aud` claim validation in this package — single client per issuer assumed
|
|
200
102
|
- `email-code` is a client-side alias for the issuer's OpenAuth `code` provider
|
|
201
|
-
-
|
|
103
|
+
- CSRF state parameter verified on callback
|
|
104
|
+
- Access-token user claims live under `payload.properties.*`; the client falls back to top-level `payload.email`, `payload.name`, `payload.image`, and `payload.isNewUser` for legacy issuers
|
|
105
|
+
- Forgot/reset password routes are issuer-owned redirects, not local form UIs
|
|
106
|
+
- No `aud` claim validation in this package — single client per issuer assumed
|
|
107
|
+
- `.astro` component files ship as source, not compiled
|
|
@@ -1,17 +1,2 @@
|
|
|
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;
|
|
1
|
+
export declare function loginPageScript(): string;
|
|
17
2
|
//# 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
|
|
1
|
+
{"version":3,"file":"login-page.d.ts","sourceRoot":"","sources":["../../src/client/login-page.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,IAAI,MAAM,CAWxC"}
|
|
@@ -1,27 +1,8 @@
|
|
|
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);
|
|
1
|
+
export function loginPageScript() {
|
|
19
2
|
return `
|
|
20
3
|
document.querySelectorAll('[data-provider]').forEach(function(el) {
|
|
21
4
|
el.addEventListener('click', function() {
|
|
22
5
|
var provider = el.getAttribute('data-provider');
|
|
23
|
-
var prefix = ${prefixLiteral};
|
|
24
|
-
document.cookie = prefix + '_provider=' + encodeURIComponent(provider) + '; max-age=600; path=/; samesite=lax; secure';
|
|
25
6
|
if (window.glAnalytics) {
|
|
26
7
|
window.glAnalytics.track('login_clicked', { provider: provider });
|
|
27
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-page.js","sourceRoot":"","sources":["../../src/client/login-page.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -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.
|
|
17
|
+
id: verified.sub,
|
|
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.
|
|
30
|
+
id: refreshed.claims.sub,
|
|
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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAErE,MAAM,CAAC,KAAK,UAAU,SAAS,CAC9B,OAAwE,EACxE,IAA6B;IAE7B,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAE3B,8BAA8B;IAC9B,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,KAAK,CAAC,CAAA;IACpE,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,KAAK,CAAC,CAAA;IAErE,6BAA6B;IAC7B,IAAI,IAAI,GAAS,EAAE,aAAa,EAAE,KAAK,EAAE,CAAA;IACzC,IAAI,SAAS,GAA8C,IAAI,CAAA;IAE/D,IAAI,WAAW,EAAE,CAAC;QACjB,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;QACrE,IAAI,QAAQ,EAAE,CAAC;YACd,IAAI,GAAG;gBACN,EAAE,EAAE,QAAQ,CAAC,
|
|
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;
|
|
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,CA8DrE"}
|
package/dist/routes/callback.js
CHANGED
|
@@ -3,16 +3,6 @@ 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
|
-
}
|
|
16
6
|
export async function GET(context) {
|
|
17
7
|
const options = getConfig();
|
|
18
8
|
const { url, request } = context;
|
|
@@ -28,24 +18,14 @@ export async function GET(context) {
|
|
|
28
18
|
if (!codeVerifier) {
|
|
29
19
|
return new Response('Missing PKCE verifier', { status: 400 });
|
|
30
20
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// NOT include is_new_user or provider. Both come from elsewhere.
|
|
37
|
-
const tokenResponse = await exchangeCode(url, code, codeVerifier);
|
|
21
|
+
const provider = getCookie(request, `${options.cookiePrefix}_provider`);
|
|
22
|
+
// Exchange code for tokens
|
|
23
|
+
const tokenResponse = await exchangeCode(url, code, codeVerifier, provider === 'google' || provider === 'password' || provider === 'email-code'
|
|
24
|
+
? provider
|
|
25
|
+
: undefined);
|
|
38
26
|
if (!tokenResponse) {
|
|
39
27
|
return Response.redirect(new URL(`${options.loginPath}?error=auth_failed`, url.origin).toString(), 302);
|
|
40
28
|
}
|
|
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;
|
|
49
29
|
// Build redirect response
|
|
50
30
|
const redirectPath = getCookie(request, `${options.cookiePrefix}_redirect`) || '/';
|
|
51
31
|
const response = Response.redirect(new URL(redirectPath, url.origin).toString(), 302);
|
|
@@ -59,10 +39,10 @@ export async function GET(context) {
|
|
|
59
39
|
clearAuthFlowCookies(mutableResponse, options);
|
|
60
40
|
// Emit analytics event (non-blocking)
|
|
61
41
|
if (analyticsAvailable(context)) {
|
|
62
|
-
const eventName = isNewUser ? 'signup_completed' : 'login_completed';
|
|
42
|
+
const eventName = tokenResponse.isNewUser ? 'signup_completed' : 'login_completed';
|
|
63
43
|
const promise = emitAuthEvent(context, eventName, {
|
|
64
|
-
provider,
|
|
65
|
-
userId: tokenResponse.claims.
|
|
44
|
+
provider: tokenResponse.provider,
|
|
45
|
+
userId: tokenResponse.claims.sub,
|
|
66
46
|
});
|
|
67
47
|
context.waitUntil?.(promise);
|
|
68
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACzE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAS/C,MAAM,
|
|
1
|
+
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,17 +2,6 @@ interface ForgotPasswordContext {
|
|
|
2
2
|
request: Request;
|
|
3
3
|
url: URL;
|
|
4
4
|
}
|
|
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>;
|
|
5
|
+
export declare function GET(context: ForgotPasswordContext): Promise<Response>;
|
|
17
6
|
export {};
|
|
18
7
|
//# 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
|
|
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,CAW3E"}
|
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
import { getConfig } from '../config-store.js';
|
|
2
2
|
import { getForgotPasswordUrl } from '../utils/openauth.js';
|
|
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) {
|
|
3
|
+
export async function GET(context) {
|
|
15
4
|
const options = getConfig();
|
|
16
5
|
if (!options.providers.includes('password')) {
|
|
17
6
|
return new Response('Not found', { status: 404 });
|
|
18
7
|
}
|
|
19
|
-
const target = getForgotPasswordUrl();
|
|
20
|
-
|
|
8
|
+
const target = new URL(getForgotPasswordUrl());
|
|
9
|
+
target.search = context.url.search;
|
|
10
|
+
return Response.redirect(target.toString(), 302);
|
|
21
11
|
}
|
|
22
12
|
//# 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
|
|
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,MAAM,GAAG,IAAI,GAAG,CAAC,oBAAoB,EAAE,CAAC,CAAA;IAC9C,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAA;IAElC,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAA;AACjD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAMD,wBAAsB,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmClE"}
|
package/dist/routes/login.js
CHANGED
|
@@ -2,39 +2,40 @@ import { loginPageScript } from '../client/login-page.js';
|
|
|
2
2
|
import { getConfig } from '../config-store.js';
|
|
3
3
|
import { setAuthFlowCookies } from '../utils/cookies.js';
|
|
4
4
|
import { createAuthFlow, getAuthorizationUrl } from '../utils/openauth.js';
|
|
5
|
+
function _shouldShowForgotLink(options) {
|
|
6
|
+
return options.providers.includes('password');
|
|
7
|
+
}
|
|
5
8
|
export async function GET(context) {
|
|
6
9
|
const options = getConfig();
|
|
7
10
|
const redirectPath = context.url.searchParams.get('redirect') || '/';
|
|
11
|
+
const selectedProvider = context.url.searchParams.get('provider');
|
|
8
12
|
const error = context.url.searchParams.get('error');
|
|
9
13
|
const reset = context.url.searchParams.get('reset');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
reset,
|
|
27
|
-
authFlow,
|
|
28
|
-
});
|
|
29
|
-
const response = new Response(html, {
|
|
14
|
+
if (selectedProvider &&
|
|
15
|
+
options.providers.includes(selectedProvider)) {
|
|
16
|
+
const authFlow = await createAuthFlow(context);
|
|
17
|
+
const issuerProvider = selectedProvider === 'email-code' ? 'code' : selectedProvider;
|
|
18
|
+
const { url } = await getAuthorizationUrl(context, issuerProvider, redirectPath, authFlow);
|
|
19
|
+
const redirectResponse = Response.redirect(url, 302);
|
|
20
|
+
const response = new Response(redirectResponse.body, {
|
|
21
|
+
status: redirectResponse.status,
|
|
22
|
+
statusText: redirectResponse.statusText,
|
|
23
|
+
headers: new Headers(redirectResponse.headers),
|
|
24
|
+
});
|
|
25
|
+
setAuthFlowCookies(response, options, authFlow.verifier, authFlow.state, redirectPath, selectedProvider);
|
|
26
|
+
return response;
|
|
27
|
+
}
|
|
28
|
+
const html = renderLoginPage(options, { redirectPath, error, reset });
|
|
29
|
+
return new Response(html, {
|
|
30
30
|
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
31
31
|
});
|
|
32
|
-
setAuthFlowCookies(response, options, authFlow.verifier, authFlow.state, redirectPath);
|
|
33
|
-
return response;
|
|
34
32
|
}
|
|
35
33
|
function renderLoginPage(options, data) {
|
|
36
|
-
const { loginPage, providers } = options;
|
|
37
|
-
const
|
|
34
|
+
const { loginPage, providers, loginPath } = options;
|
|
35
|
+
const availableProviders = providers.filter((provider) => ['google', 'password', 'email-code'].includes(provider));
|
|
36
|
+
const hasGoogle = availableProviders.includes('google');
|
|
37
|
+
const hasAlternativeProvider = availableProviders.includes('password') || availableProviders.includes('email-code');
|
|
38
|
+
const makeStartUrl = (provider) => `${loginPath}?provider=${encodeURIComponent(provider)}&redirect=${encodeURIComponent(data.redirectPath)}`;
|
|
38
39
|
return `<!DOCTYPE html>
|
|
39
40
|
<html lang="en">
|
|
40
41
|
<head>
|
|
@@ -48,10 +49,11 @@ function renderLoginPage(options, data) {
|
|
|
48
49
|
.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; }
|
|
49
50
|
.btn-google { background: #4285f4; color: white; border: none; }
|
|
50
51
|
.divider { text-align: center; margin: 20px 0; color: #888; }
|
|
52
|
+
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; }
|
|
51
53
|
.error { color: #c00; text-align: center; margin: 12px 0; }
|
|
52
54
|
.success { color: #080; text-align: center; margin: 12px 0; }
|
|
53
55
|
.brand { text-align: center; color: #666; margin-bottom: 4px; }
|
|
54
|
-
.forgot-link { display: block; text-align:
|
|
56
|
+
.forgot-link { display: block; text-align: right; font-size: 14px; margin: -4px 0 8px; color: #555; }
|
|
55
57
|
</style>
|
|
56
58
|
</head>
|
|
57
59
|
<body>
|
|
@@ -60,14 +62,19 @@ function renderLoginPage(options, data) {
|
|
|
60
62
|
<h1>${loginPage.title}</h1>
|
|
61
63
|
${data.reset ? '<p class="success">Password reset successfully. Sign in with your new password.</p>' : ''}
|
|
62
64
|
${data.error ? '<p class="error">Authentication failed. Please try again.</p>' : ''}
|
|
63
|
-
${
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
${
|
|
67
|
-
${
|
|
68
|
-
? `<a href="${
|
|
65
|
+
${hasGoogle
|
|
66
|
+
? `<a href="${makeStartUrl('google')}" class="btn btn-google" data-provider="google">Sign in with Google</a>`
|
|
67
|
+
: ''}
|
|
68
|
+
${hasGoogle && hasAlternativeProvider ? '<div class="divider">or</div>' : ''}
|
|
69
|
+
${availableProviders.includes('email-code')
|
|
70
|
+
? `<a href="${makeStartUrl('email-code')}" class="btn" data-provider="email-code">Sign in with email</a>`
|
|
71
|
+
: ''}
|
|
72
|
+
${availableProviders.includes('password')
|
|
73
|
+
? `
|
|
74
|
+
<a href="${makeStartUrl('password')}" class="btn" data-provider="password">Sign in with password</a>
|
|
75
|
+
<a href="${options.forgotPasswordPath}" class="forgot-link">Forgot password?</a>`
|
|
69
76
|
: ''}
|
|
70
|
-
<script>${loginPageScript(
|
|
77
|
+
<script>${loginPageScript()}</script>
|
|
71
78
|
</body>
|
|
72
79
|
</html>`;
|
|
73
80
|
}
|
package/dist/routes/login.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/routes/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/routes/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,16 +2,6 @@ interface ResetPasswordContext {
|
|
|
2
2
|
request: Request;
|
|
3
3
|
url: URL;
|
|
4
4
|
}
|
|
5
|
-
/**
|
|
6
|
-
* Thin proxy into the issuer-hosted reset-password flow.
|
|
7
|
-
*
|
|
8
|
-
* The reset link emailed to the user can point at the consumer's
|
|
9
|
-
* `/reset-password?token=...`; this route 302s to the issuer's hosted
|
|
10
|
-
* reset page with the token preserved.
|
|
11
|
-
*
|
|
12
|
-
* Returns 400 when the token is missing, 404 when the `password`
|
|
13
|
-
* provider isn't configured.
|
|
14
|
-
*/
|
|
15
5
|
export declare function GET(context: ResetPasswordContext): Promise<Response>;
|
|
16
6
|
export {};
|
|
17
7
|
//# sourceMappingURL=reset-password.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reset-password.d.ts","sourceRoot":"","sources":["../../src/routes/reset-password.ts"],"names":[],"mappings":"AAGA,UAAU,oBAAoB;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;CACR;AAED
|
|
1
|
+
{"version":3,"file":"reset-password.d.ts","sourceRoot":"","sources":["../../src/routes/reset-password.ts"],"names":[],"mappings":"AAGA,UAAU,oBAAoB;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;CACR;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAW1E"}
|