@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.
Files changed (78) hide show
  1. package/README.md +103 -0
  2. package/dist/client/login-page.d.ts +2 -0
  3. package/dist/client/login-page.d.ts.map +1 -0
  4. package/dist/client/login-page.js +13 -0
  5. package/dist/client/login-page.js.map +1 -0
  6. package/dist/config-store.d.ts +4 -0
  7. package/dist/config-store.d.ts.map +1 -0
  8. package/dist/config-store.js +15 -0
  9. package/dist/config-store.js.map +1 -0
  10. package/dist/index.d.ts +25 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +56 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/middleware/auth.d.ts +6 -0
  15. package/dist/middleware/auth.d.ts.map +1 -0
  16. package/dist/middleware/auth.js +68 -0
  17. package/dist/middleware/auth.js.map +1 -0
  18. package/dist/options.d.ts +70 -0
  19. package/dist/options.d.ts.map +1 -0
  20. package/dist/options.js +33 -0
  21. package/dist/options.js.map +1 -0
  22. package/dist/routes/callback.d.ts +9 -0
  23. package/dist/routes/callback.d.ts.map +1 -0
  24. package/dist/routes/callback.js +48 -0
  25. package/dist/routes/callback.js.map +1 -0
  26. package/dist/routes/forgot-password.d.ts +7 -0
  27. package/dist/routes/forgot-password.d.ts.map +1 -0
  28. package/dist/routes/forgot-password.js +62 -0
  29. package/dist/routes/forgot-password.js.map +1 -0
  30. package/dist/routes/login.d.ts +7 -0
  31. package/dist/routes/login.d.ts.map +1 -0
  32. package/dist/routes/login.js +80 -0
  33. package/dist/routes/login.js.map +1 -0
  34. package/dist/routes/logout.d.ts +8 -0
  35. package/dist/routes/logout.d.ts.map +1 -0
  36. package/dist/routes/logout.js +26 -0
  37. package/dist/routes/logout.js.map +1 -0
  38. package/dist/routes/reset-password.d.ts +7 -0
  39. package/dist/routes/reset-password.d.ts.map +1 -0
  40. package/dist/routes/reset-password.js +57 -0
  41. package/dist/routes/reset-password.js.map +1 -0
  42. package/dist/types.d.ts +41 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +2 -0
  45. package/dist/types.js.map +1 -0
  46. package/dist/utils/analytics.d.ts +7 -0
  47. package/dist/utils/analytics.d.ts.map +1 -0
  48. package/dist/utils/analytics.js +44 -0
  49. package/dist/utils/analytics.js.map +1 -0
  50. package/dist/utils/cookies.d.ts +17 -0
  51. package/dist/utils/cookies.d.ts.map +1 -0
  52. package/dist/utils/cookies.js +69 -0
  53. package/dist/utils/cookies.js.map +1 -0
  54. package/dist/utils/encoding.d.ts +2 -0
  55. package/dist/utils/encoding.d.ts.map +1 -0
  56. package/dist/utils/encoding.js +7 -0
  57. package/dist/utils/encoding.js.map +1 -0
  58. package/dist/utils/index.d.ts +6 -0
  59. package/dist/utils/index.d.ts.map +1 -0
  60. package/dist/utils/index.js +5 -0
  61. package/dist/utils/index.js.map +1 -0
  62. package/dist/utils/openauth.d.ts +29 -0
  63. package/dist/utils/openauth.d.ts.map +1 -0
  64. package/dist/utils/openauth.js +106 -0
  65. package/dist/utils/openauth.js.map +1 -0
  66. package/dist/utils/pkce.d.ts +5 -0
  67. package/dist/utils/pkce.d.ts.map +1 -0
  68. package/dist/utils/pkce.js +10 -0
  69. package/dist/utils/pkce.js.map +1 -0
  70. package/dist/utils/state.d.ts +3 -0
  71. package/dist/utils/state.d.ts.map +1 -0
  72. package/dist/utils/state.js +20 -0
  73. package/dist/utils/state.js.map +1 -0
  74. package/dist/utils/tokens.d.ts +12 -0
  75. package/dist/utils/tokens.d.ts.map +1 -0
  76. package/dist/utils/tokens.js +73 -0
  77. package/dist/utils/tokens.js.map +1 -0
  78. 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,2 @@
1
+ export declare function loginPageScript(): string;
2
+ //# sourceMappingURL=login-page.d.ts.map
@@ -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,4 @@
1
+ import type { ResolvedAuthOptions } from './options.js';
2
+ export declare function _setConfig(config: ResolvedAuthOptions): void;
3
+ export declare function getConfig(): ResolvedAuthOptions;
4
+ //# sourceMappingURL=config-store.d.ts.map
@@ -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"}
@@ -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,6 @@
1
+ export declare function onRequest(context: {
2
+ request: Request;
3
+ url: URL;
4
+ locals: Record<string, unknown>;
5
+ }, next: () => Promise<Response>): Promise<Response>;
6
+ //# sourceMappingURL=auth.d.ts.map
@@ -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":"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"}
@@ -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"}
@@ -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":"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,CAAC,KAAK,UAAU,GAAG,CAAC,OAAwB;IACjD,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAEhC,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACzC,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IAE3C,oBAAoB;IACpB,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,QAAQ,CAAC,CAAA;IACvE,IAAI,CAAC,KAAK,IAAI,CAAC,WAAW,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,QAAQ,CAAC,yBAAyB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAChE,CAAC;IAED,qBAAqB;IACrB,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,OAAO,CAAC,CAAA;IACvE,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,OAAO,IAAI,QAAQ,CAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAC9D,CAAC;IAED,2BAA2B;IAC3B,MAAM,aAAa,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,IAAK,EAAE,YAAY,CAAC,CAAA;IAClE,IAAI,CAAC,aAAa,EAAE,CAAC;QACpB,OAAO,QAAQ,CAAC,QAAQ,CACvB,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,SAAS,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EACxE,GAAG,CACH,CAAA;IACF,CAAC;IAED,0BAA0B;IAC1B,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,WAAW,CAAC,IAAI,GAAG,CAAA;IAClF,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAA;IAErF,mCAAmC;IACnC,MAAM,eAAe,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;QACnD,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;KACtC,CAAC,CAAA;IAEF,eAAe,CAAC,eAAe,EAAE,OAAO,EAAE,aAAa,CAAC,WAAW,EAAE,aAAa,CAAC,YAAY,CAAC,CAAA;IAChG,oBAAoB,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;IAE9C,sCAAsC;IACtC,IAAI,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,iBAAiB,CAAA;QAClF,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE;YACjD,QAAQ,EAAE,aAAa,CAAC,QAAQ;YAChC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG;SAChC,CAAC,CAAA;QACF,OAAO,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAA;IAC7B,CAAC;IAED,OAAO,eAAe,CAAA;AACvB,CAAC"}
@@ -0,0 +1,7 @@
1
+ interface ForgotPasswordContext {
2
+ request: Request;
3
+ url: URL;
4
+ }
5
+ export declare function GET(context: ForgotPasswordContext): Promise<Response>;
6
+ export {};
7
+ //# sourceMappingURL=forgot-password.d.ts.map
@@ -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,7 @@
1
+ interface LoginContext {
2
+ request: Request;
3
+ url: URL;
4
+ }
5
+ export declare function GET(context: LoginContext): Promise<Response>;
6
+ export {};
7
+ //# sourceMappingURL=login.d.ts.map
@@ -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"}