@inai-dev/astro 0.1.1 → 0.3.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @inai-dev/astro
2
2
 
3
- Astro integration for InAI Auth. Provides middleware, server-side helpers, and Astro components for authentication.
3
+ Full Astro integration for InAI Auth. Includes middleware with automatic token refresh, API route handlers, and server-side helpers.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,6 +8,13 @@ Astro integration for InAI Auth. Provides middleware, server-side helpers, and A
8
8
  npm install @inai-dev/astro
9
9
  ```
10
10
 
11
+ ## Environment Variables
12
+
13
+ ```env
14
+ # Required — your publishable key
15
+ INAI_PUBLISHABLE_KEY=pk_live_...
16
+ ```
17
+
11
18
  ## Setup
12
19
 
13
20
  ### 1. Add Integration
@@ -15,46 +22,193 @@ npm install @inai-dev/astro
15
22
  ```ts
16
23
  // astro.config.mjs
17
24
  import { defineConfig } from "astro/config";
18
- import inaiAuth from "@inai-dev/astro";
25
+ import { inaiAuth } from "@inai-dev/astro";
19
26
 
20
27
  export default defineConfig({
21
- integrations: [
22
- inaiAuth({
23
- publishableKey: import.meta.env.INAI_PUBLISHABLE_KEY,
24
- }),
25
- ],
28
+ output: "server", // Required — SSR mode for auth
29
+ integrations: [inaiAuth()],
30
+ });
31
+ ```
32
+
33
+ > **Note:** Auth requires `output: "server"` (or `"hybrid"`) since middleware and API routes run on the server.
34
+
35
+ ### 2. Middleware
36
+
37
+ ```ts
38
+ // src/middleware.ts
39
+ import { inaiAstroMiddleware } from "@inai-dev/astro/middleware";
40
+
41
+ export const onRequest = inaiAstroMiddleware({
42
+ publicRoutes: ["/", "/about", "/login"],
43
+ signInUrl: "/login",
44
+ });
45
+ ```
46
+
47
+ The middleware automatically:
48
+ - Skips public routes, `/api/*`, and `/_*` paths
49
+ - Validates the auth token from cookies
50
+ - Refreshes expired tokens via `/api/auth/refresh` when a refresh token exists
51
+ - Sets `Astro.locals.auth` with the `AuthObject` for authenticated requests
52
+ - Redirects to sign-in for unauthenticated requests on protected routes
53
+
54
+ ### 3. API Routes
55
+
56
+ Auth uses standard Astro API routes (not Astro Actions). This is intentional: the middleware refreshes tokens via self-fetch to `/api/auth/refresh`, which requires a real HTTP endpoint. API routes also allow direct cookie manipulation and standard REST semantics for auth flows.
57
+
58
+ ```ts
59
+ // src/pages/api/auth/[path].ts
60
+ import { createAuthRoutes } from "@inai-dev/astro/api-routes";
61
+
62
+ const routes = createAuthRoutes({
63
+ publishableKey: process.env.INAI_PUBLISHABLE_KEY,
64
+ });
65
+
66
+ export const ALL = routes.ALL;
67
+ ```
68
+
69
+ Handles the following endpoints automatically:
70
+ - `POST /api/auth/login` — User login (returns `{ user }` or `{ mfa_required, mfa_token }`)
71
+ - `POST /api/auth/register` — User registration
72
+ - `POST /api/auth/mfa-challenge` — MFA verification
73
+ - `POST /api/auth/refresh` — Token refresh (also called automatically by middleware)
74
+ - `POST /api/auth/logout` — User logout
75
+
76
+ #### Calling from the client
77
+
78
+ ```ts
79
+ // Login
80
+ const res = await fetch("/api/auth/login", {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ email, password }),
26
84
  });
85
+ const { user, mfa_required, mfa_token } = await res.json();
86
+
87
+ // MFA (if required)
88
+ const res = await fetch("/api/auth/mfa-challenge", {
89
+ method: "POST",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify({ mfa_token, code }),
92
+ });
93
+
94
+ // Logout
95
+ await fetch("/api/auth/logout", { method: "POST" });
27
96
  ```
28
97
 
29
- ### 2. Use in Pages
98
+ ### 4. Server-Side Auth
99
+
100
+ #### `auth()`
101
+
102
+ Returns the `AuthObject` from the current request context (populated by middleware).
103
+
104
+ ```astro
105
+ ---
106
+ import { auth } from "@inai-dev/astro/server";
107
+
108
+ const authObj = auth(Astro);
109
+ if (!authObj?.userId) {
110
+ return Astro.redirect("/login");
111
+ }
112
+
113
+ // Check roles/permissions
114
+ if (authObj.has({ role: "admin" })) {
115
+ // admin-only logic
116
+ }
117
+ ---
118
+ <p>User: {authObj.userId}</p>
119
+ ```
120
+
121
+ **`AuthObject`:**
122
+
123
+ | Property | Type | Description |
124
+ |---|---|---|
125
+ | `userId` | `string \| null` | Current user ID |
126
+ | `tenantId` | `string \| null` | Tenant ID |
127
+ | `appId` | `string \| null` | Application ID |
128
+ | `envId` | `string \| null` | Environment ID |
129
+ | `orgId` | `string \| null` | Active organization ID |
130
+ | `orgRole` | `string \| null` | Role in active organization |
131
+ | `sessionId` | `string \| null` | Session ID |
132
+ | `getToken()` | `() => Promise<string \| null>` | Get the access token |
133
+ | `has(params)` | `({ role?, permission? }) => boolean` | Check role or permission |
134
+
135
+ #### `currentUser()`
136
+
137
+ Fetches the full `UserResource` from the API.
30
138
 
31
139
  ```astro
32
140
  ---
33
- // src/pages/dashboard.astro
34
- const auth = Astro.locals.auth;
35
- if (!auth?.userId) return Astro.redirect("/sign-in");
141
+ import { currentUser } from "@inai-dev/astro/server";
142
+
143
+ const user = await currentUser(Astro);
144
+ if (!user) return Astro.redirect("/login");
36
145
  ---
37
- <p>Welcome {auth.user.email}</p>
146
+ <p>{user.email}</p>
38
147
  ```
39
148
 
40
- ### 3. API Endpoints
149
+ #### Cookie Helpers
150
+
151
+ For advanced use cases (custom auth flows, manual token management):
41
152
 
42
153
  ```ts
43
- // src/pages/api/auth/[...path].ts
44
- import { handleAuthRoutes } from "@inai-dev/astro/server";
45
- export const ALL = handleAuthRoutes();
154
+ import { setAuthCookies, clearAuthCookies } from "@inai-dev/astro/server";
155
+
156
+ // Set auth cookies after manual authentication
157
+ setAuthCookies(Astro.cookies, tokens, user);
158
+
159
+ // Clear all auth cookies (manual logout)
160
+ clearAuthCookies(Astro.cookies);
46
161
  ```
47
162
 
48
- ## Exports
163
+ ## Exports Reference
164
+
165
+ ### `@inai-dev/astro`
166
+
167
+ | Export | Kind | Description |
168
+ |---|---|---|
169
+ | `inaiAuth` | Function | Astro integration |
170
+ | `inaiAstroMiddleware` | Function | Auth middleware |
171
+ | `auth` | Function | Get `AuthObject` from context |
172
+ | `currentUser` | Function | Get current user from API |
173
+ | `setAuthCookies` | Function | Set auth cookies |
174
+ | `clearAuthCookies` | Function | Clear auth cookies |
175
+ | `createAuthRoutes` | Function | API route handlers |
176
+
177
+ ### `@inai-dev/astro/middleware`
178
+
179
+ | Export | Kind | Description |
180
+ |---|---|---|
181
+ | `inaiAstroMiddleware` | Function | Auth middleware with token refresh |
49
182
 
50
- - `@inai-dev/astro` — Astro integration function
51
- - `@inai-dev/astro/middleware` — Auth middleware
52
- - `@inai-dev/astro/server` Server-side helpers and API route handlers
183
+ ### `@inai-dev/astro/server`
184
+
185
+ | Export | Kind | Description |
186
+ |---|---|---|
187
+ | `auth` | Function | Get `AuthObject` from context |
188
+ | `currentUser` | Function | Get current user |
189
+ | `setAuthCookies` | Function | Set auth cookies |
190
+ | `clearAuthCookies` | Function | Clear auth cookies |
191
+
192
+ ### `@inai-dev/astro/api-routes`
193
+
194
+ | Export | Kind | Description |
195
+ |---|---|---|
196
+ | `createAuthRoutes` | Function | Create API route handlers |
197
+ | `setAuthCookies` | Function | Set auth cookies |
198
+ | `clearAuthCookies` | Function | Clear auth cookies |
199
+
200
+ ## Exported Types
201
+
202
+ ```ts
203
+ import type { InAIAstroConfig } from "@inai-dev/astro";
204
+ import type { InAIAstroMiddlewareConfig } from "@inai-dev/astro/middleware";
205
+ import type { AstroCookies, AstroAPIContext } from "@inai-dev/astro/api-routes";
206
+ import type { AuthObject, UserResource, OrganizationResource } from "@inai-dev/astro";
207
+ ```
53
208
 
54
- ## Documentation
209
+ ## Questions & Support
55
210
 
56
- - [Astro Integration](https://github.com/inai-dev/sdk/blob/main/docs/astro-integration.md)
57
- - [API Reference](https://github.com/inai-dev/sdk/blob/main/docs/api-reference.md)
211
+ Visit [https://inai.dev](https://inai.dev) for documentation, guides, and support.
58
212
 
59
213
  ## License
60
214
 
@@ -0,0 +1,24 @@
1
+ import { InAIAuthConfig, TokenPair, UserResource } from '@inai-dev/types';
2
+
3
+ interface AstroCookies {
4
+ get(name: string): {
5
+ value: string;
6
+ } | undefined;
7
+ set(name: string, value: string, options?: Record<string, unknown>): void;
8
+ delete(name: string, options?: Record<string, unknown>): void;
9
+ }
10
+ interface AstroAPIContext {
11
+ request: Request;
12
+ cookies: AstroCookies;
13
+ params: Record<string, string | undefined>;
14
+ url: URL;
15
+ }
16
+ declare function setAuthCookies(cookies: AstroCookies, tokens: TokenPair, user: UserResource): void;
17
+ declare function clearAuthCookies(cookies: AstroCookies): void;
18
+ declare function createAuthRoutes(config?: InAIAuthConfig): {
19
+ ALL: (context: AstroAPIContext) => Promise<Response>;
20
+ POST: (context: AstroAPIContext) => Promise<Response>;
21
+ GET: (context: AstroAPIContext) => Promise<Response>;
22
+ };
23
+
24
+ export { type AstroAPIContext, type AstroCookies, clearAuthCookies, createAuthRoutes, setAuthCookies };
@@ -0,0 +1,178 @@
1
+ // src/api-routes.ts
2
+ import { InAIAuthClient } from "@inai-dev/backend";
3
+ import {
4
+ COOKIE_AUTH_TOKEN,
5
+ COOKIE_REFRESH_TOKEN,
6
+ COOKIE_AUTH_SESSION,
7
+ decodeJWTPayload
8
+ } from "@inai-dev/shared";
9
+ function setAuthCookies(cookies, tokens, user) {
10
+ const isProduction = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
11
+ const claims = decodeJWTPayload(tokens.access_token);
12
+ const expiresAt = claims ? new Date(claims.exp * 1e3).toISOString() : new Date(Date.now() + tokens.expires_in * 1e3).toISOString();
13
+ cookies.set(COOKIE_AUTH_TOKEN, tokens.access_token, {
14
+ httpOnly: true,
15
+ secure: isProduction,
16
+ sameSite: "lax",
17
+ path: "/",
18
+ maxAge: tokens.expires_in
19
+ });
20
+ cookies.set(COOKIE_REFRESH_TOKEN, tokens.refresh_token, {
21
+ httpOnly: true,
22
+ secure: isProduction,
23
+ sameSite: "strict",
24
+ path: "/api/auth",
25
+ maxAge: 7 * 24 * 60 * 60
26
+ });
27
+ cookies.set(COOKIE_AUTH_SESSION, JSON.stringify({
28
+ user,
29
+ expiresAt,
30
+ permissions: claims?.permissions ?? [],
31
+ orgId: claims?.org_id,
32
+ orgRole: claims?.org_role,
33
+ appId: claims?.app_id,
34
+ envId: claims?.env_id
35
+ }), {
36
+ httpOnly: false,
37
+ secure: isProduction,
38
+ sameSite: "lax",
39
+ path: "/",
40
+ maxAge: tokens.expires_in
41
+ });
42
+ }
43
+ function clearAuthCookies(cookies) {
44
+ cookies.delete(COOKIE_AUTH_TOKEN, { path: "/" });
45
+ cookies.delete(COOKIE_REFRESH_TOKEN, { path: "/api/auth" });
46
+ cookies.delete(COOKIE_AUTH_SESSION, { path: "/" });
47
+ }
48
+ function jsonResponse(data, status = 200) {
49
+ return new Response(JSON.stringify(data), {
50
+ status,
51
+ headers: { "Content-Type": "application/json" }
52
+ });
53
+ }
54
+ function createAuthRoutes(config = {}) {
55
+ const client = new InAIAuthClient(config);
56
+ async function handleLogin(context) {
57
+ try {
58
+ const body = await context.request.json();
59
+ const result = await client.login({
60
+ email: body.email,
61
+ password: body.password
62
+ });
63
+ if (result.mfa_required) {
64
+ return jsonResponse({
65
+ mfa_required: true,
66
+ mfa_token: result.mfa_token
67
+ });
68
+ }
69
+ const tokens = result;
70
+ const loginUser = result.user;
71
+ const user = loginUser ?? (await client.getMe(tokens.access_token)).data;
72
+ setAuthCookies(context.cookies, tokens, user);
73
+ return jsonResponse({ user });
74
+ } catch (err) {
75
+ const message = err instanceof Error ? err.message : "Login failed";
76
+ return jsonResponse({ error: message }, 401);
77
+ }
78
+ }
79
+ async function handleRegister(context) {
80
+ try {
81
+ const body = await context.request.json();
82
+ const result = await client.register({
83
+ email: body.email,
84
+ password: body.password,
85
+ firstName: body.firstName,
86
+ lastName: body.lastName
87
+ });
88
+ if (!result.access_token) {
89
+ return jsonResponse({
90
+ needs_email_verification: true,
91
+ user: result.user
92
+ });
93
+ }
94
+ const tokens = result;
95
+ const loginUser = result.user;
96
+ const user = loginUser ?? (await client.getMe(tokens.access_token)).data;
97
+ setAuthCookies(context.cookies, tokens, user);
98
+ return jsonResponse({ user });
99
+ } catch (err) {
100
+ const message = err instanceof Error ? err.message : "Registration failed";
101
+ return jsonResponse({ error: message }, 400);
102
+ }
103
+ }
104
+ async function handleMFAChallenge(context) {
105
+ try {
106
+ const body = await context.request.json();
107
+ const tokens = await client.mfaChallenge({
108
+ mfa_token: body.mfa_token,
109
+ code: body.code
110
+ });
111
+ const { data: user } = await client.getMe(tokens.access_token);
112
+ setAuthCookies(context.cookies, tokens, user);
113
+ return jsonResponse({ user });
114
+ } catch (err) {
115
+ const message = err instanceof Error ? err.message : "MFA verification failed";
116
+ return jsonResponse({ error: message }, 401);
117
+ }
118
+ }
119
+ async function handleRefresh(context) {
120
+ try {
121
+ const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;
122
+ if (!refreshToken) {
123
+ clearAuthCookies(context.cookies);
124
+ return jsonResponse({ error: "No refresh token" }, 401);
125
+ }
126
+ const tokens = await client.refresh(refreshToken);
127
+ const { data: user } = await client.getMe(tokens.access_token);
128
+ setAuthCookies(context.cookies, tokens, user);
129
+ return jsonResponse({ user });
130
+ } catch {
131
+ clearAuthCookies(context.cookies);
132
+ return jsonResponse({ error: "Refresh failed" }, 401);
133
+ }
134
+ }
135
+ async function handleLogout(context) {
136
+ try {
137
+ const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;
138
+ if (refreshToken) {
139
+ await client.logout(refreshToken).catch(() => {
140
+ });
141
+ }
142
+ clearAuthCookies(context.cookies);
143
+ return jsonResponse({ success: true });
144
+ } catch {
145
+ clearAuthCookies(context.cookies);
146
+ return jsonResponse({ success: true });
147
+ }
148
+ }
149
+ async function handler(context) {
150
+ const path = context.params.path ?? "";
151
+ if (context.request.method === "POST") {
152
+ switch (path) {
153
+ case "login":
154
+ return handleLogin(context);
155
+ case "register":
156
+ return handleRegister(context);
157
+ case "mfa-challenge":
158
+ return handleMFAChallenge(context);
159
+ case "refresh":
160
+ return handleRefresh(context);
161
+ case "logout":
162
+ return handleLogout(context);
163
+ }
164
+ }
165
+ return jsonResponse({ error: "Not found" }, 404);
166
+ }
167
+ return {
168
+ ALL: handler,
169
+ POST: handler,
170
+ GET: handler
171
+ };
172
+ }
173
+ export {
174
+ clearAuthCookies,
175
+ createAuthRoutes,
176
+ setAuthCookies
177
+ };
178
+ //# sourceMappingURL=api-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api-routes.ts"],"sourcesContent":["import type { InAIAuthConfig, TokenPair, UserResource, LoginResult } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\ninterface AstroCookies {\n get(name: string): { value: string } | undefined;\n set(name: string, value: string, options?: Record<string, unknown>): void;\n delete(name: string, options?: Record<string, unknown>): void;\n}\n\ninterface AstroAPIContext {\n request: Request;\n cookies: AstroCookies;\n params: Record<string, string | undefined>;\n url: URL;\n}\n\nfunction setAuthCookies(\n cookies: AstroCookies,\n tokens: TokenPair,\n user: UserResource,\n): void {\n const isProduction = typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\";\n const claims = decodeJWTPayload(tokens.access_token);\n const expiresAt = claims\n ? new Date(claims.exp * 1000).toISOString()\n : new Date(Date.now() + tokens.expires_in * 1000).toISOString();\n\n cookies.set(COOKIE_AUTH_TOKEN, tokens.access_token, {\n httpOnly: true,\n secure: isProduction,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n });\n\n cookies.set(COOKIE_REFRESH_TOKEN, tokens.refresh_token, {\n httpOnly: true,\n secure: isProduction,\n sameSite: \"strict\",\n path: \"/api/auth\",\n maxAge: 7 * 24 * 60 * 60,\n });\n\n cookies.set(COOKIE_AUTH_SESSION, JSON.stringify({\n user,\n expiresAt,\n permissions: claims?.permissions ?? [],\n orgId: claims?.org_id,\n orgRole: claims?.org_role,\n appId: claims?.app_id,\n envId: claims?.env_id,\n }), {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n });\n}\n\nfunction clearAuthCookies(cookies: AstroCookies): void {\n cookies.delete(COOKIE_AUTH_TOKEN, { path: \"/\" });\n cookies.delete(COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n cookies.delete(COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n\nfunction jsonResponse(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nexport function createAuthRoutes(config: InAIAuthConfig = {}) {\n const client = new InAIAuthClient(config);\n\n async function handleLogin(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const result = await client.login({\n email: body.email,\n password: body.password,\n }) as LoginResult & { user?: UserResource };\n\n if (result.mfa_required) {\n return jsonResponse({\n mfa_required: true,\n mfa_token: result.mfa_token,\n });\n }\n\n const tokens = result as unknown as TokenPair;\n const loginUser = result.user;\n const user = loginUser ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Login failed\";\n return jsonResponse({ error: message }, 401);\n }\n }\n\n async function handleRegister(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const result = await client.register({\n email: body.email,\n password: body.password,\n firstName: body.firstName,\n lastName: body.lastName,\n });\n\n if (!result.access_token) {\n return jsonResponse({\n needs_email_verification: true,\n user: result.user,\n });\n }\n\n const tokens = result as unknown as TokenPair;\n const loginUser = result.user;\n const user = loginUser ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Registration failed\";\n return jsonResponse({ error: message }, 400);\n }\n }\n\n async function handleMFAChallenge(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const tokens = await client.mfaChallenge({\n mfa_token: body.mfa_token,\n code: body.code,\n });\n\n const { data: user } = await client.getMe(tokens.access_token);\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"MFA verification failed\";\n return jsonResponse({ error: message }, 401);\n }\n }\n\n async function handleRefresh(context: AstroAPIContext): Promise<Response> {\n try {\n const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n\n if (!refreshToken) {\n clearAuthCookies(context.cookies);\n return jsonResponse({ error: \"No refresh token\" }, 401);\n }\n\n const tokens = await client.refresh(refreshToken);\n const { data: user } = await client.getMe(tokens.access_token);\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch {\n clearAuthCookies(context.cookies);\n return jsonResponse({ error: \"Refresh failed\" }, 401);\n }\n }\n\n async function handleLogout(context: AstroAPIContext): Promise<Response> {\n try {\n const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n if (refreshToken) {\n await client.logout(refreshToken).catch(() => {});\n }\n clearAuthCookies(context.cookies);\n return jsonResponse({ success: true });\n } catch {\n clearAuthCookies(context.cookies);\n return jsonResponse({ success: true });\n }\n }\n\n async function handler(context: AstroAPIContext): Promise<Response> {\n const path = context.params.path ?? \"\";\n\n if (context.request.method === \"POST\") {\n switch (path) {\n case \"login\":\n return handleLogin(context);\n case \"register\":\n return handleRegister(context);\n case \"mfa-challenge\":\n return handleMFAChallenge(context);\n case \"refresh\":\n return handleRefresh(context);\n case \"logout\":\n return handleLogout(context);\n }\n }\n\n return jsonResponse({ error: \"Not found\" }, 404);\n }\n\n return {\n ALL: handler,\n POST: handler,\n GET: handler,\n };\n}\n\nexport { setAuthCookies, clearAuthCookies };\nexport type { AstroCookies, AstroAPIContext };\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import { AstroIntegration } from 'astro';
2
2
  export { InAIAstroMiddlewareConfig, inaiAstroMiddleware } from './middleware.js';
3
3
  export { auth, currentUser } from './server.js';
4
+ export { AstroAPIContext, AstroCookies, clearAuthCookies, createAuthRoutes, setAuthCookies } from './api-routes.js';
4
5
  export { AuthObject, OrganizationResource, UserResource } from '@inai-dev/types';
5
6
 
6
7
  interface InAIAstroConfig {
7
- apiUrl: string;
8
- publishableKey?: string;
9
8
  }
10
- declare function inaiAuth(config: InAIAstroConfig): AstroIntegration;
9
+ declare function inaiAuth(_config?: InAIAstroConfig): AstroIntegration;
11
10
 
12
11
  export { type InAIAstroConfig, inaiAuth };
package/dist/index.js CHANGED
@@ -1,27 +1,15 @@
1
1
  // src/integration.ts
2
- function inaiAuth(config) {
2
+ function inaiAuth(_config = {}) {
3
3
  return {
4
4
  name: "@inai-dev/astro",
5
- hooks: {
6
- "astro:config:setup": ({ updateConfig }) => {
7
- updateConfig({
8
- vite: {
9
- define: {
10
- "import.meta.env.INAI_API_URL": JSON.stringify(config.apiUrl),
11
- "import.meta.env.INAI_PUBLISHABLE_KEY": JSON.stringify(
12
- config.publishableKey ?? ""
13
- )
14
- }
15
- }
16
- });
17
- }
18
- }
5
+ hooks: {}
19
6
  };
20
7
  }
21
8
 
22
9
  // src/middleware.ts
23
10
  import {
24
11
  COOKIE_AUTH_TOKEN,
12
+ COOKIE_REFRESH_TOKEN,
25
13
  getClaimsFromToken,
26
14
  isTokenExpired
27
15
  } from "@inai-dev/shared";
@@ -38,8 +26,30 @@ function inaiAstroMiddleware(config = {}) {
38
26
  if (isPublic) {
39
27
  return next();
40
28
  }
41
- const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;
29
+ let token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;
42
30
  if (!token || isTokenExpired(token)) {
31
+ const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;
32
+ if (refreshToken) {
33
+ try {
34
+ const refreshUrl = new URL("/api/auth/refresh", context.url.origin);
35
+ const refreshRes = await fetch(refreshUrl.toString(), {
36
+ method: "POST",
37
+ headers: {
38
+ "Content-Type": "application/json",
39
+ Cookie: context.request.headers.get("cookie") ?? ""
40
+ }
41
+ });
42
+ if (refreshRes.ok) {
43
+ const setCookies = refreshRes.headers.getSetCookie?.() ?? [];
44
+ const response = await next();
45
+ for (const cookie of setCookies) {
46
+ response.headers.append("Set-Cookie", cookie);
47
+ }
48
+ return response;
49
+ }
50
+ } catch {
51
+ }
52
+ }
43
53
  return context.redirect(
44
54
  `${signInUrl}?returnTo=${encodeURIComponent(pathname)}`
45
55
  );
@@ -72,16 +82,191 @@ function inaiAstroMiddleware(config = {}) {
72
82
  }
73
83
 
74
84
  // src/server.ts
75
- import { InAIAuthClient } from "@inai-dev/backend";
85
+ import { InAIAuthClient as InAIAuthClient2 } from "@inai-dev/backend";
76
86
  import {
77
- COOKIE_AUTH_TOKEN as COOKIE_AUTH_TOKEN2,
87
+ COOKIE_AUTH_TOKEN as COOKIE_AUTH_TOKEN3,
78
88
  getClaimsFromToken as getClaimsFromToken2,
79
89
  isTokenExpired as isTokenExpired2
80
90
  } from "@inai-dev/shared";
91
+
92
+ // src/api-routes.ts
93
+ import { InAIAuthClient } from "@inai-dev/backend";
94
+ import {
95
+ COOKIE_AUTH_TOKEN as COOKIE_AUTH_TOKEN2,
96
+ COOKIE_REFRESH_TOKEN as COOKIE_REFRESH_TOKEN2,
97
+ COOKIE_AUTH_SESSION,
98
+ decodeJWTPayload
99
+ } from "@inai-dev/shared";
100
+ function setAuthCookies(cookies, tokens, user) {
101
+ const isProduction = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
102
+ const claims = decodeJWTPayload(tokens.access_token);
103
+ const expiresAt = claims ? new Date(claims.exp * 1e3).toISOString() : new Date(Date.now() + tokens.expires_in * 1e3).toISOString();
104
+ cookies.set(COOKIE_AUTH_TOKEN2, tokens.access_token, {
105
+ httpOnly: true,
106
+ secure: isProduction,
107
+ sameSite: "lax",
108
+ path: "/",
109
+ maxAge: tokens.expires_in
110
+ });
111
+ cookies.set(COOKIE_REFRESH_TOKEN2, tokens.refresh_token, {
112
+ httpOnly: true,
113
+ secure: isProduction,
114
+ sameSite: "strict",
115
+ path: "/api/auth",
116
+ maxAge: 7 * 24 * 60 * 60
117
+ });
118
+ cookies.set(COOKIE_AUTH_SESSION, JSON.stringify({
119
+ user,
120
+ expiresAt,
121
+ permissions: claims?.permissions ?? [],
122
+ orgId: claims?.org_id,
123
+ orgRole: claims?.org_role,
124
+ appId: claims?.app_id,
125
+ envId: claims?.env_id
126
+ }), {
127
+ httpOnly: false,
128
+ secure: isProduction,
129
+ sameSite: "lax",
130
+ path: "/",
131
+ maxAge: tokens.expires_in
132
+ });
133
+ }
134
+ function clearAuthCookies(cookies) {
135
+ cookies.delete(COOKIE_AUTH_TOKEN2, { path: "/" });
136
+ cookies.delete(COOKIE_REFRESH_TOKEN2, { path: "/api/auth" });
137
+ cookies.delete(COOKIE_AUTH_SESSION, { path: "/" });
138
+ }
139
+ function jsonResponse(data, status = 200) {
140
+ return new Response(JSON.stringify(data), {
141
+ status,
142
+ headers: { "Content-Type": "application/json" }
143
+ });
144
+ }
145
+ function createAuthRoutes(config = {}) {
146
+ const client = new InAIAuthClient(config);
147
+ async function handleLogin(context) {
148
+ try {
149
+ const body = await context.request.json();
150
+ const result = await client.login({
151
+ email: body.email,
152
+ password: body.password
153
+ });
154
+ if (result.mfa_required) {
155
+ return jsonResponse({
156
+ mfa_required: true,
157
+ mfa_token: result.mfa_token
158
+ });
159
+ }
160
+ const tokens = result;
161
+ const loginUser = result.user;
162
+ const user = loginUser ?? (await client.getMe(tokens.access_token)).data;
163
+ setAuthCookies(context.cookies, tokens, user);
164
+ return jsonResponse({ user });
165
+ } catch (err) {
166
+ const message = err instanceof Error ? err.message : "Login failed";
167
+ return jsonResponse({ error: message }, 401);
168
+ }
169
+ }
170
+ async function handleRegister(context) {
171
+ try {
172
+ const body = await context.request.json();
173
+ const result = await client.register({
174
+ email: body.email,
175
+ password: body.password,
176
+ firstName: body.firstName,
177
+ lastName: body.lastName
178
+ });
179
+ if (!result.access_token) {
180
+ return jsonResponse({
181
+ needs_email_verification: true,
182
+ user: result.user
183
+ });
184
+ }
185
+ const tokens = result;
186
+ const loginUser = result.user;
187
+ const user = loginUser ?? (await client.getMe(tokens.access_token)).data;
188
+ setAuthCookies(context.cookies, tokens, user);
189
+ return jsonResponse({ user });
190
+ } catch (err) {
191
+ const message = err instanceof Error ? err.message : "Registration failed";
192
+ return jsonResponse({ error: message }, 400);
193
+ }
194
+ }
195
+ async function handleMFAChallenge(context) {
196
+ try {
197
+ const body = await context.request.json();
198
+ const tokens = await client.mfaChallenge({
199
+ mfa_token: body.mfa_token,
200
+ code: body.code
201
+ });
202
+ const { data: user } = await client.getMe(tokens.access_token);
203
+ setAuthCookies(context.cookies, tokens, user);
204
+ return jsonResponse({ user });
205
+ } catch (err) {
206
+ const message = err instanceof Error ? err.message : "MFA verification failed";
207
+ return jsonResponse({ error: message }, 401);
208
+ }
209
+ }
210
+ async function handleRefresh(context) {
211
+ try {
212
+ const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN2)?.value;
213
+ if (!refreshToken) {
214
+ clearAuthCookies(context.cookies);
215
+ return jsonResponse({ error: "No refresh token" }, 401);
216
+ }
217
+ const tokens = await client.refresh(refreshToken);
218
+ const { data: user } = await client.getMe(tokens.access_token);
219
+ setAuthCookies(context.cookies, tokens, user);
220
+ return jsonResponse({ user });
221
+ } catch {
222
+ clearAuthCookies(context.cookies);
223
+ return jsonResponse({ error: "Refresh failed" }, 401);
224
+ }
225
+ }
226
+ async function handleLogout(context) {
227
+ try {
228
+ const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN2)?.value;
229
+ if (refreshToken) {
230
+ await client.logout(refreshToken).catch(() => {
231
+ });
232
+ }
233
+ clearAuthCookies(context.cookies);
234
+ return jsonResponse({ success: true });
235
+ } catch {
236
+ clearAuthCookies(context.cookies);
237
+ return jsonResponse({ success: true });
238
+ }
239
+ }
240
+ async function handler(context) {
241
+ const path = context.params.path ?? "";
242
+ if (context.request.method === "POST") {
243
+ switch (path) {
244
+ case "login":
245
+ return handleLogin(context);
246
+ case "register":
247
+ return handleRegister(context);
248
+ case "mfa-challenge":
249
+ return handleMFAChallenge(context);
250
+ case "refresh":
251
+ return handleRefresh(context);
252
+ case "logout":
253
+ return handleLogout(context);
254
+ }
255
+ }
256
+ return jsonResponse({ error: "Not found" }, 404);
257
+ }
258
+ return {
259
+ ALL: handler,
260
+ POST: handler,
261
+ GET: handler
262
+ };
263
+ }
264
+
265
+ // src/server.ts
81
266
  function auth(context) {
82
267
  const existing = context.locals.auth;
83
268
  if (existing) return existing;
84
- const token = context.cookies.get(COOKIE_AUTH_TOKEN2)?.value;
269
+ const token = context.cookies.get(COOKIE_AUTH_TOKEN3)?.value;
85
270
  if (!token || isTokenExpired2(token)) return null;
86
271
  const claims = getClaimsFromToken2(token);
87
272
  if (!claims) return null;
@@ -105,11 +290,10 @@ function auth(context) {
105
290
  };
106
291
  }
107
292
  async function currentUser(context, config) {
108
- const token = context.cookies.get(COOKIE_AUTH_TOKEN2)?.value;
293
+ const token = context.cookies.get(COOKIE_AUTH_TOKEN3)?.value;
109
294
  if (!token || isTokenExpired2(token)) return null;
110
- const client = new InAIAuthClient({
111
- apiUrl: config.apiUrl,
112
- publishableKey: config.publishableKey
295
+ const client = new InAIAuthClient2({
296
+ publishableKey: config?.publishableKey
113
297
  });
114
298
  try {
115
299
  const { data } = await client.getMe(token);
@@ -120,8 +304,11 @@ async function currentUser(context, config) {
120
304
  }
121
305
  export {
122
306
  auth,
307
+ clearAuthCookies,
308
+ createAuthRoutes,
123
309
  currentUser,
124
310
  inaiAstroMiddleware,
125
- inaiAuth
311
+ inaiAuth,
312
+ setAuthCookies
126
313
  };
127
314
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/integration.ts","../src/middleware.ts","../src/server.ts"],"sourcesContent":["import type { AstroIntegration } from \"astro\";\n\nexport interface InAIAstroConfig {\n apiUrl: string;\n publishableKey?: string;\n}\n\nexport function inaiAuth(config: InAIAstroConfig): AstroIntegration {\n return {\n name: \"@inai-dev/astro\",\n hooks: {\n \"astro:config:setup\": ({ updateConfig }) => {\n updateConfig({\n vite: {\n define: {\n \"import.meta.env.INAI_API_URL\": JSON.stringify(config.apiUrl),\n \"import.meta.env.INAI_PUBLISHABLE_KEY\": JSON.stringify(\n config.publishableKey ?? \"\",\n ),\n },\n },\n });\n },\n },\n };\n}\n","import type { MiddlewareHandler } from \"astro\";\nimport type { AuthObject } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\nexport interface InAIAstroMiddlewareConfig {\n publicRoutes?: string[];\n signInUrl?: string;\n}\n\nexport function inaiAstroMiddleware(\n config: InAIAstroMiddlewareConfig = {},\n): MiddlewareHandler {\n const { publicRoutes = [], signInUrl = \"/login\" } = config;\n\n return async (context, next) => {\n const { pathname } = context.url;\n\n const isPublic =\n publicRoutes.some((route) => {\n if (route.endsWith(\"*\")) {\n return pathname.startsWith(route.slice(0, -1));\n }\n return pathname === route;\n }) ||\n pathname === signInUrl ||\n pathname.startsWith(\"/_\") ||\n pathname.startsWith(\"/api/\");\n\n if (isPublic) {\n return next();\n }\n\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n\n if (!token || isTokenExpired(token)) {\n return context.redirect(\n `${signInUrl}?returnTo=${encodeURIComponent(pathname)}`,\n );\n }\n\n const claims = getClaimsFromToken(token);\n if (!claims) {\n return context.redirect(signInUrl);\n }\n\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\n\n const authObject: AuthObject = {\n userId: claims.sub,\n tenantId: claims.tenant_id,\n appId: claims.app_id ?? null,\n envId: claims.env_id ?? null,\n orgId: claims.org_id ?? null,\n orgRole: claims.org_role ?? null,\n sessionId: null,\n getToken: async () => token,\n has: (params: { role?: string; permission?: string }) => {\n if (params.role && roles.includes(params.role)) return true;\n if (params.permission && permissions.includes(params.permission))\n return true;\n return false;\n },\n };\n\n (context.locals as Record<string, unknown>).auth = authObject;\n\n return next();\n };\n}\n","import type { AuthObject, UserResource } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n COOKIE_AUTH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\ninterface AstroContext {\n cookies: {\n get(name: string): { value: string } | undefined;\n };\n locals: Record<string, unknown>;\n}\n\nexport function auth(context: AstroContext): AuthObject | null {\n const existing = (context.locals as Record<string, unknown>).auth as AuthObject | undefined;\n if (existing) return existing;\n\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n if (!token || isTokenExpired(token)) return null;\n\n const claims = getClaimsFromToken(token);\n if (!claims) return null;\n\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\n\n return {\n userId: claims.sub,\n tenantId: claims.tenant_id,\n appId: claims.app_id ?? null,\n envId: claims.env_id ?? null,\n orgId: claims.org_id ?? null,\n orgRole: claims.org_role ?? null,\n sessionId: null,\n getToken: async () => token,\n has: (params: { role?: string; permission?: string }) => {\n if (params.role && roles.includes(params.role)) return true;\n if (params.permission && permissions.includes(params.permission))\n return true;\n return false;\n },\n };\n}\n\nexport async function currentUser(\n context: AstroContext,\n config: { apiUrl: string; publishableKey?: string },\n): Promise<UserResource | null> {\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n if (!token || isTokenExpired(token)) return null;\n\n const client = new InAIAuthClient({\n apiUrl: config.apiUrl,\n publishableKey: config.publishableKey,\n });\n\n try {\n const { data } = await client.getMe(token);\n return data;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["COOKIE_AUTH_TOKEN","getClaimsFromToken","isTokenExpired"]}
1
+ {"version":3,"sources":["../src/integration.ts","../src/middleware.ts","../src/server.ts","../src/api-routes.ts"],"sourcesContent":["import type { AstroIntegration } from \"astro\";\n\nexport interface InAIAstroConfig {}\n\nexport function inaiAuth(_config: InAIAstroConfig = {}): AstroIntegration {\n return {\n name: \"@inai-dev/astro\",\n hooks: {},\n };\n}\n","import type { MiddlewareHandler } from \"astro\";\nimport type { AuthObject } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\nexport interface InAIAstroMiddlewareConfig {\n publicRoutes?: string[];\n signInUrl?: string;\n}\n\nexport function inaiAstroMiddleware(\n config: InAIAstroMiddlewareConfig = {},\n): MiddlewareHandler {\n const { publicRoutes = [], signInUrl = \"/login\" } = config;\n\n return async (context, next) => {\n const { pathname } = context.url;\n\n const isPublic =\n publicRoutes.some((route) => {\n if (route.endsWith(\"*\")) {\n return pathname.startsWith(route.slice(0, -1));\n }\n return pathname === route;\n }) ||\n pathname === signInUrl ||\n pathname.startsWith(\"/_\") ||\n pathname.startsWith(\"/api/\");\n\n if (isPublic) {\n return next();\n }\n\n let token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n if (refreshToken) {\n try {\n const refreshUrl = new URL(\"/api/auth/refresh\", context.url.origin);\n const refreshRes = await fetch(refreshUrl.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Cookie: context.request.headers.get(\"cookie\") ?? \"\",\n },\n });\n if (refreshRes.ok) {\n const setCookies = refreshRes.headers.getSetCookie?.() ?? [];\n const response = await next();\n for (const cookie of setCookies) {\n response.headers.append(\"Set-Cookie\", cookie);\n }\n return response;\n }\n } catch {\n // Refresh failed, redirect to sign-in\n }\n }\n\n return context.redirect(\n `${signInUrl}?returnTo=${encodeURIComponent(pathname)}`,\n );\n }\n\n const claims = getClaimsFromToken(token);\n if (!claims) {\n return context.redirect(signInUrl);\n }\n\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\n\n const authObject: AuthObject = {\n userId: claims.sub,\n tenantId: claims.tenant_id,\n appId: claims.app_id ?? null,\n envId: claims.env_id ?? null,\n orgId: claims.org_id ?? null,\n orgRole: claims.org_role ?? null,\n sessionId: null,\n getToken: async () => token,\n has: (params: { role?: string; permission?: string }) => {\n if (params.role && roles.includes(params.role)) return true;\n if (params.permission && permissions.includes(params.permission))\n return true;\n return false;\n },\n };\n\n (context.locals as Record<string, unknown>).auth = authObject;\n\n return next();\n };\n}\n","import type { AuthObject, UserResource } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n COOKIE_AUTH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\ninterface AstroContext {\n cookies: {\n get(name: string): { value: string } | undefined;\n };\n locals: Record<string, unknown>;\n}\n\nexport function auth(context: AstroContext): AuthObject | null {\n const existing = (context.locals as Record<string, unknown>).auth as AuthObject | undefined;\n if (existing) return existing;\n\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n if (!token || isTokenExpired(token)) return null;\n\n const claims = getClaimsFromToken(token);\n if (!claims) return null;\n\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\n\n return {\n userId: claims.sub,\n tenantId: claims.tenant_id,\n appId: claims.app_id ?? null,\n envId: claims.env_id ?? null,\n orgId: claims.org_id ?? null,\n orgRole: claims.org_role ?? null,\n sessionId: null,\n getToken: async () => token,\n has: (params: { role?: string; permission?: string }) => {\n if (params.role && roles.includes(params.role)) return true;\n if (params.permission && permissions.includes(params.permission))\n return true;\n return false;\n },\n };\n}\n\nexport async function currentUser(\n context: AstroContext,\n config?: { publishableKey?: string },\n): Promise<UserResource | null> {\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n if (!token || isTokenExpired(token)) return null;\n\n const client = new InAIAuthClient({\n publishableKey: config?.publishableKey,\n });\n\n try {\n const { data } = await client.getMe(token);\n return data;\n } catch {\n return null;\n }\n}\n\nexport { setAuthCookies, clearAuthCookies } from \"./api-routes\";\nexport type { AstroCookies } from \"./api-routes\";\n","import type { InAIAuthConfig, TokenPair, UserResource, LoginResult } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\ninterface AstroCookies {\n get(name: string): { value: string } | undefined;\n set(name: string, value: string, options?: Record<string, unknown>): void;\n delete(name: string, options?: Record<string, unknown>): void;\n}\n\ninterface AstroAPIContext {\n request: Request;\n cookies: AstroCookies;\n params: Record<string, string | undefined>;\n url: URL;\n}\n\nfunction setAuthCookies(\n cookies: AstroCookies,\n tokens: TokenPair,\n user: UserResource,\n): void {\n const isProduction = typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\";\n const claims = decodeJWTPayload(tokens.access_token);\n const expiresAt = claims\n ? new Date(claims.exp * 1000).toISOString()\n : new Date(Date.now() + tokens.expires_in * 1000).toISOString();\n\n cookies.set(COOKIE_AUTH_TOKEN, tokens.access_token, {\n httpOnly: true,\n secure: isProduction,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n });\n\n cookies.set(COOKIE_REFRESH_TOKEN, tokens.refresh_token, {\n httpOnly: true,\n secure: isProduction,\n sameSite: \"strict\",\n path: \"/api/auth\",\n maxAge: 7 * 24 * 60 * 60,\n });\n\n cookies.set(COOKIE_AUTH_SESSION, JSON.stringify({\n user,\n expiresAt,\n permissions: claims?.permissions ?? [],\n orgId: claims?.org_id,\n orgRole: claims?.org_role,\n appId: claims?.app_id,\n envId: claims?.env_id,\n }), {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n });\n}\n\nfunction clearAuthCookies(cookies: AstroCookies): void {\n cookies.delete(COOKIE_AUTH_TOKEN, { path: \"/\" });\n cookies.delete(COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n cookies.delete(COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n\nfunction jsonResponse(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nexport function createAuthRoutes(config: InAIAuthConfig = {}) {\n const client = new InAIAuthClient(config);\n\n async function handleLogin(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const result = await client.login({\n email: body.email,\n password: body.password,\n }) as LoginResult & { user?: UserResource };\n\n if (result.mfa_required) {\n return jsonResponse({\n mfa_required: true,\n mfa_token: result.mfa_token,\n });\n }\n\n const tokens = result as unknown as TokenPair;\n const loginUser = result.user;\n const user = loginUser ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Login failed\";\n return jsonResponse({ error: message }, 401);\n }\n }\n\n async function handleRegister(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const result = await client.register({\n email: body.email,\n password: body.password,\n firstName: body.firstName,\n lastName: body.lastName,\n });\n\n if (!result.access_token) {\n return jsonResponse({\n needs_email_verification: true,\n user: result.user,\n });\n }\n\n const tokens = result as unknown as TokenPair;\n const loginUser = result.user;\n const user = loginUser ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Registration failed\";\n return jsonResponse({ error: message }, 400);\n }\n }\n\n async function handleMFAChallenge(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const tokens = await client.mfaChallenge({\n mfa_token: body.mfa_token,\n code: body.code,\n });\n\n const { data: user } = await client.getMe(tokens.access_token);\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"MFA verification failed\";\n return jsonResponse({ error: message }, 401);\n }\n }\n\n async function handleRefresh(context: AstroAPIContext): Promise<Response> {\n try {\n const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n\n if (!refreshToken) {\n clearAuthCookies(context.cookies);\n return jsonResponse({ error: \"No refresh token\" }, 401);\n }\n\n const tokens = await client.refresh(refreshToken);\n const { data: user } = await client.getMe(tokens.access_token);\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch {\n clearAuthCookies(context.cookies);\n return jsonResponse({ error: \"Refresh failed\" }, 401);\n }\n }\n\n async function handleLogout(context: AstroAPIContext): Promise<Response> {\n try {\n const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n if (refreshToken) {\n await client.logout(refreshToken).catch(() => {});\n }\n clearAuthCookies(context.cookies);\n return jsonResponse({ success: true });\n } catch {\n clearAuthCookies(context.cookies);\n return jsonResponse({ success: true });\n }\n }\n\n async function handler(context: AstroAPIContext): Promise<Response> {\n const path = context.params.path ?? \"\";\n\n if (context.request.method === \"POST\") {\n switch (path) {\n case \"login\":\n return handleLogin(context);\n case \"register\":\n return handleRegister(context);\n case \"mfa-challenge\":\n return handleMFAChallenge(context);\n case \"refresh\":\n return handleRefresh(context);\n case \"logout\":\n return handleLogout(context);\n }\n }\n\n return jsonResponse({ error: \"Not found\" }, 404);\n }\n\n return {\n ALL: handler,\n POST: handler,\n GET: handler,\n };\n}\n\nexport { setAuthCookies, clearAuthCookies };\nexport type { AstroCookies, AstroAPIContext };\n"],"mappings":";AAIO,SAAS,SAAS,UAA2B,CAAC,GAAqB;AACxE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,CAAC;AAAA,EACV;AACF;;;ACPA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAOA,SAAS,oBACd,SAAoC,CAAC,GAClB;AACnB,QAAM,EAAE,eAAe,CAAC,GAAG,YAAY,SAAS,IAAI;AAEpD,SAAO,OAAO,SAAS,SAAS;AAC9B,UAAM,EAAE,SAAS,IAAI,QAAQ;AAE7B,UAAM,WACJ,aAAa,KAAK,CAAC,UAAU;AAC3B,UAAI,MAAM,SAAS,GAAG,GAAG;AACvB,eAAO,SAAS,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC;AAAA,MAC/C;AACA,aAAO,aAAa;AAAA,IACtB,CAAC,KACD,aAAa,aACb,SAAS,WAAW,IAAI,KACxB,SAAS,WAAW,OAAO;AAE7B,QAAI,UAAU;AACZ,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,QAAQ,QAAQ,QAAQ,IAAI,iBAAiB,GAAG;AAEpD,QAAI,CAAC,SAAS,eAAe,KAAK,GAAG;AACnC,YAAM,eAAe,QAAQ,QAAQ,IAAI,oBAAoB,GAAG;AAChE,UAAI,cAAc;AAChB,YAAI;AACF,gBAAM,aAAa,IAAI,IAAI,qBAAqB,QAAQ,IAAI,MAAM;AAClE,gBAAM,aAAa,MAAM,MAAM,WAAW,SAAS,GAAG;AAAA,YACpD,QAAQ;AAAA,YACR,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,QAAQ,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAAA,YACnD;AAAA,UACF,CAAC;AACD,cAAI,WAAW,IAAI;AACjB,kBAAM,aAAa,WAAW,QAAQ,eAAe,KAAK,CAAC;AAC3D,kBAAM,WAAW,MAAM,KAAK;AAC5B,uBAAW,UAAU,YAAY;AAC/B,uBAAS,QAAQ,OAAO,cAAc,MAAM;AAAA,YAC9C;AACA,mBAAO;AAAA,UACT;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,aAAO,QAAQ;AAAA,QACb,GAAG,SAAS,aAAa,mBAAmB,QAAQ,CAAC;AAAA,MACvD;AAAA,IACF;AAEA,UAAM,SAAS,mBAAmB,KAAK;AACvC,QAAI,CAAC,QAAQ;AACX,aAAO,QAAQ,SAAS,SAAS;AAAA,IACnC;AAEA,UAAM,QAAQ,OAAO,SAAS,CAAC;AAC/B,UAAM,cAAc,OAAO,eAAe,CAAC;AAE3C,UAAM,aAAyB;AAAA,MAC7B,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,OAAO,OAAO,UAAU;AAAA,MACxB,OAAO,OAAO,UAAU;AAAA,MACxB,OAAO,OAAO,UAAU;AAAA,MACxB,SAAS,OAAO,YAAY;AAAA,MAC5B,WAAW;AAAA,MACX,UAAU,YAAY;AAAA,MACtB,KAAK,CAAC,WAAmD;AACvD,YAAI,OAAO,QAAQ,MAAM,SAAS,OAAO,IAAI,EAAG,QAAO;AACvD,YAAI,OAAO,cAAc,YAAY,SAAS,OAAO,UAAU;AAC7D,iBAAO;AACT,eAAO;AAAA,MACT;AAAA,IACF;AAEA,IAAC,QAAQ,OAAmC,OAAO;AAEnD,WAAO,KAAK;AAAA,EACd;AACF;;;ACjGA,SAAS,kBAAAA,uBAAsB;AAC/B;AAAA,EACE,qBAAAC;AAAA,EACA,sBAAAC;AAAA,EACA,kBAAAC;AAAA,OACK;;;ACLP,SAAS,sBAAsB;AAC/B;AAAA,EACE,qBAAAC;AAAA,EACA,wBAAAC;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAeP,SAAS,eACP,SACA,QACA,MACM;AACN,QAAM,eAAe,OAAO,YAAY,eAAe,QAAQ,KAAK,aAAa;AACjF,QAAM,SAAS,iBAAiB,OAAO,YAAY;AACnD,QAAM,YAAY,SACd,IAAI,KAAK,OAAO,MAAM,GAAI,EAAE,YAAY,IACxC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,aAAa,GAAI,EAAE,YAAY;AAEhE,UAAQ,IAAID,oBAAmB,OAAO,cAAc;AAAA,IAClD,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ,OAAO;AAAA,EACjB,CAAC;AAED,UAAQ,IAAIC,uBAAsB,OAAO,eAAe;AAAA,IACtD,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ,IAAI,KAAK,KAAK;AAAA,EACxB,CAAC;AAED,UAAQ,IAAI,qBAAqB,KAAK,UAAU;AAAA,IAC9C;AAAA,IACA;AAAA,IACA,aAAa,QAAQ,eAAe,CAAC;AAAA,IACrC,OAAO,QAAQ;AAAA,IACf,SAAS,QAAQ;AAAA,IACjB,OAAO,QAAQ;AAAA,IACf,OAAO,QAAQ;AAAA,EACjB,CAAC,GAAG;AAAA,IACF,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ,OAAO;AAAA,EACjB,CAAC;AACH;AAEA,SAAS,iBAAiB,SAA6B;AACrD,UAAQ,OAAOD,oBAAmB,EAAE,MAAM,IAAI,CAAC;AAC/C,UAAQ,OAAOC,uBAAsB,EAAE,MAAM,YAAY,CAAC;AAC1D,UAAQ,OAAO,qBAAqB,EAAE,MAAM,IAAI,CAAC;AACnD;AAEA,SAAS,aAAa,MAAe,SAAS,KAAe;AAC3D,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAEO,SAAS,iBAAiB,SAAyB,CAAC,GAAG;AAC5D,QAAM,SAAS,IAAI,eAAe,MAAM;AAExC,iBAAe,YAAY,SAA6C;AACtE,QAAI;AACF,YAAM,OAAO,MAAM,QAAQ,QAAQ,KAAK;AACxC,YAAM,SAAS,MAAM,OAAO,MAAM;AAAA,QAChC,OAAO,KAAK;AAAA,QACZ,UAAU,KAAK;AAAA,MACjB,CAAC;AAED,UAAI,OAAO,cAAc;AACvB,eAAO,aAAa;AAAA,UAClB,cAAc;AAAA,UACd,WAAW,OAAO;AAAA,QACpB,CAAC;AAAA,MACH;AAEA,YAAM,SAAS;AACf,YAAM,YAAY,OAAO;AACzB,YAAM,OAAO,cAAc,MAAM,OAAO,MAAM,OAAO,YAAY,GAAG;AACpE,qBAAe,QAAQ,SAAS,QAAQ,IAAI;AAE5C,aAAO,aAAa,EAAE,KAAK,CAAC;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,aAAO,aAAa,EAAE,OAAO,QAAQ,GAAG,GAAG;AAAA,IAC7C;AAAA,EACF;AAEA,iBAAe,eAAe,SAA6C;AACzE,QAAI;AACF,YAAM,OAAO,MAAM,QAAQ,QAAQ,KAAK;AACxC,YAAM,SAAS,MAAM,OAAO,SAAS;AAAA,QACnC,OAAO,KAAK;AAAA,QACZ,UAAU,KAAK;AAAA,QACf,WAAW,KAAK;AAAA,QAChB,UAAU,KAAK;AAAA,MACjB,CAAC;AAED,UAAI,CAAC,OAAO,cAAc;AACxB,eAAO,aAAa;AAAA,UAClB,0BAA0B;AAAA,UAC1B,MAAM,OAAO;AAAA,QACf,CAAC;AAAA,MACH;AAEA,YAAM,SAAS;AACf,YAAM,YAAY,OAAO;AACzB,YAAM,OAAO,cAAc,MAAM,OAAO,MAAM,OAAO,YAAY,GAAG;AACpE,qBAAe,QAAQ,SAAS,QAAQ,IAAI;AAE5C,aAAO,aAAa,EAAE,KAAK,CAAC;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,aAAO,aAAa,EAAE,OAAO,QAAQ,GAAG,GAAG;AAAA,IAC7C;AAAA,EACF;AAEA,iBAAe,mBAAmB,SAA6C;AAC7E,QAAI;AACF,YAAM,OAAO,MAAM,QAAQ,QAAQ,KAAK;AACxC,YAAM,SAAS,MAAM,OAAO,aAAa;AAAA,QACvC,WAAW,KAAK;AAAA,QAChB,MAAM,KAAK;AAAA,MACb,CAAC;AAED,YAAM,EAAE,MAAM,KAAK,IAAI,MAAM,OAAO,MAAM,OAAO,YAAY;AAC7D,qBAAe,QAAQ,SAAS,QAAQ,IAAI;AAE5C,aAAO,aAAa,EAAE,KAAK,CAAC;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,aAAO,aAAa,EAAE,OAAO,QAAQ,GAAG,GAAG;AAAA,IAC7C;AAAA,EACF;AAEA,iBAAe,cAAc,SAA6C;AACxE,QAAI;AACF,YAAM,eAAe,QAAQ,QAAQ,IAAIA,qBAAoB,GAAG;AAEhE,UAAI,CAAC,cAAc;AACjB,yBAAiB,QAAQ,OAAO;AAChC,eAAO,aAAa,EAAE,OAAO,mBAAmB,GAAG,GAAG;AAAA,MACxD;AAEA,YAAM,SAAS,MAAM,OAAO,QAAQ,YAAY;AAChD,YAAM,EAAE,MAAM,KAAK,IAAI,MAAM,OAAO,MAAM,OAAO,YAAY;AAC7D,qBAAe,QAAQ,SAAS,QAAQ,IAAI;AAE5C,aAAO,aAAa,EAAE,KAAK,CAAC;AAAA,IAC9B,QAAQ;AACN,uBAAiB,QAAQ,OAAO;AAChC,aAAO,aAAa,EAAE,OAAO,iBAAiB,GAAG,GAAG;AAAA,IACtD;AAAA,EACF;AAEA,iBAAe,aAAa,SAA6C;AACvE,QAAI;AACF,YAAM,eAAe,QAAQ,QAAQ,IAAIA,qBAAoB,GAAG;AAChE,UAAI,cAAc;AAChB,cAAM,OAAO,OAAO,YAAY,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAClD;AACA,uBAAiB,QAAQ,OAAO;AAChC,aAAO,aAAa,EAAE,SAAS,KAAK,CAAC;AAAA,IACvC,QAAQ;AACN,uBAAiB,QAAQ,OAAO;AAChC,aAAO,aAAa,EAAE,SAAS,KAAK,CAAC;AAAA,IACvC;AAAA,EACF;AAEA,iBAAe,QAAQ,SAA6C;AAClE,UAAM,OAAO,QAAQ,OAAO,QAAQ;AAEpC,QAAI,QAAQ,QAAQ,WAAW,QAAQ;AACrC,cAAQ,MAAM;AAAA,QACZ,KAAK;AACH,iBAAO,YAAY,OAAO;AAAA,QAC5B,KAAK;AACH,iBAAO,eAAe,OAAO;AAAA,QAC/B,KAAK;AACH,iBAAO,mBAAmB,OAAO;AAAA,QACnC,KAAK;AACH,iBAAO,cAAc,OAAO;AAAA,QAC9B,KAAK;AACH,iBAAO,aAAa,OAAO;AAAA,MAC/B;AAAA,IACF;AAEA,WAAO,aAAa,EAAE,OAAO,YAAY,GAAG,GAAG;AAAA,EACjD;AAEA,SAAO;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,EACP;AACF;;;ADzMO,SAAS,KAAK,SAA0C;AAC7D,QAAM,WAAY,QAAQ,OAAmC;AAC7D,MAAI,SAAU,QAAO;AAErB,QAAM,QAAQ,QAAQ,QAAQ,IAAIC,kBAAiB,GAAG;AACtD,MAAI,CAAC,SAASC,gBAAe,KAAK,EAAG,QAAO;AAE5C,QAAM,SAASC,oBAAmB,KAAK;AACvC,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,QAAQ,OAAO,SAAS,CAAC;AAC/B,QAAM,cAAc,OAAO,eAAe,CAAC;AAE3C,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO,UAAU;AAAA,IACxB,OAAO,OAAO,UAAU;AAAA,IACxB,OAAO,OAAO,UAAU;AAAA,IACxB,SAAS,OAAO,YAAY;AAAA,IAC5B,WAAW;AAAA,IACX,UAAU,YAAY;AAAA,IACtB,KAAK,CAAC,WAAmD;AACvD,UAAI,OAAO,QAAQ,MAAM,SAAS,OAAO,IAAI,EAAG,QAAO;AACvD,UAAI,OAAO,cAAc,YAAY,SAAS,OAAO,UAAU;AAC7D,eAAO;AACT,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,eAAsB,YACpB,SACA,QAC8B;AAC9B,QAAM,QAAQ,QAAQ,QAAQ,IAAIF,kBAAiB,GAAG;AACtD,MAAI,CAAC,SAASC,gBAAe,KAAK,EAAG,QAAO;AAE5C,QAAM,SAAS,IAAIE,gBAAe;AAAA,IAChC,gBAAgB,QAAQ;AAAA,EAC1B,CAAC;AAED,MAAI;AACF,UAAM,EAAE,KAAK,IAAI,MAAM,OAAO,MAAM,KAAK;AACzC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":["InAIAuthClient","COOKIE_AUTH_TOKEN","getClaimsFromToken","isTokenExpired","COOKIE_AUTH_TOKEN","COOKIE_REFRESH_TOKEN","COOKIE_AUTH_TOKEN","isTokenExpired","getClaimsFromToken","InAIAuthClient"]}
@@ -1,6 +1,7 @@
1
1
  // src/middleware.ts
2
2
  import {
3
3
  COOKIE_AUTH_TOKEN,
4
+ COOKIE_REFRESH_TOKEN,
4
5
  getClaimsFromToken,
5
6
  isTokenExpired
6
7
  } from "@inai-dev/shared";
@@ -17,8 +18,30 @@ function inaiAstroMiddleware(config = {}) {
17
18
  if (isPublic) {
18
19
  return next();
19
20
  }
20
- const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;
21
+ let token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;
21
22
  if (!token || isTokenExpired(token)) {
23
+ const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;
24
+ if (refreshToken) {
25
+ try {
26
+ const refreshUrl = new URL("/api/auth/refresh", context.url.origin);
27
+ const refreshRes = await fetch(refreshUrl.toString(), {
28
+ method: "POST",
29
+ headers: {
30
+ "Content-Type": "application/json",
31
+ Cookie: context.request.headers.get("cookie") ?? ""
32
+ }
33
+ });
34
+ if (refreshRes.ok) {
35
+ const setCookies = refreshRes.headers.getSetCookie?.() ?? [];
36
+ const response = await next();
37
+ for (const cookie of setCookies) {
38
+ response.headers.append("Set-Cookie", cookie);
39
+ }
40
+ return response;
41
+ }
42
+ } catch {
43
+ }
44
+ }
22
45
  return context.redirect(
23
46
  `${signInUrl}?returnTo=${encodeURIComponent(pathname)}`
24
47
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"astro\";\nimport type { AuthObject } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\nexport interface InAIAstroMiddlewareConfig {\n publicRoutes?: string[];\n signInUrl?: string;\n}\n\nexport function inaiAstroMiddleware(\n config: InAIAstroMiddlewareConfig = {},\n): MiddlewareHandler {\n const { publicRoutes = [], signInUrl = \"/login\" } = config;\n\n return async (context, next) => {\n const { pathname } = context.url;\n\n const isPublic =\n publicRoutes.some((route) => {\n if (route.endsWith(\"*\")) {\n return pathname.startsWith(route.slice(0, -1));\n }\n return pathname === route;\n }) ||\n pathname === signInUrl ||\n pathname.startsWith(\"/_\") ||\n pathname.startsWith(\"/api/\");\n\n if (isPublic) {\n return next();\n }\n\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n\n if (!token || isTokenExpired(token)) {\n return context.redirect(\n `${signInUrl}?returnTo=${encodeURIComponent(pathname)}`,\n );\n }\n\n const claims = getClaimsFromToken(token);\n if (!claims) {\n return context.redirect(signInUrl);\n }\n\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\n\n const authObject: AuthObject = {\n userId: claims.sub,\n tenantId: claims.tenant_id,\n appId: claims.app_id ?? null,\n envId: claims.env_id ?? null,\n orgId: claims.org_id ?? null,\n orgRole: claims.org_role ?? null,\n sessionId: null,\n getToken: async () => token,\n has: (params: { role?: string; permission?: string }) => {\n if (params.role && roles.includes(params.role)) return true;\n if (params.permission && permissions.includes(params.permission))\n return true;\n return false;\n },\n };\n\n (context.locals as Record<string, unknown>).auth = authObject;\n\n return next();\n };\n}\n"],"mappings":";AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAOA,SAAS,oBACd,SAAoC,CAAC,GAClB;AACnB,QAAM,EAAE,eAAe,CAAC,GAAG,YAAY,SAAS,IAAI;AAEpD,SAAO,OAAO,SAAS,SAAS;AAC9B,UAAM,EAAE,SAAS,IAAI,QAAQ;AAE7B,UAAM,WACJ,aAAa,KAAK,CAAC,UAAU;AAC3B,UAAI,MAAM,SAAS,GAAG,GAAG;AACvB,eAAO,SAAS,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC;AAAA,MAC/C;AACA,aAAO,aAAa;AAAA,IACtB,CAAC,KACD,aAAa,aACb,SAAS,WAAW,IAAI,KACxB,SAAS,WAAW,OAAO;AAE7B,QAAI,UAAU;AACZ,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,QAAQ,QAAQ,QAAQ,IAAI,iBAAiB,GAAG;AAEtD,QAAI,CAAC,SAAS,eAAe,KAAK,GAAG;AACnC,aAAO,QAAQ;AAAA,QACb,GAAG,SAAS,aAAa,mBAAmB,QAAQ,CAAC;AAAA,MACvD;AAAA,IACF;AAEA,UAAM,SAAS,mBAAmB,KAAK;AACvC,QAAI,CAAC,QAAQ;AACX,aAAO,QAAQ,SAAS,SAAS;AAAA,IACnC;AAEA,UAAM,QAAQ,OAAO,SAAS,CAAC;AAC/B,UAAM,cAAc,OAAO,eAAe,CAAC;AAE3C,UAAM,aAAyB;AAAA,MAC7B,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,OAAO,OAAO,UAAU;AAAA,MACxB,OAAO,OAAO,UAAU;AAAA,MACxB,OAAO,OAAO,UAAU;AAAA,MACxB,SAAS,OAAO,YAAY;AAAA,MAC5B,WAAW;AAAA,MACX,UAAU,YAAY;AAAA,MACtB,KAAK,CAAC,WAAmD;AACvD,YAAI,OAAO,QAAQ,MAAM,SAAS,OAAO,IAAI,EAAG,QAAO;AACvD,YAAI,OAAO,cAAc,YAAY,SAAS,OAAO,UAAU;AAC7D,iBAAO;AACT,eAAO;AAAA,MACT;AAAA,IACF;AAEA,IAAC,QAAQ,OAAmC,OAAO;AAEnD,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"astro\";\nimport type { AuthObject } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\nexport interface InAIAstroMiddlewareConfig {\n publicRoutes?: string[];\n signInUrl?: string;\n}\n\nexport function inaiAstroMiddleware(\n config: InAIAstroMiddlewareConfig = {},\n): MiddlewareHandler {\n const { publicRoutes = [], signInUrl = \"/login\" } = config;\n\n return async (context, next) => {\n const { pathname } = context.url;\n\n const isPublic =\n publicRoutes.some((route) => {\n if (route.endsWith(\"*\")) {\n return pathname.startsWith(route.slice(0, -1));\n }\n return pathname === route;\n }) ||\n pathname === signInUrl ||\n pathname.startsWith(\"/_\") ||\n pathname.startsWith(\"/api/\");\n\n if (isPublic) {\n return next();\n }\n\n let token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n if (refreshToken) {\n try {\n const refreshUrl = new URL(\"/api/auth/refresh\", context.url.origin);\n const refreshRes = await fetch(refreshUrl.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Cookie: context.request.headers.get(\"cookie\") ?? \"\",\n },\n });\n if (refreshRes.ok) {\n const setCookies = refreshRes.headers.getSetCookie?.() ?? [];\n const response = await next();\n for (const cookie of setCookies) {\n response.headers.append(\"Set-Cookie\", cookie);\n }\n return response;\n }\n } catch {\n // Refresh failed, redirect to sign-in\n }\n }\n\n return context.redirect(\n `${signInUrl}?returnTo=${encodeURIComponent(pathname)}`,\n );\n }\n\n const claims = getClaimsFromToken(token);\n if (!claims) {\n return context.redirect(signInUrl);\n }\n\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\n\n const authObject: AuthObject = {\n userId: claims.sub,\n tenantId: claims.tenant_id,\n appId: claims.app_id ?? null,\n envId: claims.env_id ?? null,\n orgId: claims.org_id ?? null,\n orgRole: claims.org_role ?? null,\n sessionId: null,\n getToken: async () => token,\n has: (params: { role?: string; permission?: string }) => {\n if (params.role && roles.includes(params.role)) return true;\n if (params.permission && permissions.includes(params.permission))\n return true;\n return false;\n },\n };\n\n (context.locals as Record<string, unknown>).auth = authObject;\n\n return next();\n };\n}\n"],"mappings":";AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAOA,SAAS,oBACd,SAAoC,CAAC,GAClB;AACnB,QAAM,EAAE,eAAe,CAAC,GAAG,YAAY,SAAS,IAAI;AAEpD,SAAO,OAAO,SAAS,SAAS;AAC9B,UAAM,EAAE,SAAS,IAAI,QAAQ;AAE7B,UAAM,WACJ,aAAa,KAAK,CAAC,UAAU;AAC3B,UAAI,MAAM,SAAS,GAAG,GAAG;AACvB,eAAO,SAAS,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC;AAAA,MAC/C;AACA,aAAO,aAAa;AAAA,IACtB,CAAC,KACD,aAAa,aACb,SAAS,WAAW,IAAI,KACxB,SAAS,WAAW,OAAO;AAE7B,QAAI,UAAU;AACZ,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,QAAQ,QAAQ,QAAQ,IAAI,iBAAiB,GAAG;AAEpD,QAAI,CAAC,SAAS,eAAe,KAAK,GAAG;AACnC,YAAM,eAAe,QAAQ,QAAQ,IAAI,oBAAoB,GAAG;AAChE,UAAI,cAAc;AAChB,YAAI;AACF,gBAAM,aAAa,IAAI,IAAI,qBAAqB,QAAQ,IAAI,MAAM;AAClE,gBAAM,aAAa,MAAM,MAAM,WAAW,SAAS,GAAG;AAAA,YACpD,QAAQ;AAAA,YACR,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,QAAQ,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAAA,YACnD;AAAA,UACF,CAAC;AACD,cAAI,WAAW,IAAI;AACjB,kBAAM,aAAa,WAAW,QAAQ,eAAe,KAAK,CAAC;AAC3D,kBAAM,WAAW,MAAM,KAAK;AAC5B,uBAAW,UAAU,YAAY;AAC/B,uBAAS,QAAQ,OAAO,cAAc,MAAM;AAAA,YAC9C;AACA,mBAAO;AAAA,UACT;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,aAAO,QAAQ;AAAA,QACb,GAAG,SAAS,aAAa,mBAAmB,QAAQ,CAAC;AAAA,MACvD;AAAA,IACF;AAEA,UAAM,SAAS,mBAAmB,KAAK;AACvC,QAAI,CAAC,QAAQ;AACX,aAAO,QAAQ,SAAS,SAAS;AAAA,IACnC;AAEA,UAAM,QAAQ,OAAO,SAAS,CAAC;AAC/B,UAAM,cAAc,OAAO,eAAe,CAAC;AAE3C,UAAM,aAAyB;AAAA,MAC7B,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,OAAO,OAAO,UAAU;AAAA,MACxB,OAAO,OAAO,UAAU;AAAA,MACxB,OAAO,OAAO,UAAU;AAAA,MACxB,SAAS,OAAO,YAAY;AAAA,MAC5B,WAAW;AAAA,MACX,UAAU,YAAY;AAAA,MACtB,KAAK,CAAC,WAAmD;AACvD,YAAI,OAAO,QAAQ,MAAM,SAAS,OAAO,IAAI,EAAG,QAAO;AACvD,YAAI,OAAO,cAAc,YAAY,SAAS,OAAO,UAAU;AAC7D,iBAAO;AACT,eAAO;AAAA,MACT;AAAA,IACF;AAEA,IAAC,QAAQ,OAAmC,OAAO;AAEnD,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AuthObject, UserResource } from '@inai-dev/types';
2
+ export { AstroCookies, clearAuthCookies, setAuthCookies } from './api-routes.js';
2
3
 
3
4
  interface AstroContext {
4
5
  cookies: {
@@ -9,8 +10,7 @@ interface AstroContext {
9
10
  locals: Record<string, unknown>;
10
11
  }
11
12
  declare function auth(context: AstroContext): AuthObject | null;
12
- declare function currentUser(context: AstroContext, config: {
13
- apiUrl: string;
13
+ declare function currentUser(context: AstroContext, config?: {
14
14
  publishableKey?: string;
15
15
  }): Promise<UserResource | null>;
16
16
 
package/dist/server.js CHANGED
@@ -1,14 +1,64 @@
1
1
  // src/server.ts
2
- import { InAIAuthClient } from "@inai-dev/backend";
2
+ import { InAIAuthClient as InAIAuthClient2 } from "@inai-dev/backend";
3
3
  import {
4
- COOKIE_AUTH_TOKEN,
4
+ COOKIE_AUTH_TOKEN as COOKIE_AUTH_TOKEN2,
5
5
  getClaimsFromToken,
6
6
  isTokenExpired
7
7
  } from "@inai-dev/shared";
8
+
9
+ // src/api-routes.ts
10
+ import { InAIAuthClient } from "@inai-dev/backend";
11
+ import {
12
+ COOKIE_AUTH_TOKEN,
13
+ COOKIE_REFRESH_TOKEN,
14
+ COOKIE_AUTH_SESSION,
15
+ decodeJWTPayload
16
+ } from "@inai-dev/shared";
17
+ function setAuthCookies(cookies, tokens, user) {
18
+ const isProduction = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
19
+ const claims = decodeJWTPayload(tokens.access_token);
20
+ const expiresAt = claims ? new Date(claims.exp * 1e3).toISOString() : new Date(Date.now() + tokens.expires_in * 1e3).toISOString();
21
+ cookies.set(COOKIE_AUTH_TOKEN, tokens.access_token, {
22
+ httpOnly: true,
23
+ secure: isProduction,
24
+ sameSite: "lax",
25
+ path: "/",
26
+ maxAge: tokens.expires_in
27
+ });
28
+ cookies.set(COOKIE_REFRESH_TOKEN, tokens.refresh_token, {
29
+ httpOnly: true,
30
+ secure: isProduction,
31
+ sameSite: "strict",
32
+ path: "/api/auth",
33
+ maxAge: 7 * 24 * 60 * 60
34
+ });
35
+ cookies.set(COOKIE_AUTH_SESSION, JSON.stringify({
36
+ user,
37
+ expiresAt,
38
+ permissions: claims?.permissions ?? [],
39
+ orgId: claims?.org_id,
40
+ orgRole: claims?.org_role,
41
+ appId: claims?.app_id,
42
+ envId: claims?.env_id
43
+ }), {
44
+ httpOnly: false,
45
+ secure: isProduction,
46
+ sameSite: "lax",
47
+ path: "/",
48
+ maxAge: tokens.expires_in
49
+ });
50
+ }
51
+ function clearAuthCookies(cookies) {
52
+ cookies.delete(COOKIE_AUTH_TOKEN, { path: "/" });
53
+ cookies.delete(COOKIE_REFRESH_TOKEN, { path: "/api/auth" });
54
+ cookies.delete(COOKIE_AUTH_SESSION, { path: "/" });
55
+ }
56
+
57
+ // src/server.ts
8
58
  function auth(context) {
9
59
  const existing = context.locals.auth;
10
60
  if (existing) return existing;
11
- const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;
61
+ const token = context.cookies.get(COOKIE_AUTH_TOKEN2)?.value;
12
62
  if (!token || isTokenExpired(token)) return null;
13
63
  const claims = getClaimsFromToken(token);
14
64
  if (!claims) return null;
@@ -32,11 +82,10 @@ function auth(context) {
32
82
  };
33
83
  }
34
84
  async function currentUser(context, config) {
35
- const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;
85
+ const token = context.cookies.get(COOKIE_AUTH_TOKEN2)?.value;
36
86
  if (!token || isTokenExpired(token)) return null;
37
- const client = new InAIAuthClient({
38
- apiUrl: config.apiUrl,
39
- publishableKey: config.publishableKey
87
+ const client = new InAIAuthClient2({
88
+ publishableKey: config?.publishableKey
40
89
  });
41
90
  try {
42
91
  const { data } = await client.getMe(token);
@@ -47,6 +96,8 @@ async function currentUser(context, config) {
47
96
  }
48
97
  export {
49
98
  auth,
50
- currentUser
99
+ clearAuthCookies,
100
+ currentUser,
101
+ setAuthCookies
51
102
  };
52
103
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import type { AuthObject, UserResource } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n COOKIE_AUTH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\ninterface AstroContext {\n cookies: {\n get(name: string): { value: string } | undefined;\n };\n locals: Record<string, unknown>;\n}\n\nexport function auth(context: AstroContext): AuthObject | null {\n const existing = (context.locals as Record<string, unknown>).auth as AuthObject | undefined;\n if (existing) return existing;\n\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n if (!token || isTokenExpired(token)) return null;\n\n const claims = getClaimsFromToken(token);\n if (!claims) return null;\n\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\n\n return {\n userId: claims.sub,\n tenantId: claims.tenant_id,\n appId: claims.app_id ?? null,\n envId: claims.env_id ?? null,\n orgId: claims.org_id ?? null,\n orgRole: claims.org_role ?? null,\n sessionId: null,\n getToken: async () => token,\n has: (params: { role?: string; permission?: string }) => {\n if (params.role && roles.includes(params.role)) return true;\n if (params.permission && permissions.includes(params.permission))\n return true;\n return false;\n },\n };\n}\n\nexport async function currentUser(\n context: AstroContext,\n config: { apiUrl: string; publishableKey?: string },\n): Promise<UserResource | null> {\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n if (!token || isTokenExpired(token)) return null;\n\n const client = new InAIAuthClient({\n apiUrl: config.apiUrl,\n publishableKey: config.publishableKey,\n });\n\n try {\n const { data } = await client.getMe(token);\n return data;\n } catch {\n return null;\n }\n}\n"],"mappings":";AACA,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASA,SAAS,KAAK,SAA0C;AAC7D,QAAM,WAAY,QAAQ,OAAmC;AAC7D,MAAI,SAAU,QAAO;AAErB,QAAM,QAAQ,QAAQ,QAAQ,IAAI,iBAAiB,GAAG;AACtD,MAAI,CAAC,SAAS,eAAe,KAAK,EAAG,QAAO;AAE5C,QAAM,SAAS,mBAAmB,KAAK;AACvC,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,QAAQ,OAAO,SAAS,CAAC;AAC/B,QAAM,cAAc,OAAO,eAAe,CAAC;AAE3C,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO,UAAU;AAAA,IACxB,OAAO,OAAO,UAAU;AAAA,IACxB,OAAO,OAAO,UAAU;AAAA,IACxB,SAAS,OAAO,YAAY;AAAA,IAC5B,WAAW;AAAA,IACX,UAAU,YAAY;AAAA,IACtB,KAAK,CAAC,WAAmD;AACvD,UAAI,OAAO,QAAQ,MAAM,SAAS,OAAO,IAAI,EAAG,QAAO;AACvD,UAAI,OAAO,cAAc,YAAY,SAAS,OAAO,UAAU;AAC7D,eAAO;AACT,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,eAAsB,YACpB,SACA,QAC8B;AAC9B,QAAM,QAAQ,QAAQ,QAAQ,IAAI,iBAAiB,GAAG;AACtD,MAAI,CAAC,SAAS,eAAe,KAAK,EAAG,QAAO;AAE5C,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,QAAQ,OAAO;AAAA,IACf,gBAAgB,OAAO;AAAA,EACzB,CAAC;AAED,MAAI;AACF,UAAM,EAAE,KAAK,IAAI,MAAM,OAAO,MAAM,KAAK;AACzC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts","../src/api-routes.ts"],"sourcesContent":["import type { AuthObject, UserResource } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n COOKIE_AUTH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\ninterface AstroContext {\n cookies: {\n get(name: string): { value: string } | undefined;\n };\n locals: Record<string, unknown>;\n}\n\nexport function auth(context: AstroContext): AuthObject | null {\n const existing = (context.locals as Record<string, unknown>).auth as AuthObject | undefined;\n if (existing) return existing;\n\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n if (!token || isTokenExpired(token)) return null;\n\n const claims = getClaimsFromToken(token);\n if (!claims) return null;\n\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\n\n return {\n userId: claims.sub,\n tenantId: claims.tenant_id,\n appId: claims.app_id ?? null,\n envId: claims.env_id ?? null,\n orgId: claims.org_id ?? null,\n orgRole: claims.org_role ?? null,\n sessionId: null,\n getToken: async () => token,\n has: (params: { role?: string; permission?: string }) => {\n if (params.role && roles.includes(params.role)) return true;\n if (params.permission && permissions.includes(params.permission))\n return true;\n return false;\n },\n };\n}\n\nexport async function currentUser(\n context: AstroContext,\n config?: { publishableKey?: string },\n): Promise<UserResource | null> {\n const token = context.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n if (!token || isTokenExpired(token)) return null;\n\n const client = new InAIAuthClient({\n publishableKey: config?.publishableKey,\n });\n\n try {\n const { data } = await client.getMe(token);\n return data;\n } catch {\n return null;\n }\n}\n\nexport { setAuthCookies, clearAuthCookies } from \"./api-routes\";\nexport type { AstroCookies } from \"./api-routes\";\n","import type { InAIAuthConfig, TokenPair, UserResource, LoginResult } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\ninterface AstroCookies {\n get(name: string): { value: string } | undefined;\n set(name: string, value: string, options?: Record<string, unknown>): void;\n delete(name: string, options?: Record<string, unknown>): void;\n}\n\ninterface AstroAPIContext {\n request: Request;\n cookies: AstroCookies;\n params: Record<string, string | undefined>;\n url: URL;\n}\n\nfunction setAuthCookies(\n cookies: AstroCookies,\n tokens: TokenPair,\n user: UserResource,\n): void {\n const isProduction = typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\";\n const claims = decodeJWTPayload(tokens.access_token);\n const expiresAt = claims\n ? new Date(claims.exp * 1000).toISOString()\n : new Date(Date.now() + tokens.expires_in * 1000).toISOString();\n\n cookies.set(COOKIE_AUTH_TOKEN, tokens.access_token, {\n httpOnly: true,\n secure: isProduction,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n });\n\n cookies.set(COOKIE_REFRESH_TOKEN, tokens.refresh_token, {\n httpOnly: true,\n secure: isProduction,\n sameSite: \"strict\",\n path: \"/api/auth\",\n maxAge: 7 * 24 * 60 * 60,\n });\n\n cookies.set(COOKIE_AUTH_SESSION, JSON.stringify({\n user,\n expiresAt,\n permissions: claims?.permissions ?? [],\n orgId: claims?.org_id,\n orgRole: claims?.org_role,\n appId: claims?.app_id,\n envId: claims?.env_id,\n }), {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n });\n}\n\nfunction clearAuthCookies(cookies: AstroCookies): void {\n cookies.delete(COOKIE_AUTH_TOKEN, { path: \"/\" });\n cookies.delete(COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n cookies.delete(COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n\nfunction jsonResponse(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nexport function createAuthRoutes(config: InAIAuthConfig = {}) {\n const client = new InAIAuthClient(config);\n\n async function handleLogin(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const result = await client.login({\n email: body.email,\n password: body.password,\n }) as LoginResult & { user?: UserResource };\n\n if (result.mfa_required) {\n return jsonResponse({\n mfa_required: true,\n mfa_token: result.mfa_token,\n });\n }\n\n const tokens = result as unknown as TokenPair;\n const loginUser = result.user;\n const user = loginUser ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Login failed\";\n return jsonResponse({ error: message }, 401);\n }\n }\n\n async function handleRegister(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const result = await client.register({\n email: body.email,\n password: body.password,\n firstName: body.firstName,\n lastName: body.lastName,\n });\n\n if (!result.access_token) {\n return jsonResponse({\n needs_email_verification: true,\n user: result.user,\n });\n }\n\n const tokens = result as unknown as TokenPair;\n const loginUser = result.user;\n const user = loginUser ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Registration failed\";\n return jsonResponse({ error: message }, 400);\n }\n }\n\n async function handleMFAChallenge(context: AstroAPIContext): Promise<Response> {\n try {\n const body = await context.request.json() as Record<string, string>;\n const tokens = await client.mfaChallenge({\n mfa_token: body.mfa_token,\n code: body.code,\n });\n\n const { data: user } = await client.getMe(tokens.access_token);\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"MFA verification failed\";\n return jsonResponse({ error: message }, 401);\n }\n }\n\n async function handleRefresh(context: AstroAPIContext): Promise<Response> {\n try {\n const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n\n if (!refreshToken) {\n clearAuthCookies(context.cookies);\n return jsonResponse({ error: \"No refresh token\" }, 401);\n }\n\n const tokens = await client.refresh(refreshToken);\n const { data: user } = await client.getMe(tokens.access_token);\n setAuthCookies(context.cookies, tokens, user);\n\n return jsonResponse({ user });\n } catch {\n clearAuthCookies(context.cookies);\n return jsonResponse({ error: \"Refresh failed\" }, 401);\n }\n }\n\n async function handleLogout(context: AstroAPIContext): Promise<Response> {\n try {\n const refreshToken = context.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n if (refreshToken) {\n await client.logout(refreshToken).catch(() => {});\n }\n clearAuthCookies(context.cookies);\n return jsonResponse({ success: true });\n } catch {\n clearAuthCookies(context.cookies);\n return jsonResponse({ success: true });\n }\n }\n\n async function handler(context: AstroAPIContext): Promise<Response> {\n const path = context.params.path ?? \"\";\n\n if (context.request.method === \"POST\") {\n switch (path) {\n case \"login\":\n return handleLogin(context);\n case \"register\":\n return handleRegister(context);\n case \"mfa-challenge\":\n return handleMFAChallenge(context);\n case \"refresh\":\n return handleRefresh(context);\n case \"logout\":\n return handleLogout(context);\n }\n }\n\n return jsonResponse({ error: \"Not found\" }, 404);\n }\n\n return {\n ALL: handler,\n POST: handler,\n GET: handler,\n };\n}\n\nexport { setAuthCookies, clearAuthCookies };\nexport type { AstroCookies, AstroAPIContext };\n"],"mappings":"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","names":["InAIAuthClient","COOKIE_AUTH_TOKEN","COOKIE_AUTH_TOKEN","InAIAuthClient"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inai-dev/astro",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Astro integration for InAI Auth SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,11 +17,15 @@
17
17
  "./server": {
18
18
  "types": "./dist/server.d.ts",
19
19
  "import": "./dist/server.js"
20
+ },
21
+ "./api-routes": {
22
+ "types": "./dist/api-routes.d.ts",
23
+ "import": "./dist/api-routes.js"
20
24
  }
21
25
  },
26
+ "sideEffects": false,
22
27
  "files": [
23
- "dist",
24
- "src/components"
28
+ "dist"
25
29
  ],
26
30
  "scripts": {
27
31
  "build": "tsup",
@@ -31,9 +35,9 @@
31
35
  "prepublishOnly": "npm run build"
32
36
  },
33
37
  "dependencies": {
34
- "@inai-dev/types": "^1.0.0",
35
- "@inai-dev/shared": "^1.0.0",
36
- "@inai-dev/backend": "^1.0.0"
38
+ "@inai-dev/types": "^1.2.0",
39
+ "@inai-dev/shared": "^1.2.0",
40
+ "@inai-dev/backend": "^1.3.0"
37
41
  },
38
42
  "peerDependencies": {
39
43
  "astro": ">=4.0.0"
@@ -1,6 +0,0 @@
1
- ---
2
- const auth = Astro.locals.auth;
3
- const isSignedIn = !!auth?.userId;
4
- ---
5
-
6
- {isSignedIn && <slot />}
@@ -1,6 +0,0 @@
1
- ---
2
- const auth = Astro.locals.auth;
3
- const isSignedIn = !!auth?.userId;
4
- ---
5
-
6
- {!isSignedIn && <slot />}