@growth-labs/auth 0.1.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 +103 -0
- package/dist/client/login-page.d.ts +2 -0
- package/dist/client/login-page.d.ts.map +1 -0
- package/dist/client/login-page.js +13 -0
- package/dist/client/login-page.js.map +1 -0
- package/dist/config-store.d.ts +4 -0
- package/dist/config-store.d.ts.map +1 -0
- package/dist/config-store.js +15 -0
- package/dist/config-store.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +6 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +68 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/options.d.ts +70 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +33 -0
- package/dist/options.js.map +1 -0
- package/dist/routes/callback.d.ts +9 -0
- package/dist/routes/callback.d.ts.map +1 -0
- package/dist/routes/callback.js +48 -0
- package/dist/routes/callback.js.map +1 -0
- package/dist/routes/forgot-password.d.ts +7 -0
- package/dist/routes/forgot-password.d.ts.map +1 -0
- package/dist/routes/forgot-password.js +62 -0
- package/dist/routes/forgot-password.js.map +1 -0
- package/dist/routes/login.d.ts +7 -0
- package/dist/routes/login.d.ts.map +1 -0
- package/dist/routes/login.js +80 -0
- package/dist/routes/login.js.map +1 -0
- package/dist/routes/logout.d.ts +8 -0
- package/dist/routes/logout.d.ts.map +1 -0
- package/dist/routes/logout.js +26 -0
- package/dist/routes/logout.js.map +1 -0
- package/dist/routes/reset-password.d.ts +7 -0
- package/dist/routes/reset-password.d.ts.map +1 -0
- package/dist/routes/reset-password.js +57 -0
- package/dist/routes/reset-password.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/analytics.d.ts +7 -0
- package/dist/utils/analytics.d.ts.map +1 -0
- package/dist/utils/analytics.js +44 -0
- package/dist/utils/analytics.js.map +1 -0
- package/dist/utils/cookies.d.ts +17 -0
- package/dist/utils/cookies.d.ts.map +1 -0
- package/dist/utils/cookies.js +69 -0
- package/dist/utils/cookies.js.map +1 -0
- package/dist/utils/encoding.d.ts +2 -0
- package/dist/utils/encoding.d.ts.map +1 -0
- package/dist/utils/encoding.js +7 -0
- package/dist/utils/encoding.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/openauth.d.ts +29 -0
- package/dist/utils/openauth.d.ts.map +1 -0
- package/dist/utils/openauth.js +106 -0
- package/dist/utils/openauth.js.map +1 -0
- package/dist/utils/pkce.d.ts +5 -0
- package/dist/utils/pkce.d.ts.map +1 -0
- package/dist/utils/pkce.js +10 -0
- package/dist/utils/pkce.js.map +1 -0
- package/dist/utils/state.d.ts +3 -0
- package/dist/utils/state.d.ts.map +1 -0
- package/dist/utils/state.js +20 -0
- package/dist/utils/state.js.map +1 -0
- package/dist/utils/tokens.d.ts +12 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/tokens.js +73 -0
- package/dist/utils/tokens.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @growth-labs/auth
|
|
2
|
+
|
|
3
|
+
OpenAuth **client** package for Astro + Cloudflare. Verifies JWTs, manages session cookies, gates protected paths, and injects login/logout/callback routes.
|
|
4
|
+
|
|
5
|
+
**This is a client only.** It does NOT run an OpenAuth issuer. The issuer is a separate Worker — see `apps/auth-server/`.
|
|
6
|
+
|
|
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
|
+
|
|
9
|
+
## Config
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import auth from '@growth-labs/auth'
|
|
13
|
+
|
|
14
|
+
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
|
|
20
|
+
logoutRedirect: '/',
|
|
21
|
+
callbackPath: '/api/auth/callback',
|
|
22
|
+
providers: ['google', 'password', 'email-code'],
|
|
23
|
+
loginPage: {
|
|
24
|
+
title: 'Sign in',
|
|
25
|
+
brandName: 'FEDweek',
|
|
26
|
+
logoUrl: 'https://media.fedweek.com/logos/header.png',
|
|
27
|
+
},
|
|
28
|
+
accessTokenMaxAge: 900, // 15 min (seconds)
|
|
29
|
+
refreshTokenMaxAge: 2592000, // 30 days
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What It Injects
|
|
34
|
+
|
|
35
|
+
**Middleware** (runs on every request):
|
|
36
|
+
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)
|
|
38
|
+
3. If expired but refresh token valid → silent refresh, set new cookies
|
|
39
|
+
4. Populates `context.locals.user` as `AuthenticatedUser | UnauthenticatedUser`
|
|
40
|
+
5. If path matches `gatedPaths` and user unauthenticated → redirect to `loginPath`
|
|
41
|
+
|
|
42
|
+
**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)
|
|
46
|
+
|
|
47
|
+
## Using `context.locals.user`
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Gated page — user guaranteed authenticated
|
|
51
|
+
const { user } = Astro.locals
|
|
52
|
+
// user.id, user.email, user.name, user.image, user.authenticated (true)
|
|
53
|
+
|
|
54
|
+
// Non-gated page — check first
|
|
55
|
+
if (user.authenticated) {
|
|
56
|
+
// show personalized content
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## TypeScript
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface AuthenticatedUser {
|
|
64
|
+
id: string; email: string; name?: string; image?: string; authenticated: true
|
|
65
|
+
}
|
|
66
|
+
interface UnauthenticatedUser { authenticated: false }
|
|
67
|
+
type User = AuthenticatedUser | UnauthenticatedUser
|
|
68
|
+
// Available on context.locals.user (Astro.locals.user in .astro files)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Wrangler Bindings
|
|
72
|
+
|
|
73
|
+
None required by this package. The issuer needs KV + D1 — see `apps/auth-server/README.md`.
|
|
74
|
+
|
|
75
|
+
## Integration Order
|
|
76
|
+
|
|
77
|
+
Auth middleware MUST run before consent and analytics middleware. List `auth()` first in `astro.config.mjs` integrations array.
|
|
78
|
+
|
|
79
|
+
## Issuer Unreachable (Cold Start)
|
|
80
|
+
|
|
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
|
|
84
|
+
|
|
85
|
+
## OAuth Flow Summary
|
|
86
|
+
|
|
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
|
|
90
|
+
|
|
91
|
+
## Analytics Integration
|
|
92
|
+
|
|
93
|
+
Optional peer dependency on `@growth-labs/analytics`. If installed, emits `login_completed` / `signup_completed` events automatically. If not installed, silently skipped.
|
|
94
|
+
|
|
95
|
+
## Key Patterns
|
|
96
|
+
|
|
97
|
+
- Virtual module: `virtual:growth-labs/auth/config` (slash, not hyphen)
|
|
98
|
+
- Cookie names: `{cookiePrefix}_at`, `{cookiePrefix}_rt`, `{cookiePrefix}_pkce`, `{cookiePrefix}_state`, `{cookiePrefix}_redirect`
|
|
99
|
+
- 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
|
|
102
|
+
- No `aud` claim validation in this package — single client per issuer assumed
|
|
103
|
+
- `.astro` component files ship as source, not compiled
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-page.d.ts","sourceRoot":"","sources":["../../src/client/login-page.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,IAAI,MAAM,CAWxC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function loginPageScript() {
|
|
2
|
+
return `
|
|
3
|
+
document.querySelectorAll('[data-provider]').forEach(function(el) {
|
|
4
|
+
el.addEventListener('click', function() {
|
|
5
|
+
var provider = el.getAttribute('data-provider');
|
|
6
|
+
if (window.glAnalytics) {
|
|
7
|
+
window.glAnalytics.track('login_clicked', { provider: provider });
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
`;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=login-page.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../src/config-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAIvD,wBAAgB,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAE5D;AAED,wBAAgB,SAAS,IAAI,mBAAmB,CAe/C"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
let _config = null;
|
|
2
|
+
export function _setConfig(config) {
|
|
3
|
+
_config = config;
|
|
4
|
+
}
|
|
5
|
+
export function getConfig() {
|
|
6
|
+
const runtimeConfig = globalThis.__GL_AUTH_CONFIG__;
|
|
7
|
+
if (runtimeConfig) {
|
|
8
|
+
return runtimeConfig;
|
|
9
|
+
}
|
|
10
|
+
if (!_config) {
|
|
11
|
+
throw new Error('@growth-labs/auth: integration not configured. Add auth() to your astro.config.mjs integrations array.');
|
|
12
|
+
}
|
|
13
|
+
return _config;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=config-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-store.js","sourceRoot":"","sources":["../src/config-store.ts"],"names":[],"mappings":"AAEA,IAAI,OAAO,GAA+B,IAAI,CAAA;AAE9C,MAAM,UAAU,UAAU,CAAC,MAA2B;IACrD,OAAO,GAAG,MAAM,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,SAAS;IACxB,MAAM,aAAa,GAClB,UAGA,CAAC,kBAAkB,CAAA;IACpB,IAAI,aAAa,EAAE,CAAC;QACnB,OAAO,aAAa,CAAA;IACrB,CAAC;IACD,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACd,wGAAwG,CACxG,CAAA;IACF,CAAC;IACD,OAAO,OAAO,CAAA;AACf,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AuthOptions } from './options.js';
|
|
2
|
+
interface AstroIntegration {
|
|
3
|
+
name: string;
|
|
4
|
+
hooks: {
|
|
5
|
+
'astro:config:setup': (params: {
|
|
6
|
+
addMiddleware: (opts: {
|
|
7
|
+
entrypoint: string;
|
|
8
|
+
order: 'pre' | 'post';
|
|
9
|
+
}) => void;
|
|
10
|
+
injectRoute: (opts: {
|
|
11
|
+
pattern: string;
|
|
12
|
+
entrypoint: string;
|
|
13
|
+
prerender?: boolean;
|
|
14
|
+
}) => void;
|
|
15
|
+
updateConfig: (config: {
|
|
16
|
+
vite?: {
|
|
17
|
+
define?: Record<string, string>;
|
|
18
|
+
};
|
|
19
|
+
}) => void;
|
|
20
|
+
}) => void;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export default function auth(userOptions: AuthOptions): AstroIntegration;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,UAAU,gBAAgB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE;QACN,oBAAoB,EAAE,CAAC,MAAM,EAAE;YAC9B,aAAa,EAAE,CAAC,IAAI,EAAE;gBAAE,UAAU,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAA;aAAE,KAAK,IAAI,CAAA;YAC5E,WAAW,EAAE,CAAC,IAAI,EAAE;gBAAE,OAAO,EAAE,MAAM,CAAC;gBAAC,UAAU,EAAE,MAAM,CAAC;gBAAC,SAAS,CAAC,EAAE,OAAO,CAAA;aAAE,KAAK,IAAI,CAAA;YACzF,YAAY,EAAE,CAAC,MAAM,EAAE;gBAAE,IAAI,CAAC,EAAE;oBAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;iBAAE,CAAA;aAAE,KAAK,IAAI,CAAA;SAC9E,KAAK,IAAI,CAAA;KACV,CAAA;CACD;AAQD,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,gBAAgB,CAuDvE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { _setConfig } from './config-store.js';
|
|
2
|
+
import { authOptionsSchema } from './options.js';
|
|
3
|
+
const ENTRYPOINT_EXTENSION = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
|
|
4
|
+
function resolveEntrypoint(path) {
|
|
5
|
+
return decodeURIComponent(new URL(`${path}${ENTRYPOINT_EXTENSION}`, import.meta.url).pathname);
|
|
6
|
+
}
|
|
7
|
+
export default function auth(userOptions) {
|
|
8
|
+
const options = authOptionsSchema.parse(userOptions);
|
|
9
|
+
_setConfig(options);
|
|
10
|
+
return {
|
|
11
|
+
name: '@growth-labs/auth',
|
|
12
|
+
hooks: {
|
|
13
|
+
'astro:config:setup': ({ addMiddleware, injectRoute, updateConfig }) => {
|
|
14
|
+
updateConfig({
|
|
15
|
+
vite: {
|
|
16
|
+
define: {
|
|
17
|
+
'globalThis.__GL_AUTH_CONFIG__': JSON.stringify(options),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
addMiddleware({
|
|
22
|
+
entrypoint: resolveEntrypoint('./middleware/auth'),
|
|
23
|
+
order: 'pre',
|
|
24
|
+
});
|
|
25
|
+
injectRoute({
|
|
26
|
+
pattern: options.loginPath,
|
|
27
|
+
entrypoint: resolveEntrypoint('./routes/login'),
|
|
28
|
+
prerender: false,
|
|
29
|
+
});
|
|
30
|
+
injectRoute({
|
|
31
|
+
pattern: '/logout',
|
|
32
|
+
entrypoint: resolveEntrypoint('./routes/logout'),
|
|
33
|
+
prerender: false,
|
|
34
|
+
});
|
|
35
|
+
injectRoute({
|
|
36
|
+
pattern: options.callbackPath,
|
|
37
|
+
entrypoint: resolveEntrypoint('./routes/callback'),
|
|
38
|
+
prerender: false,
|
|
39
|
+
});
|
|
40
|
+
if (options.providers.includes('password')) {
|
|
41
|
+
injectRoute({
|
|
42
|
+
pattern: options.forgotPasswordPath,
|
|
43
|
+
entrypoint: resolveEntrypoint('./routes/forgot-password'),
|
|
44
|
+
prerender: false,
|
|
45
|
+
});
|
|
46
|
+
injectRoute({
|
|
47
|
+
pattern: options.resetPasswordPath,
|
|
48
|
+
entrypoint: resolveEntrypoint('./routes/reset-password'),
|
|
49
|
+
prerender: false,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAahD,MAAM,oBAAoB,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAA;AAE5E,SAAS,iBAAiB,CAAC,IAAY;IACtC,OAAO,kBAAkB,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,oBAAoB,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAA;AAC/F,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,WAAwB;IACpD,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IACpD,UAAU,CAAC,OAAO,CAAC,CAAA;IAEnB,OAAO;QACN,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE;YACN,oBAAoB,EAAE,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,EAAE;gBACtE,YAAY,CAAC;oBACZ,IAAI,EAAE;wBACL,MAAM,EAAE;4BACP,+BAA+B,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;yBACxD;qBACD;iBACD,CAAC,CAAA;gBAEF,aAAa,CAAC;oBACb,UAAU,EAAE,iBAAiB,CAAC,mBAAmB,CAAC;oBAClD,KAAK,EAAE,KAAK;iBACZ,CAAC,CAAA;gBAEF,WAAW,CAAC;oBACX,OAAO,EAAE,OAAO,CAAC,SAAS;oBAC1B,UAAU,EAAE,iBAAiB,CAAC,gBAAgB,CAAC;oBAC/C,SAAS,EAAE,KAAK;iBAChB,CAAC,CAAA;gBAEF,WAAW,CAAC;oBACX,OAAO,EAAE,SAAS;oBAClB,UAAU,EAAE,iBAAiB,CAAC,iBAAiB,CAAC;oBAChD,SAAS,EAAE,KAAK;iBAChB,CAAC,CAAA;gBAEF,WAAW,CAAC;oBACX,OAAO,EAAE,OAAO,CAAC,YAAY;oBAC7B,UAAU,EAAE,iBAAiB,CAAC,mBAAmB,CAAC;oBAClD,SAAS,EAAE,KAAK;iBAChB,CAAC,CAAA;gBAEF,IAAI,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC5C,WAAW,CAAC;wBACX,OAAO,EAAE,OAAO,CAAC,kBAAkB;wBACnC,UAAU,EAAE,iBAAiB,CAAC,0BAA0B,CAAC;wBACzD,SAAS,EAAE,KAAK;qBAChB,CAAC,CAAA;oBAEF,WAAW,CAAC;wBACX,OAAO,EAAE,OAAO,CAAC,iBAAiB;wBAClC,UAAU,EAAE,iBAAiB,CAAC,yBAAyB,CAAC;wBACxD,SAAS,EAAE,KAAK;qBAChB,CAAC,CAAA;gBACH,CAAC;YACF,CAAC;SACD;KACD,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAKA,wBAAsB,SAAS,CAC9B,OAAO,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EACxE,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAkEnB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { getConfig } from '../config-store.js';
|
|
2
|
+
import { getCookie, setTokenCookies } from '../utils/cookies.js';
|
|
3
|
+
import { refreshTokens, verifyAccessToken } from '../utils/tokens.js';
|
|
4
|
+
export async function onRequest(context, next) {
|
|
5
|
+
const options = getConfig();
|
|
6
|
+
const { request } = context;
|
|
7
|
+
// 1. Read tokens from cookies
|
|
8
|
+
const accessToken = getCookie(request, `${options.cookiePrefix}_at`);
|
|
9
|
+
const refreshToken = getCookie(request, `${options.cookiePrefix}_rt`);
|
|
10
|
+
// 2. Attempt to authenticate
|
|
11
|
+
let user = { authenticated: false };
|
|
12
|
+
let refreshed = null;
|
|
13
|
+
if (accessToken) {
|
|
14
|
+
const verified = await verifyAccessToken(options.issuer, accessToken);
|
|
15
|
+
if (verified) {
|
|
16
|
+
user = {
|
|
17
|
+
id: verified.sub,
|
|
18
|
+
email: verified.email,
|
|
19
|
+
name: verified.name,
|
|
20
|
+
image: verified.image,
|
|
21
|
+
authenticated: true,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!user.authenticated && refreshToken) {
|
|
26
|
+
const clientId = options.clientId || context.url.hostname;
|
|
27
|
+
refreshed = await refreshTokens(options.issuer, refreshToken, clientId);
|
|
28
|
+
if (refreshed) {
|
|
29
|
+
user = {
|
|
30
|
+
id: refreshed.claims.sub,
|
|
31
|
+
email: refreshed.claims.email,
|
|
32
|
+
name: refreshed.claims.name,
|
|
33
|
+
image: refreshed.claims.image,
|
|
34
|
+
authenticated: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// 3. Populate context.locals.user
|
|
39
|
+
context.locals.user = user;
|
|
40
|
+
// 4. Gate protected paths
|
|
41
|
+
if (!user.authenticated && isGatedPath(context.url.pathname, options.gatedPaths)) {
|
|
42
|
+
const loginUrl = new URL(`${options.loginPath}?redirect=${encodeURIComponent(context.url.pathname)}`, context.url.origin);
|
|
43
|
+
return Response.redirect(loginUrl.toString(), 302);
|
|
44
|
+
}
|
|
45
|
+
// 5. Continue to the page
|
|
46
|
+
const response = await next();
|
|
47
|
+
// 6. If tokens were refreshed, set new cookies on the response
|
|
48
|
+
if (refreshed) {
|
|
49
|
+
const modifiedResponse = new Response(response.body, {
|
|
50
|
+
status: response.status,
|
|
51
|
+
statusText: response.statusText,
|
|
52
|
+
headers: new Headers(response.headers),
|
|
53
|
+
});
|
|
54
|
+
setTokenCookies(modifiedResponse, options, refreshed.accessToken, refreshed.refreshToken);
|
|
55
|
+
return modifiedResponse;
|
|
56
|
+
}
|
|
57
|
+
return response;
|
|
58
|
+
}
|
|
59
|
+
function isGatedPath(pathname, gatedPaths) {
|
|
60
|
+
return gatedPaths.some((pattern) => {
|
|
61
|
+
if (pattern.endsWith('/*')) {
|
|
62
|
+
const prefix = pattern.slice(0, -1); // '/premium/*' → '/premium/'
|
|
63
|
+
return pathname.startsWith(prefix);
|
|
64
|
+
}
|
|
65
|
+
return pathname === pattern;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +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,GAAG;gBAChB,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,aAAa,EAAE,IAAI;aACnB,CAAA;QACF,CAAC;IACF,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,YAAY,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAA;QACzD,SAAS,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAA;QACvE,IAAI,SAAS,EAAE,CAAC;YACf,IAAI,GAAG;gBACN,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG;gBACxB,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK;gBAC7B,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI;gBAC3B,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK;gBAC7B,aAAa,EAAE,IAAI;aACnB,CAAA;QACF,CAAC;IACF,CAAC;IAED,kCAAkC;IAClC,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAA;IAE1B,0BAA0B;IAC1B,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAClF,MAAM,QAAQ,GAAG,IAAI,GAAG,CACvB,GAAG,OAAO,CAAC,SAAS,aAAa,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAC3E,OAAO,CAAC,GAAG,CAAC,MAAM,CAClB,CAAA;QACD,OAAO,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAA;IACnD,CAAC;IAED,0BAA0B;IAC1B,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAA;IAE7B,+DAA+D;IAC/D,IAAI,SAAS,EAAE,CAAC;QACf,MAAM,gBAAgB,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;YACpD,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;SACtC,CAAC,CAAA;QACF,eAAe,CAAC,gBAAgB,EAAE,OAAO,EAAE,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;QACzF,OAAO,gBAAgB,CAAA;IACxB,CAAC;IAED,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,UAAoB;IAC1D,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;QAClC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA,CAAC,6BAA6B;YACjE,OAAO,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QACnC,CAAC;QACD,OAAO,QAAQ,KAAK,OAAO,CAAA;IAC5B,CAAC,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const authOptionsSchema: z.ZodObject<{
|
|
3
|
+
issuer: z.ZodString;
|
|
4
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
5
|
+
cookiePrefix: z.ZodString;
|
|
6
|
+
sessionCookieDomain: z.ZodOptional<z.ZodString>;
|
|
7
|
+
accessTokenMaxAge: z.ZodDefault<z.ZodNumber>;
|
|
8
|
+
refreshTokenMaxAge: z.ZodDefault<z.ZodNumber>;
|
|
9
|
+
gatedPaths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
10
|
+
loginPath: z.ZodDefault<z.ZodString>;
|
|
11
|
+
logoutRedirect: z.ZodDefault<z.ZodString>;
|
|
12
|
+
callbackPath: z.ZodDefault<z.ZodString>;
|
|
13
|
+
forgotPasswordPath: z.ZodDefault<z.ZodString>;
|
|
14
|
+
resetPasswordPath: z.ZodDefault<z.ZodString>;
|
|
15
|
+
providers: z.ZodDefault<z.ZodArray<z.ZodEnum<["google", "password", "email-code"]>, "many">>;
|
|
16
|
+
loginPage: z.ZodDefault<z.ZodObject<{
|
|
17
|
+
title: z.ZodDefault<z.ZodString>;
|
|
18
|
+
brandName: z.ZodOptional<z.ZodString>;
|
|
19
|
+
logoUrl: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, "strip", z.ZodTypeAny, {
|
|
21
|
+
title: string;
|
|
22
|
+
brandName?: string | undefined;
|
|
23
|
+
logoUrl?: string | undefined;
|
|
24
|
+
}, {
|
|
25
|
+
title?: string | undefined;
|
|
26
|
+
brandName?: string | undefined;
|
|
27
|
+
logoUrl?: string | undefined;
|
|
28
|
+
}>>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
issuer: string;
|
|
31
|
+
cookiePrefix: string;
|
|
32
|
+
accessTokenMaxAge: number;
|
|
33
|
+
refreshTokenMaxAge: number;
|
|
34
|
+
gatedPaths: string[];
|
|
35
|
+
loginPath: string;
|
|
36
|
+
logoutRedirect: string;
|
|
37
|
+
callbackPath: string;
|
|
38
|
+
forgotPasswordPath: string;
|
|
39
|
+
resetPasswordPath: string;
|
|
40
|
+
providers: ("google" | "password" | "email-code")[];
|
|
41
|
+
loginPage: {
|
|
42
|
+
title: string;
|
|
43
|
+
brandName?: string | undefined;
|
|
44
|
+
logoUrl?: string | undefined;
|
|
45
|
+
};
|
|
46
|
+
clientId?: string | undefined;
|
|
47
|
+
sessionCookieDomain?: string | undefined;
|
|
48
|
+
}, {
|
|
49
|
+
issuer: string;
|
|
50
|
+
cookiePrefix: string;
|
|
51
|
+
clientId?: string | undefined;
|
|
52
|
+
sessionCookieDomain?: string | undefined;
|
|
53
|
+
accessTokenMaxAge?: number | undefined;
|
|
54
|
+
refreshTokenMaxAge?: number | undefined;
|
|
55
|
+
gatedPaths?: string[] | undefined;
|
|
56
|
+
loginPath?: string | undefined;
|
|
57
|
+
logoutRedirect?: string | undefined;
|
|
58
|
+
callbackPath?: string | undefined;
|
|
59
|
+
forgotPasswordPath?: string | undefined;
|
|
60
|
+
resetPasswordPath?: string | undefined;
|
|
61
|
+
providers?: ("google" | "password" | "email-code")[] | undefined;
|
|
62
|
+
loginPage?: {
|
|
63
|
+
title?: string | undefined;
|
|
64
|
+
brandName?: string | undefined;
|
|
65
|
+
logoUrl?: string | undefined;
|
|
66
|
+
} | undefined;
|
|
67
|
+
}>;
|
|
68
|
+
export type AuthOptions = z.input<typeof authOptionsSchema>;
|
|
69
|
+
export type ResolvedAuthOptions = z.output<typeof authOptionsSchema>;
|
|
70
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoC5B,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAC3D,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAA"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const authOptionsSchema = z.object({
|
|
3
|
+
// ─── Required ───
|
|
4
|
+
issuer: z.string().url(),
|
|
5
|
+
// ─── Client identity ───
|
|
6
|
+
clientId: z.string().optional(),
|
|
7
|
+
// ─── Cookie configuration ───
|
|
8
|
+
cookiePrefix: z.string(),
|
|
9
|
+
sessionCookieDomain: z.string().optional(),
|
|
10
|
+
// ─── Token lifetimes ───
|
|
11
|
+
accessTokenMaxAge: z.number().default(900),
|
|
12
|
+
refreshTokenMaxAge: z.number().default(2592000),
|
|
13
|
+
// ─── Path configuration ───
|
|
14
|
+
gatedPaths: z.array(z.string()).default([]),
|
|
15
|
+
loginPath: z.string().default('/login'),
|
|
16
|
+
logoutRedirect: z.string().default('/'),
|
|
17
|
+
callbackPath: z.string().default('/api/auth/callback'),
|
|
18
|
+
forgotPasswordPath: z.string().default('/forgot-password'),
|
|
19
|
+
resetPasswordPath: z.string().default('/reset-password'),
|
|
20
|
+
// ─── Providers ───
|
|
21
|
+
providers: z
|
|
22
|
+
.array(z.enum(['google', 'password', 'email-code']))
|
|
23
|
+
.default(['google', 'password', 'email-code']),
|
|
24
|
+
// ─── Login page customization ───
|
|
25
|
+
loginPage: z
|
|
26
|
+
.object({
|
|
27
|
+
title: z.string().default('Sign in'),
|
|
28
|
+
brandName: z.string().optional(),
|
|
29
|
+
logoUrl: z.string().optional(),
|
|
30
|
+
})
|
|
31
|
+
.default({}),
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,mBAAmB;IACnB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAExB,0BAA0B;IAC1B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAE/B,+BAA+B;IAC/B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAE1C,0BAA0B;IAC1B,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IAC1C,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IAE/C,6BAA6B;IAC7B,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC3C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IACvC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IACvC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,oBAAoB,CAAC;IACtD,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC;IAC1D,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC;IAExD,oBAAoB;IACpB,SAAS,EAAE,CAAC;SACV,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;SACnD,OAAO,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;IAE/C,mCAAmC;IACnC,SAAS,EAAE,CAAC;SACV,MAAM,CAAC;QACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;QACpC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAChC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC9B,CAAC;SACD,OAAO,CAAC,EAAE,CAAC;CACb,CAAC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface CallbackContext {
|
|
2
|
+
request: Request;
|
|
3
|
+
url: URL;
|
|
4
|
+
locals: Record<string, unknown>;
|
|
5
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function GET(context: CallbackContext): Promise<Response>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=callback.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getConfig } from '../config-store.js';
|
|
2
|
+
import { analyticsAvailable, emitAuthEvent } from '../utils/analytics.js';
|
|
3
|
+
import { clearAuthFlowCookies, getCookie, setTokenCookies } from '../utils/cookies.js';
|
|
4
|
+
import { exchangeCode } from '../utils/openauth.js';
|
|
5
|
+
import { verifyState } from '../utils/state.js';
|
|
6
|
+
export async function GET(context) {
|
|
7
|
+
const options = getConfig();
|
|
8
|
+
const { url, request } = context;
|
|
9
|
+
const code = url.searchParams.get('code');
|
|
10
|
+
const state = url.searchParams.get('state');
|
|
11
|
+
// Verify CSRF state
|
|
12
|
+
const storedState = getCookie(request, `${options.cookiePrefix}_state`);
|
|
13
|
+
if (!state || !storedState || !verifyState(state, storedState)) {
|
|
14
|
+
return new Response('Invalid state parameter', { status: 403 });
|
|
15
|
+
}
|
|
16
|
+
// Read PKCE verifier
|
|
17
|
+
const codeVerifier = getCookie(request, `${options.cookiePrefix}_pkce`);
|
|
18
|
+
if (!codeVerifier) {
|
|
19
|
+
return new Response('Missing PKCE verifier', { status: 400 });
|
|
20
|
+
}
|
|
21
|
+
// Exchange code for tokens
|
|
22
|
+
const tokenResponse = await exchangeCode(url, code, codeVerifier);
|
|
23
|
+
if (!tokenResponse) {
|
|
24
|
+
return Response.redirect(new URL(`${options.loginPath}?error=auth_failed`, url.origin).toString(), 302);
|
|
25
|
+
}
|
|
26
|
+
// Build redirect response
|
|
27
|
+
const redirectPath = getCookie(request, `${options.cookiePrefix}_redirect`) || '/';
|
|
28
|
+
const response = Response.redirect(new URL(redirectPath, url.origin).toString(), 302);
|
|
29
|
+
// Mutable copy for setting cookies
|
|
30
|
+
const mutableResponse = new Response(response.body, {
|
|
31
|
+
status: response.status,
|
|
32
|
+
statusText: response.statusText,
|
|
33
|
+
headers: new Headers(response.headers),
|
|
34
|
+
});
|
|
35
|
+
setTokenCookies(mutableResponse, options, tokenResponse.accessToken, tokenResponse.refreshToken);
|
|
36
|
+
clearAuthFlowCookies(mutableResponse, options);
|
|
37
|
+
// Emit analytics event (non-blocking)
|
|
38
|
+
if (analyticsAvailable(context)) {
|
|
39
|
+
const eventName = tokenResponse.isNewUser ? 'signup_completed' : 'login_completed';
|
|
40
|
+
const promise = emitAuthEvent(context, eventName, {
|
|
41
|
+
provider: tokenResponse.provider,
|
|
42
|
+
userId: tokenResponse.claims.sub,
|
|
43
|
+
});
|
|
44
|
+
context.waitUntil?.(promise);
|
|
45
|
+
}
|
|
46
|
+
return mutableResponse;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=callback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getConfig } from '../config-store.js';
|
|
2
|
+
import { getForgotPasswordUrl } from '../utils/openauth.js';
|
|
3
|
+
export async function GET(context) {
|
|
4
|
+
const options = getConfig();
|
|
5
|
+
if (!options.providers.includes('password')) {
|
|
6
|
+
return new Response('Not found', { status: 404 });
|
|
7
|
+
}
|
|
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>`;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=forgot-password.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +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"}
|