@inai-dev/hono 1.0.0 → 1.2.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 +318 -0
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +4 -2
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.js +5 -3
- package/dist/middleware.js.map +1 -1
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# @inai-dev/hono
|
|
2
|
+
|
|
3
|
+
Full Hono integration for InAI Auth. Includes middleware with automatic token refresh, route protection with RBAC, and API route handlers. Works on Cloudflare Workers, Node.js, Deno, and Bun.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @inai-dev/hono
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Environment Variables
|
|
12
|
+
|
|
13
|
+
```env
|
|
14
|
+
# Required — your publishable key
|
|
15
|
+
INAI_PUBLISHABLE_KEY=pk_live_...
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> On Cloudflare Workers, set this as a secret: `wrangler secret put INAI_PUBLISHABLE_KEY`
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
### 1. Middleware
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Hono } from "hono";
|
|
26
|
+
import { inaiAuthMiddleware } from "@inai-dev/hono/middleware";
|
|
27
|
+
|
|
28
|
+
const app = new Hono();
|
|
29
|
+
|
|
30
|
+
app.use(
|
|
31
|
+
"*",
|
|
32
|
+
inaiAuthMiddleware({
|
|
33
|
+
publicRoutes: ["/", "/health", "/api/public/*"],
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The middleware automatically:
|
|
39
|
+
- Skips public routes (supports glob patterns with `*`)
|
|
40
|
+
- Validates the auth token from `Authorization: Bearer <token>` header or cookies
|
|
41
|
+
- Refreshes expired tokens when a refresh token exists in cookies
|
|
42
|
+
- Sets `c.get("inaiAuth")` with the `AuthObject` for authenticated requests
|
|
43
|
+
- Returns `401 Unauthorized` for unauthenticated requests on protected routes
|
|
44
|
+
- Uses `hono/cookie` for cookie parsing (works across all runtimes)
|
|
45
|
+
|
|
46
|
+
#### Configuration
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
inaiAuthMiddleware({
|
|
50
|
+
// Auth mode: "app" (default) or "platform" (admin panel auth)
|
|
51
|
+
authMode: "app",
|
|
52
|
+
|
|
53
|
+
// Routes that don't require authentication
|
|
54
|
+
publicRoutes: ["/", "/health", "/api/public/*"],
|
|
55
|
+
// Or use a function for dynamic matching
|
|
56
|
+
publicRoutes: (path) => path.startsWith("/public"),
|
|
57
|
+
|
|
58
|
+
// Custom unauthorized handler (default: 401 JSON response)
|
|
59
|
+
onUnauthorized: (c) => c.json({ error: "Please sign in" }, 401),
|
|
60
|
+
|
|
61
|
+
// JWKS endpoint for ES256 token verification (optional)
|
|
62
|
+
jwksUrl: "https://apiauth.inai.dev/.well-known/jwks.json",
|
|
63
|
+
|
|
64
|
+
// InAIAuthClient config (optional if using env vars)
|
|
65
|
+
publishableKey: "pk_live_...",
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> All tokens are cryptographically verified using ES256 (ECDSA P-256). Public keys are fetched from the JWKS endpoint and cached for 5 minutes.
|
|
70
|
+
|
|
71
|
+
### 2. Route Protection (RBAC)
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { requireAuth } from "@inai-dev/hono/middleware";
|
|
75
|
+
|
|
76
|
+
// Require any authenticated user
|
|
77
|
+
app.get("/api/profile", requireAuth(), (c) => {
|
|
78
|
+
const auth = c.get("inaiAuth");
|
|
79
|
+
return c.json({ userId: auth!.userId });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Require a specific role
|
|
83
|
+
app.get("/api/admin", requireAuth({ role: "admin" }), (c) => {
|
|
84
|
+
return c.json({ message: "Admin area" });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Require a specific permission
|
|
88
|
+
app.post("/api/posts", requireAuth({ permission: "posts:write" }), (c) => {
|
|
89
|
+
return c.json({ message: "Post created" });
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`requireAuth()` returns:
|
|
94
|
+
- `401` if no auth or no `userId`
|
|
95
|
+
- `403` if the user lacks the required `role` or `permission`
|
|
96
|
+
|
|
97
|
+
### 3. API Routes
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { createAuthRoutes } from "@inai-dev/hono/api-routes";
|
|
101
|
+
|
|
102
|
+
const authApp = createAuthRoutes({
|
|
103
|
+
publishableKey: "pk_live_...",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
app.route("/api/auth", authApp);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Handles the following endpoints automatically:
|
|
110
|
+
- `POST /api/auth/login` — User login (returns `{ user }` or `{ mfa_required, mfa_token }`)
|
|
111
|
+
- `POST /api/auth/register` — User registration
|
|
112
|
+
- `POST /api/auth/mfa-challenge` — MFA verification
|
|
113
|
+
- `POST /api/auth/refresh` — Token refresh
|
|
114
|
+
- `POST /api/auth/logout` — User logout
|
|
115
|
+
|
|
116
|
+
### 4. Auth Helpers
|
|
117
|
+
|
|
118
|
+
#### `getAuth(c)`
|
|
119
|
+
|
|
120
|
+
Returns the `AuthObject` from the context (populated by middleware).
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { getAuth } from "@inai-dev/hono";
|
|
124
|
+
|
|
125
|
+
app.get("/api/me", (c) => {
|
|
126
|
+
const auth = getAuth(c);
|
|
127
|
+
if (!auth?.userId) {
|
|
128
|
+
return c.json({ error: "Not signed in" }, 401);
|
|
129
|
+
}
|
|
130
|
+
return c.json({ userId: auth.userId, orgId: auth.orgId });
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**`AuthObject`:**
|
|
135
|
+
|
|
136
|
+
| Property | Type | Description |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `userId` | `string \| null` | Current user ID |
|
|
139
|
+
| `tenantId` | `string \| null` | Tenant ID |
|
|
140
|
+
| `appId` | `string \| null` | Application ID |
|
|
141
|
+
| `envId` | `string \| null` | Environment ID |
|
|
142
|
+
| `orgId` | `string \| null` | Active organization ID |
|
|
143
|
+
| `orgRole` | `string \| null` | Role in active organization |
|
|
144
|
+
| `sessionId` | `string \| null` | Session ID |
|
|
145
|
+
| `getToken()` | `() => Promise<string \| null>` | Get the access token |
|
|
146
|
+
| `has(params)` | `({ role?, permission? }) => boolean` | Check role or permission |
|
|
147
|
+
|
|
148
|
+
#### Cookie Helpers
|
|
149
|
+
|
|
150
|
+
For advanced use cases (custom auth flows, manual token management):
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { setAuthCookies, clearAuthCookies } from "@inai-dev/hono";
|
|
154
|
+
|
|
155
|
+
// Set auth cookies after manual authentication
|
|
156
|
+
setAuthCookies(c, tokens, user);
|
|
157
|
+
|
|
158
|
+
// Clear all auth cookies (manual logout)
|
|
159
|
+
clearAuthCookies(c);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Token Extraction
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { getTokenFromContext, getRefreshTokenFromContext } from "@inai-dev/hono";
|
|
166
|
+
|
|
167
|
+
// Get access token from Authorization header or cookies
|
|
168
|
+
const token = getTokenFromContext(c);
|
|
169
|
+
|
|
170
|
+
// Get refresh token from cookies
|
|
171
|
+
const refreshToken = getRefreshTokenFromContext(c);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Full Example
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { Hono } from "hono";
|
|
178
|
+
import {
|
|
179
|
+
inaiAuthMiddleware,
|
|
180
|
+
requireAuth,
|
|
181
|
+
createAuthRoutes,
|
|
182
|
+
getAuth,
|
|
183
|
+
} from "@inai-dev/hono";
|
|
184
|
+
|
|
185
|
+
const app = new Hono();
|
|
186
|
+
|
|
187
|
+
// Auth middleware — protects all routes except public ones
|
|
188
|
+
app.use(
|
|
189
|
+
"*",
|
|
190
|
+
inaiAuthMiddleware({
|
|
191
|
+
publicRoutes: ["/", "/health", "/api/auth/*"],
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Auth API routes
|
|
196
|
+
app.route("/api/auth", createAuthRoutes());
|
|
197
|
+
|
|
198
|
+
// Public route
|
|
199
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
200
|
+
|
|
201
|
+
// Protected route — any authenticated user
|
|
202
|
+
app.get("/api/profile", (c) => {
|
|
203
|
+
const auth = getAuth(c);
|
|
204
|
+
return c.json({ userId: auth?.userId });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Protected route — admin only
|
|
208
|
+
app.get("/api/admin", requireAuth({ role: "admin" }), (c) => {
|
|
209
|
+
return c.json({ message: "Welcome, admin" });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
export default app;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Cloudflare Workers
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// src/index.ts
|
|
219
|
+
import { Hono } from "hono";
|
|
220
|
+
import { inaiAuthMiddleware, createAuthRoutes } from "@inai-dev/hono";
|
|
221
|
+
|
|
222
|
+
const app = new Hono();
|
|
223
|
+
|
|
224
|
+
app.use(
|
|
225
|
+
"*",
|
|
226
|
+
inaiAuthMiddleware({
|
|
227
|
+
publicRoutes: ["/", "/api/auth/*"],
|
|
228
|
+
publishableKey: "pk_live_...",
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
app.route("/api/auth", createAuthRoutes());
|
|
233
|
+
|
|
234
|
+
app.get("/api/hello", (c) => {
|
|
235
|
+
const auth = c.get("inaiAuth");
|
|
236
|
+
return c.json({ userId: auth?.userId });
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export default app;
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Platform Mode (Admin Panel)
|
|
243
|
+
|
|
244
|
+
For admin panels that authenticate against the InAI platform API:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
app.use(
|
|
248
|
+
"*",
|
|
249
|
+
inaiAuthMiddleware({
|
|
250
|
+
authMode: "platform",
|
|
251
|
+
publicRoutes: ["/login"],
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
In platform mode, the middleware uses `platformRefresh()` and `platformGetMe()` for token refresh, targeting the platform auth endpoints instead of app user endpoints.
|
|
257
|
+
|
|
258
|
+
## Type Augmentation
|
|
259
|
+
|
|
260
|
+
The package augments Hono's `ContextVariableMap` so `c.get("inaiAuth")` is fully typed:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// Automatic — just import the package
|
|
264
|
+
import "@inai-dev/hono";
|
|
265
|
+
|
|
266
|
+
// c.get("inaiAuth") is typed as AuthObject | null
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Exports Reference
|
|
270
|
+
|
|
271
|
+
### `@inai-dev/hono`
|
|
272
|
+
|
|
273
|
+
| Export | Kind | Description |
|
|
274
|
+
|---|---|---|
|
|
275
|
+
| `inaiAuthMiddleware` | Function | Auth middleware |
|
|
276
|
+
| `requireAuth` | Function | RBAC route guard |
|
|
277
|
+
| `createAuthRoutes` | Function | Auth API route handlers |
|
|
278
|
+
| `getAuth` | Function | Get `AuthObject` from context |
|
|
279
|
+
| `setAuthCookies` | Function | Set auth cookies |
|
|
280
|
+
| `clearAuthCookies` | Function | Clear auth cookies |
|
|
281
|
+
| `getTokenFromContext` | Function | Extract access token |
|
|
282
|
+
| `getRefreshTokenFromContext` | Function | Extract refresh token |
|
|
283
|
+
|
|
284
|
+
### `@inai-dev/hono/middleware`
|
|
285
|
+
|
|
286
|
+
| Export | Kind | Description |
|
|
287
|
+
|---|---|---|
|
|
288
|
+
| `inaiAuthMiddleware` | Function | Auth middleware with token refresh |
|
|
289
|
+
| `requireAuth` | Function | RBAC route guard |
|
|
290
|
+
|
|
291
|
+
### `@inai-dev/hono/api-routes`
|
|
292
|
+
|
|
293
|
+
| Export | Kind | Description |
|
|
294
|
+
|---|---|---|
|
|
295
|
+
| `createAuthRoutes` | Function | Create Hono sub-app with auth endpoints |
|
|
296
|
+
|
|
297
|
+
## Exported Types
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import type {
|
|
301
|
+
InAIHonoMiddlewareConfig,
|
|
302
|
+
RequireAuthConfig,
|
|
303
|
+
} from "@inai-dev/hono";
|
|
304
|
+
|
|
305
|
+
import type {
|
|
306
|
+
AuthObject,
|
|
307
|
+
UserResource,
|
|
308
|
+
OrganizationResource,
|
|
309
|
+
} from "@inai-dev/hono";
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Questions & Support
|
|
313
|
+
|
|
314
|
+
Visit [https://inai.dev](https://inai.dev) for documentation, guides, and support.
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
[MIT](../../LICENSE)
|
package/dist/index.cjs
CHANGED
|
@@ -118,6 +118,8 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
118
118
|
} = config;
|
|
119
119
|
const client = new import_backend.InAIAuthClient(authClientConfig);
|
|
120
120
|
const isPlatform = authMode === "platform";
|
|
121
|
+
const jwksUrl = authClientConfig.jwksUrl ?? `${authClientConfig.apiUrl ?? import_shared2.DEFAULT_API_URL}/.well-known/jwks.json`;
|
|
122
|
+
const jwksClient = new import_shared2.JWKSClient(jwksUrl);
|
|
121
123
|
const defaultUnauthorized = (c) => c.json({ error: "Unauthorized" }, 401);
|
|
122
124
|
const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;
|
|
123
125
|
return async function middleware(c, next) {
|
|
@@ -135,7 +137,7 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
135
137
|
const tokens = isPlatform ? await client.platformRefresh(refreshToken) : await client.refresh(refreshToken);
|
|
136
138
|
const { data: user } = isPlatform ? await client.platformGetMe(tokens.access_token) : await client.getMe(tokens.access_token);
|
|
137
139
|
setAuthCookies(c, tokens, user);
|
|
138
|
-
const authObj2 = (0, import_backend.buildAuthObjectFromToken)(tokens.access_token);
|
|
140
|
+
const authObj2 = await (0, import_backend.buildAuthObjectFromToken)(tokens.access_token, jwksClient);
|
|
139
141
|
c.set("inaiAuth", authObj2);
|
|
140
142
|
await next();
|
|
141
143
|
return;
|
|
@@ -146,7 +148,7 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
146
148
|
}
|
|
147
149
|
return handleUnauthorized(c);
|
|
148
150
|
}
|
|
149
|
-
const authObj = (0, import_backend.buildAuthObjectFromToken)(token);
|
|
151
|
+
const authObj = await (0, import_backend.buildAuthObjectFromToken)(token, jwksClient);
|
|
150
152
|
if (!authObj) {
|
|
151
153
|
return handleUnauthorized(c);
|
|
152
154
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/helpers.ts","../src/api-routes.ts"],"sourcesContent":["import \"./types\";\n\nexport { inaiAuthMiddleware, requireAuth } from \"./middleware\";\nexport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\n\nexport {\n getAuth,\n setAuthCookies,\n clearAuthCookies,\n getTokenFromContext,\n getRefreshTokenFromContext,\n} from \"./helpers\";\n\nexport { createAuthRoutes } from \"./api-routes\";\n\nexport type {\n AuthObject,\n UserResource,\n OrganizationResource,\n} from \"@inai-dev/types\";\n","import type { MiddlewareHandler } from \"hono\";\nimport type { InAIAuthConfig } from \"@inai-dev/types\";\nimport { InAIAuthClient, buildAuthObjectFromToken } from \"@inai-dev/backend\";\nimport { isTokenExpired } from \"@inai-dev/shared\";\nimport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\nimport {\n getTokenFromContext,\n getRefreshTokenFromContext,\n setAuthCookies,\n clearAuthCookies,\n getAuth,\n} from \"./helpers\";\n\nfunction matchesRoute(pathname: string, patterns: string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n return pathname === pattern;\n });\n}\n\nfunction isPublicRoute(\n path: string,\n publicRoutes: string[] | ((path: string) => boolean),\n): boolean {\n if (typeof publicRoutes === \"function\") return publicRoutes(path);\n return matchesRoute(path, publicRoutes);\n}\n\nexport function inaiAuthMiddleware(\n config: InAIHonoMiddlewareConfig & InAIAuthConfig = {},\n): MiddlewareHandler {\n const {\n authMode = \"app\",\n publicRoutes = [],\n onUnauthorized,\n ...authClientConfig\n } = config;\n\n const client = new InAIAuthClient(authClientConfig);\n const isPlatform = authMode === \"platform\";\n\n const defaultUnauthorized = (c: Parameters<MiddlewareHandler>[0]) =>\n c.json({ error: \"Unauthorized\" }, 401);\n\n const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;\n\n return async function middleware(c, next) {\n const path = new URL(c.req.url).pathname;\n\n if (isPublicRoute(path, publicRoutes)) {\n c.set(\"inaiAuth\", null);\n await next();\n return;\n }\n\n const token = getTokenFromContext(c);\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (refreshToken) {\n try {\n const tokens = isPlatform\n ? await client.platformRefresh(refreshToken)\n : await client.refresh(refreshToken);\n const { data: user } = isPlatform\n ? await client.platformGetMe(tokens.access_token)\n : await client.getMe(tokens.access_token);\n setAuthCookies(c, tokens, user);\n\n const authObj = buildAuthObjectFromToken(tokens.access_token);\n c.set(\"inaiAuth\", authObj);\n\n await next();\n return;\n } catch {\n clearAuthCookies(c);\n return handleUnauthorized(c);\n }\n }\n\n return handleUnauthorized(c);\n }\n\n const authObj = buildAuthObjectFromToken(token);\n if (!authObj) {\n return handleUnauthorized(c);\n }\n\n c.set(\"inaiAuth\", authObj);\n await next();\n };\n}\n\nexport function requireAuth(config: RequireAuthConfig = {}): MiddlewareHandler {\n return async function middleware(c, next) {\n const auth = getAuth(c);\n\n if (!auth?.userId) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n if (config.role || config.permission) {\n const hasAccess = auth.has({\n role: config.role,\n permission: config.permission,\n });\n\n if (!hasAccess) {\n return c.json({ error: \"Forbidden\" }, 403);\n }\n }\n\n await next();\n };\n}\n","import type { Context } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { AuthObject, TokenPair, UserResource, PlatformUserResource } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\nexport function getAuth(c: Context): AuthObject | null {\n return c.get(\"inaiAuth\") ?? null;\n}\n\nexport function getTokenFromContext(c: Context): string | null {\n const authHeader = c.req.header(\"Authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n return authHeader.slice(7);\n }\n\n return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;\n}\n\nexport function getRefreshTokenFromContext(c: Context): string | null {\n return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;\n}\n\nexport function setAuthCookies(\n c: Context,\n tokens: TokenPair,\n user: UserResource | PlatformUserResource,\n): void {\n const isProduction =\n 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 setCookie(c, 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 setCookie(c, 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 setCookie(\n c,\n COOKIE_AUTH_SESSION,\n 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 {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n },\n );\n}\n\nexport function clearAuthCookies(c: Context): void {\n deleteCookie(c, COOKIE_AUTH_TOKEN, { path: \"/\" });\n deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n deleteCookie(c, COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n","import { Hono } from \"hono\";\nimport type { InAIAuthConfig, TokenPair, UserResource, LoginResult } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n setAuthCookies,\n clearAuthCookies,\n getRefreshTokenFromContext,\n} from \"./helpers\";\n\nexport function createAuthRoutes(config: InAIAuthConfig = {}) {\n const app = new Hono();\n const client = new InAIAuthClient(config);\n\n app.post(\"/login\", async (c) => {\n try {\n const body = await c.req.json<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 c.json({ mfa_required: true, mfa_token: result.mfa_token });\n }\n\n const tokens = result as unknown as TokenPair;\n const user =\n result.user ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Login failed\";\n return c.json({ error: message }, 401);\n }\n });\n\n app.post(\"/register\", async (c) => {\n try {\n const body = await c.req.json<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 c.json({ needs_email_verification: true, user: result.user });\n }\n\n const tokens = result as unknown as TokenPair;\n const user =\n result.user ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Registration failed\";\n return c.json({ error: message }, 400);\n }\n });\n\n app.post(\"/mfa-challenge\", async (c) => {\n try {\n const body = await c.req.json<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(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"MFA verification failed\";\n return c.json({ error: message }, 401);\n }\n });\n\n app.post(\"/refresh\", async (c) => {\n try {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (!refreshToken) {\n clearAuthCookies(c);\n return c.json({ 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(c, tokens, user);\n\n return c.json({ user });\n } catch {\n clearAuthCookies(c);\n return c.json({ error: \"Refresh failed\" }, 401);\n }\n });\n\n app.post(\"/logout\", async (c) => {\n try {\n const refreshToken = getRefreshTokenFromContext(c);\n if (refreshToken) {\n await client.logout(refreshToken).catch(() => {});\n }\n clearAuthCookies(c);\n return c.json({ success: true });\n } catch {\n clearAuthCookies(c);\n return c.json({ success: true });\n }\n });\n\n return app;\n}\n"],"mappings":"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","names":["import_shared","authObj","import_backend"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/helpers.ts","../src/api-routes.ts"],"sourcesContent":["import \"./types\";\n\nexport { inaiAuthMiddleware, requireAuth } from \"./middleware\";\nexport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\n\nexport {\n getAuth,\n setAuthCookies,\n clearAuthCookies,\n getTokenFromContext,\n getRefreshTokenFromContext,\n} from \"./helpers\";\n\nexport { createAuthRoutes } from \"./api-routes\";\n\nexport type {\n AuthObject,\n UserResource,\n OrganizationResource,\n} from \"@inai-dev/types\";\n","import type { MiddlewareHandler } from \"hono\";\nimport type { InAIAuthConfig } from \"@inai-dev/types\";\nimport { InAIAuthClient, buildAuthObjectFromToken } from \"@inai-dev/backend\";\nimport { isTokenExpired, JWKSClient, DEFAULT_API_URL } from \"@inai-dev/shared\";\nimport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\nimport {\n getTokenFromContext,\n getRefreshTokenFromContext,\n setAuthCookies,\n clearAuthCookies,\n getAuth,\n} from \"./helpers\";\n\nfunction matchesRoute(pathname: string, patterns: string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n return pathname === pattern;\n });\n}\n\nfunction isPublicRoute(\n path: string,\n publicRoutes: string[] | ((path: string) => boolean),\n): boolean {\n if (typeof publicRoutes === \"function\") return publicRoutes(path);\n return matchesRoute(path, publicRoutes);\n}\n\nexport function inaiAuthMiddleware(\n config: InAIHonoMiddlewareConfig & InAIAuthConfig = {},\n): MiddlewareHandler {\n const {\n authMode = \"app\",\n publicRoutes = [],\n onUnauthorized,\n ...authClientConfig\n } = config;\n\n const client = new InAIAuthClient(authClientConfig);\n const isPlatform = authMode === \"platform\";\n\n const jwksUrl = authClientConfig.jwksUrl\n ?? `${authClientConfig.apiUrl ?? DEFAULT_API_URL}/.well-known/jwks.json`;\n const jwksClient = new JWKSClient(jwksUrl);\n\n const defaultUnauthorized = (c: Parameters<MiddlewareHandler>[0]) =>\n c.json({ error: \"Unauthorized\" }, 401);\n\n const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;\n\n return async function middleware(c, next) {\n const path = new URL(c.req.url).pathname;\n\n if (isPublicRoute(path, publicRoutes)) {\n c.set(\"inaiAuth\", null);\n await next();\n return;\n }\n\n const token = getTokenFromContext(c);\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (refreshToken) {\n try {\n const tokens = isPlatform\n ? await client.platformRefresh(refreshToken)\n : await client.refresh(refreshToken);\n const { data: user } = isPlatform\n ? await client.platformGetMe(tokens.access_token)\n : await client.getMe(tokens.access_token);\n setAuthCookies(c, tokens, user);\n\n const authObj = await buildAuthObjectFromToken(tokens.access_token, jwksClient);\n c.set(\"inaiAuth\", authObj);\n\n await next();\n return;\n } catch {\n clearAuthCookies(c);\n return handleUnauthorized(c);\n }\n }\n\n return handleUnauthorized(c);\n }\n\n const authObj = await buildAuthObjectFromToken(token, jwksClient);\n if (!authObj) {\n return handleUnauthorized(c);\n }\n\n c.set(\"inaiAuth\", authObj);\n await next();\n };\n}\n\nexport function requireAuth(config: RequireAuthConfig = {}): MiddlewareHandler {\n return async function middleware(c, next) {\n const auth = getAuth(c);\n\n if (!auth?.userId) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n if (config.role || config.permission) {\n const hasAccess = auth.has({\n role: config.role,\n permission: config.permission,\n });\n\n if (!hasAccess) {\n return c.json({ error: \"Forbidden\" }, 403);\n }\n }\n\n await next();\n };\n}\n","import type { Context } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { AuthObject, TokenPair, UserResource, PlatformUserResource } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\nexport function getAuth(c: Context): AuthObject | null {\n return c.get(\"inaiAuth\") ?? null;\n}\n\nexport function getTokenFromContext(c: Context): string | null {\n const authHeader = c.req.header(\"Authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n return authHeader.slice(7);\n }\n\n return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;\n}\n\nexport function getRefreshTokenFromContext(c: Context): string | null {\n return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;\n}\n\nexport function setAuthCookies(\n c: Context,\n tokens: TokenPair,\n user: UserResource | PlatformUserResource,\n): void {\n const isProduction =\n 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 setCookie(c, 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 setCookie(c, 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 setCookie(\n c,\n COOKIE_AUTH_SESSION,\n 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 {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n },\n );\n}\n\nexport function clearAuthCookies(c: Context): void {\n deleteCookie(c, COOKIE_AUTH_TOKEN, { path: \"/\" });\n deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n deleteCookie(c, COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n","import { Hono } from \"hono\";\nimport type { InAIAuthConfig, TokenPair, UserResource, LoginResult } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n setAuthCookies,\n clearAuthCookies,\n getRefreshTokenFromContext,\n} from \"./helpers\";\n\nexport function createAuthRoutes(config: InAIAuthConfig = {}) {\n const app = new Hono();\n const client = new InAIAuthClient(config);\n\n app.post(\"/login\", async (c) => {\n try {\n const body = await c.req.json<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 c.json({ mfa_required: true, mfa_token: result.mfa_token });\n }\n\n const tokens = result as unknown as TokenPair;\n const user =\n result.user ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Login failed\";\n return c.json({ error: message }, 401);\n }\n });\n\n app.post(\"/register\", async (c) => {\n try {\n const body = await c.req.json<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 c.json({ needs_email_verification: true, user: result.user });\n }\n\n const tokens = result as unknown as TokenPair;\n const user =\n result.user ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Registration failed\";\n return c.json({ error: message }, 400);\n }\n });\n\n app.post(\"/mfa-challenge\", async (c) => {\n try {\n const body = await c.req.json<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(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"MFA verification failed\";\n return c.json({ error: message }, 401);\n }\n });\n\n app.post(\"/refresh\", async (c) => {\n try {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (!refreshToken) {\n clearAuthCookies(c);\n return c.json({ 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(c, tokens, user);\n\n return c.json({ user });\n } catch {\n clearAuthCookies(c);\n return c.json({ error: \"Refresh failed\" }, 401);\n }\n });\n\n app.post(\"/logout\", async (c) => {\n try {\n const refreshToken = getRefreshTokenFromContext(c);\n if (refreshToken) {\n await client.logout(refreshToken).catch(() => {});\n }\n clearAuthCookies(c);\n return c.json({ success: true });\n } catch {\n clearAuthCookies(c);\n return c.json({ success: true });\n }\n });\n\n return app;\n}\n"],"mappings":"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","names":["import_shared","authObj","import_backend"]}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/middleware.ts
|
|
2
2
|
import { InAIAuthClient, buildAuthObjectFromToken } from "@inai-dev/backend";
|
|
3
|
-
import { isTokenExpired } from "@inai-dev/shared";
|
|
3
|
+
import { isTokenExpired, JWKSClient, DEFAULT_API_URL } from "@inai-dev/shared";
|
|
4
4
|
|
|
5
5
|
// src/helpers.ts
|
|
6
6
|
import { getCookie, setCookie, deleteCookie } from "hono/cookie";
|
|
@@ -90,6 +90,8 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
90
90
|
} = config;
|
|
91
91
|
const client = new InAIAuthClient(authClientConfig);
|
|
92
92
|
const isPlatform = authMode === "platform";
|
|
93
|
+
const jwksUrl = authClientConfig.jwksUrl ?? `${authClientConfig.apiUrl ?? DEFAULT_API_URL}/.well-known/jwks.json`;
|
|
94
|
+
const jwksClient = new JWKSClient(jwksUrl);
|
|
93
95
|
const defaultUnauthorized = (c) => c.json({ error: "Unauthorized" }, 401);
|
|
94
96
|
const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;
|
|
95
97
|
return async function middleware(c, next) {
|
|
@@ -107,7 +109,7 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
107
109
|
const tokens = isPlatform ? await client.platformRefresh(refreshToken) : await client.refresh(refreshToken);
|
|
108
110
|
const { data: user } = isPlatform ? await client.platformGetMe(tokens.access_token) : await client.getMe(tokens.access_token);
|
|
109
111
|
setAuthCookies(c, tokens, user);
|
|
110
|
-
const authObj2 = buildAuthObjectFromToken(tokens.access_token);
|
|
112
|
+
const authObj2 = await buildAuthObjectFromToken(tokens.access_token, jwksClient);
|
|
111
113
|
c.set("inaiAuth", authObj2);
|
|
112
114
|
await next();
|
|
113
115
|
return;
|
|
@@ -118,7 +120,7 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
118
120
|
}
|
|
119
121
|
return handleUnauthorized(c);
|
|
120
122
|
}
|
|
121
|
-
const authObj = buildAuthObjectFromToken(token);
|
|
123
|
+
const authObj = await buildAuthObjectFromToken(token, jwksClient);
|
|
122
124
|
if (!authObj) {
|
|
123
125
|
return handleUnauthorized(c);
|
|
124
126
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts","../src/helpers.ts","../src/api-routes.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"hono\";\nimport type { InAIAuthConfig } from \"@inai-dev/types\";\nimport { InAIAuthClient, buildAuthObjectFromToken } from \"@inai-dev/backend\";\nimport { isTokenExpired } from \"@inai-dev/shared\";\nimport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\nimport {\n getTokenFromContext,\n getRefreshTokenFromContext,\n setAuthCookies,\n clearAuthCookies,\n getAuth,\n} from \"./helpers\";\n\nfunction matchesRoute(pathname: string, patterns: string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n return pathname === pattern;\n });\n}\n\nfunction isPublicRoute(\n path: string,\n publicRoutes: string[] | ((path: string) => boolean),\n): boolean {\n if (typeof publicRoutes === \"function\") return publicRoutes(path);\n return matchesRoute(path, publicRoutes);\n}\n\nexport function inaiAuthMiddleware(\n config: InAIHonoMiddlewareConfig & InAIAuthConfig = {},\n): MiddlewareHandler {\n const {\n authMode = \"app\",\n publicRoutes = [],\n onUnauthorized,\n ...authClientConfig\n } = config;\n\n const client = new InAIAuthClient(authClientConfig);\n const isPlatform = authMode === \"platform\";\n\n const defaultUnauthorized = (c: Parameters<MiddlewareHandler>[0]) =>\n c.json({ error: \"Unauthorized\" }, 401);\n\n const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;\n\n return async function middleware(c, next) {\n const path = new URL(c.req.url).pathname;\n\n if (isPublicRoute(path, publicRoutes)) {\n c.set(\"inaiAuth\", null);\n await next();\n return;\n }\n\n const token = getTokenFromContext(c);\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (refreshToken) {\n try {\n const tokens = isPlatform\n ? await client.platformRefresh(refreshToken)\n : await client.refresh(refreshToken);\n const { data: user } = isPlatform\n ? await client.platformGetMe(tokens.access_token)\n : await client.getMe(tokens.access_token);\n setAuthCookies(c, tokens, user);\n\n const authObj = buildAuthObjectFromToken(tokens.access_token);\n c.set(\"inaiAuth\", authObj);\n\n await next();\n return;\n } catch {\n clearAuthCookies(c);\n return handleUnauthorized(c);\n }\n }\n\n return handleUnauthorized(c);\n }\n\n const authObj = buildAuthObjectFromToken(token);\n if (!authObj) {\n return handleUnauthorized(c);\n }\n\n c.set(\"inaiAuth\", authObj);\n await next();\n };\n}\n\nexport function requireAuth(config: RequireAuthConfig = {}): MiddlewareHandler {\n return async function middleware(c, next) {\n const auth = getAuth(c);\n\n if (!auth?.userId) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n if (config.role || config.permission) {\n const hasAccess = auth.has({\n role: config.role,\n permission: config.permission,\n });\n\n if (!hasAccess) {\n return c.json({ error: \"Forbidden\" }, 403);\n }\n }\n\n await next();\n };\n}\n","import type { Context } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { AuthObject, TokenPair, UserResource, PlatformUserResource } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\nexport function getAuth(c: Context): AuthObject | null {\n return c.get(\"inaiAuth\") ?? null;\n}\n\nexport function getTokenFromContext(c: Context): string | null {\n const authHeader = c.req.header(\"Authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n return authHeader.slice(7);\n }\n\n return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;\n}\n\nexport function getRefreshTokenFromContext(c: Context): string | null {\n return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;\n}\n\nexport function setAuthCookies(\n c: Context,\n tokens: TokenPair,\n user: UserResource | PlatformUserResource,\n): void {\n const isProduction =\n 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 setCookie(c, 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 setCookie(c, 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 setCookie(\n c,\n COOKIE_AUTH_SESSION,\n 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 {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n },\n );\n}\n\nexport function clearAuthCookies(c: Context): void {\n deleteCookie(c, COOKIE_AUTH_TOKEN, { path: \"/\" });\n deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n deleteCookie(c, COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n","import { Hono } from \"hono\";\nimport type { InAIAuthConfig, TokenPair, UserResource, LoginResult } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n setAuthCookies,\n clearAuthCookies,\n getRefreshTokenFromContext,\n} from \"./helpers\";\n\nexport function createAuthRoutes(config: InAIAuthConfig = {}) {\n const app = new Hono();\n const client = new InAIAuthClient(config);\n\n app.post(\"/login\", async (c) => {\n try {\n const body = await c.req.json<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 c.json({ mfa_required: true, mfa_token: result.mfa_token });\n }\n\n const tokens = result as unknown as TokenPair;\n const user =\n result.user ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Login failed\";\n return c.json({ error: message }, 401);\n }\n });\n\n app.post(\"/register\", async (c) => {\n try {\n const body = await c.req.json<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 c.json({ needs_email_verification: true, user: result.user });\n }\n\n const tokens = result as unknown as TokenPair;\n const user =\n result.user ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Registration failed\";\n return c.json({ error: message }, 400);\n }\n });\n\n app.post(\"/mfa-challenge\", async (c) => {\n try {\n const body = await c.req.json<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(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"MFA verification failed\";\n return c.json({ error: message }, 401);\n }\n });\n\n app.post(\"/refresh\", async (c) => {\n try {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (!refreshToken) {\n clearAuthCookies(c);\n return c.json({ 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(c, tokens, user);\n\n return c.json({ user });\n } catch {\n clearAuthCookies(c);\n return c.json({ error: \"Refresh failed\" }, 401);\n }\n });\n\n app.post(\"/logout\", async (c) => {\n try {\n const refreshToken = getRefreshTokenFromContext(c);\n if (refreshToken) {\n await client.logout(refreshToken).catch(() => {});\n }\n clearAuthCookies(c);\n return c.json({ success: true });\n } catch {\n clearAuthCookies(c);\n return c.json({ success: true });\n }\n });\n\n return app;\n}\n"],"mappings":"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","names":["authObj","InAIAuthClient","InAIAuthClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../src/helpers.ts","../src/api-routes.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"hono\";\nimport type { InAIAuthConfig } from \"@inai-dev/types\";\nimport { InAIAuthClient, buildAuthObjectFromToken } from \"@inai-dev/backend\";\nimport { isTokenExpired, JWKSClient, DEFAULT_API_URL } from \"@inai-dev/shared\";\nimport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\nimport {\n getTokenFromContext,\n getRefreshTokenFromContext,\n setAuthCookies,\n clearAuthCookies,\n getAuth,\n} from \"./helpers\";\n\nfunction matchesRoute(pathname: string, patterns: string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n return pathname === pattern;\n });\n}\n\nfunction isPublicRoute(\n path: string,\n publicRoutes: string[] | ((path: string) => boolean),\n): boolean {\n if (typeof publicRoutes === \"function\") return publicRoutes(path);\n return matchesRoute(path, publicRoutes);\n}\n\nexport function inaiAuthMiddleware(\n config: InAIHonoMiddlewareConfig & InAIAuthConfig = {},\n): MiddlewareHandler {\n const {\n authMode = \"app\",\n publicRoutes = [],\n onUnauthorized,\n ...authClientConfig\n } = config;\n\n const client = new InAIAuthClient(authClientConfig);\n const isPlatform = authMode === \"platform\";\n\n const jwksUrl = authClientConfig.jwksUrl\n ?? `${authClientConfig.apiUrl ?? DEFAULT_API_URL}/.well-known/jwks.json`;\n const jwksClient = new JWKSClient(jwksUrl);\n\n const defaultUnauthorized = (c: Parameters<MiddlewareHandler>[0]) =>\n c.json({ error: \"Unauthorized\" }, 401);\n\n const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;\n\n return async function middleware(c, next) {\n const path = new URL(c.req.url).pathname;\n\n if (isPublicRoute(path, publicRoutes)) {\n c.set(\"inaiAuth\", null);\n await next();\n return;\n }\n\n const token = getTokenFromContext(c);\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (refreshToken) {\n try {\n const tokens = isPlatform\n ? await client.platformRefresh(refreshToken)\n : await client.refresh(refreshToken);\n const { data: user } = isPlatform\n ? await client.platformGetMe(tokens.access_token)\n : await client.getMe(tokens.access_token);\n setAuthCookies(c, tokens, user);\n\n const authObj = await buildAuthObjectFromToken(tokens.access_token, jwksClient);\n c.set(\"inaiAuth\", authObj);\n\n await next();\n return;\n } catch {\n clearAuthCookies(c);\n return handleUnauthorized(c);\n }\n }\n\n return handleUnauthorized(c);\n }\n\n const authObj = await buildAuthObjectFromToken(token, jwksClient);\n if (!authObj) {\n return handleUnauthorized(c);\n }\n\n c.set(\"inaiAuth\", authObj);\n await next();\n };\n}\n\nexport function requireAuth(config: RequireAuthConfig = {}): MiddlewareHandler {\n return async function middleware(c, next) {\n const auth = getAuth(c);\n\n if (!auth?.userId) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n if (config.role || config.permission) {\n const hasAccess = auth.has({\n role: config.role,\n permission: config.permission,\n });\n\n if (!hasAccess) {\n return c.json({ error: \"Forbidden\" }, 403);\n }\n }\n\n await next();\n };\n}\n","import type { Context } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { AuthObject, TokenPair, UserResource, PlatformUserResource } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\nexport function getAuth(c: Context): AuthObject | null {\n return c.get(\"inaiAuth\") ?? null;\n}\n\nexport function getTokenFromContext(c: Context): string | null {\n const authHeader = c.req.header(\"Authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n return authHeader.slice(7);\n }\n\n return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;\n}\n\nexport function getRefreshTokenFromContext(c: Context): string | null {\n return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;\n}\n\nexport function setAuthCookies(\n c: Context,\n tokens: TokenPair,\n user: UserResource | PlatformUserResource,\n): void {\n const isProduction =\n 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 setCookie(c, 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 setCookie(c, 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 setCookie(\n c,\n COOKIE_AUTH_SESSION,\n 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 {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n },\n );\n}\n\nexport function clearAuthCookies(c: Context): void {\n deleteCookie(c, COOKIE_AUTH_TOKEN, { path: \"/\" });\n deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n deleteCookie(c, COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n","import { Hono } from \"hono\";\nimport type { InAIAuthConfig, TokenPair, UserResource, LoginResult } from \"@inai-dev/types\";\nimport { InAIAuthClient } from \"@inai-dev/backend\";\nimport {\n setAuthCookies,\n clearAuthCookies,\n getRefreshTokenFromContext,\n} from \"./helpers\";\n\nexport function createAuthRoutes(config: InAIAuthConfig = {}) {\n const app = new Hono();\n const client = new InAIAuthClient(config);\n\n app.post(\"/login\", async (c) => {\n try {\n const body = await c.req.json<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 c.json({ mfa_required: true, mfa_token: result.mfa_token });\n }\n\n const tokens = result as unknown as TokenPair;\n const user =\n result.user ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Login failed\";\n return c.json({ error: message }, 401);\n }\n });\n\n app.post(\"/register\", async (c) => {\n try {\n const body = await c.req.json<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 c.json({ needs_email_verification: true, user: result.user });\n }\n\n const tokens = result as unknown as TokenPair;\n const user =\n result.user ?? (await client.getMe(tokens.access_token)).data;\n setAuthCookies(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Registration failed\";\n return c.json({ error: message }, 400);\n }\n });\n\n app.post(\"/mfa-challenge\", async (c) => {\n try {\n const body = await c.req.json<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(c, tokens, user);\n\n return c.json({ user });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"MFA verification failed\";\n return c.json({ error: message }, 401);\n }\n });\n\n app.post(\"/refresh\", async (c) => {\n try {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (!refreshToken) {\n clearAuthCookies(c);\n return c.json({ 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(c, tokens, user);\n\n return c.json({ user });\n } catch {\n clearAuthCookies(c);\n return c.json({ error: \"Refresh failed\" }, 401);\n }\n });\n\n app.post(\"/logout\", async (c) => {\n try {\n const refreshToken = getRefreshTokenFromContext(c);\n if (refreshToken) {\n await client.logout(refreshToken).catch(() => {});\n }\n clearAuthCookies(c);\n return c.json({ success: true });\n } catch {\n clearAuthCookies(c);\n return c.json({ success: true });\n }\n });\n\n return app;\n}\n"],"mappings":"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","names":["authObj","InAIAuthClient","InAIAuthClient"]}
|
package/dist/middleware.cjs
CHANGED
|
@@ -110,6 +110,8 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
110
110
|
} = config;
|
|
111
111
|
const client = new import_backend.InAIAuthClient(authClientConfig);
|
|
112
112
|
const isPlatform = authMode === "platform";
|
|
113
|
+
const jwksUrl = authClientConfig.jwksUrl ?? `${authClientConfig.apiUrl ?? import_shared2.DEFAULT_API_URL}/.well-known/jwks.json`;
|
|
114
|
+
const jwksClient = new import_shared2.JWKSClient(jwksUrl);
|
|
113
115
|
const defaultUnauthorized = (c) => c.json({ error: "Unauthorized" }, 401);
|
|
114
116
|
const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;
|
|
115
117
|
return async function middleware(c, next) {
|
|
@@ -127,7 +129,7 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
127
129
|
const tokens = isPlatform ? await client.platformRefresh(refreshToken) : await client.refresh(refreshToken);
|
|
128
130
|
const { data: user } = isPlatform ? await client.platformGetMe(tokens.access_token) : await client.getMe(tokens.access_token);
|
|
129
131
|
setAuthCookies(c, tokens, user);
|
|
130
|
-
const authObj2 = (0, import_backend.buildAuthObjectFromToken)(tokens.access_token);
|
|
132
|
+
const authObj2 = await (0, import_backend.buildAuthObjectFromToken)(tokens.access_token, jwksClient);
|
|
131
133
|
c.set("inaiAuth", authObj2);
|
|
132
134
|
await next();
|
|
133
135
|
return;
|
|
@@ -138,7 +140,7 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
138
140
|
}
|
|
139
141
|
return handleUnauthorized(c);
|
|
140
142
|
}
|
|
141
|
-
const authObj = (0, import_backend.buildAuthObjectFromToken)(token);
|
|
143
|
+
const authObj = await (0, import_backend.buildAuthObjectFromToken)(token, jwksClient);
|
|
142
144
|
if (!authObj) {
|
|
143
145
|
return handleUnauthorized(c);
|
|
144
146
|
}
|
package/dist/middleware.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts","../src/helpers.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"hono\";\nimport type { InAIAuthConfig } from \"@inai-dev/types\";\nimport { InAIAuthClient, buildAuthObjectFromToken } from \"@inai-dev/backend\";\nimport { isTokenExpired } from \"@inai-dev/shared\";\nimport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\nimport {\n getTokenFromContext,\n getRefreshTokenFromContext,\n setAuthCookies,\n clearAuthCookies,\n getAuth,\n} from \"./helpers\";\n\nfunction matchesRoute(pathname: string, patterns: string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n return pathname === pattern;\n });\n}\n\nfunction isPublicRoute(\n path: string,\n publicRoutes: string[] | ((path: string) => boolean),\n): boolean {\n if (typeof publicRoutes === \"function\") return publicRoutes(path);\n return matchesRoute(path, publicRoutes);\n}\n\nexport function inaiAuthMiddleware(\n config: InAIHonoMiddlewareConfig & InAIAuthConfig = {},\n): MiddlewareHandler {\n const {\n authMode = \"app\",\n publicRoutes = [],\n onUnauthorized,\n ...authClientConfig\n } = config;\n\n const client = new InAIAuthClient(authClientConfig);\n const isPlatform = authMode === \"platform\";\n\n const defaultUnauthorized = (c: Parameters<MiddlewareHandler>[0]) =>\n c.json({ error: \"Unauthorized\" }, 401);\n\n const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;\n\n return async function middleware(c, next) {\n const path = new URL(c.req.url).pathname;\n\n if (isPublicRoute(path, publicRoutes)) {\n c.set(\"inaiAuth\", null);\n await next();\n return;\n }\n\n const token = getTokenFromContext(c);\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (refreshToken) {\n try {\n const tokens = isPlatform\n ? await client.platformRefresh(refreshToken)\n : await client.refresh(refreshToken);\n const { data: user } = isPlatform\n ? await client.platformGetMe(tokens.access_token)\n : await client.getMe(tokens.access_token);\n setAuthCookies(c, tokens, user);\n\n const authObj = buildAuthObjectFromToken(tokens.access_token);\n c.set(\"inaiAuth\", authObj);\n\n await next();\n return;\n } catch {\n clearAuthCookies(c);\n return handleUnauthorized(c);\n }\n }\n\n return handleUnauthorized(c);\n }\n\n const authObj = buildAuthObjectFromToken(token);\n if (!authObj) {\n return handleUnauthorized(c);\n }\n\n c.set(\"inaiAuth\", authObj);\n await next();\n };\n}\n\nexport function requireAuth(config: RequireAuthConfig = {}): MiddlewareHandler {\n return async function middleware(c, next) {\n const auth = getAuth(c);\n\n if (!auth?.userId) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n if (config.role || config.permission) {\n const hasAccess = auth.has({\n role: config.role,\n permission: config.permission,\n });\n\n if (!hasAccess) {\n return c.json({ error: \"Forbidden\" }, 403);\n }\n }\n\n await next();\n };\n}\n","import type { Context } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { AuthObject, TokenPair, UserResource, PlatformUserResource } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\nexport function getAuth(c: Context): AuthObject | null {\n return c.get(\"inaiAuth\") ?? null;\n}\n\nexport function getTokenFromContext(c: Context): string | null {\n const authHeader = c.req.header(\"Authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n return authHeader.slice(7);\n }\n\n return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;\n}\n\nexport function getRefreshTokenFromContext(c: Context): string | null {\n return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;\n}\n\nexport function setAuthCookies(\n c: Context,\n tokens: TokenPair,\n user: UserResource | PlatformUserResource,\n): void {\n const isProduction =\n 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 setCookie(c, 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 setCookie(c, 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 setCookie(\n c,\n COOKIE_AUTH_SESSION,\n 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 {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n },\n );\n}\n\nexport function clearAuthCookies(c: Context): void {\n deleteCookie(c, COOKIE_AUTH_TOKEN, { path: \"/\" });\n deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n deleteCookie(c, COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,qBAAyD;AACzD,IAAAA,
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../src/helpers.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"hono\";\nimport type { InAIAuthConfig } from \"@inai-dev/types\";\nimport { InAIAuthClient, buildAuthObjectFromToken } from \"@inai-dev/backend\";\nimport { isTokenExpired, JWKSClient, DEFAULT_API_URL } from \"@inai-dev/shared\";\nimport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\nimport {\n getTokenFromContext,\n getRefreshTokenFromContext,\n setAuthCookies,\n clearAuthCookies,\n getAuth,\n} from \"./helpers\";\n\nfunction matchesRoute(pathname: string, patterns: string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n return pathname === pattern;\n });\n}\n\nfunction isPublicRoute(\n path: string,\n publicRoutes: string[] | ((path: string) => boolean),\n): boolean {\n if (typeof publicRoutes === \"function\") return publicRoutes(path);\n return matchesRoute(path, publicRoutes);\n}\n\nexport function inaiAuthMiddleware(\n config: InAIHonoMiddlewareConfig & InAIAuthConfig = {},\n): MiddlewareHandler {\n const {\n authMode = \"app\",\n publicRoutes = [],\n onUnauthorized,\n ...authClientConfig\n } = config;\n\n const client = new InAIAuthClient(authClientConfig);\n const isPlatform = authMode === \"platform\";\n\n const jwksUrl = authClientConfig.jwksUrl\n ?? `${authClientConfig.apiUrl ?? DEFAULT_API_URL}/.well-known/jwks.json`;\n const jwksClient = new JWKSClient(jwksUrl);\n\n const defaultUnauthorized = (c: Parameters<MiddlewareHandler>[0]) =>\n c.json({ error: \"Unauthorized\" }, 401);\n\n const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;\n\n return async function middleware(c, next) {\n const path = new URL(c.req.url).pathname;\n\n if (isPublicRoute(path, publicRoutes)) {\n c.set(\"inaiAuth\", null);\n await next();\n return;\n }\n\n const token = getTokenFromContext(c);\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (refreshToken) {\n try {\n const tokens = isPlatform\n ? await client.platformRefresh(refreshToken)\n : await client.refresh(refreshToken);\n const { data: user } = isPlatform\n ? await client.platformGetMe(tokens.access_token)\n : await client.getMe(tokens.access_token);\n setAuthCookies(c, tokens, user);\n\n const authObj = await buildAuthObjectFromToken(tokens.access_token, jwksClient);\n c.set(\"inaiAuth\", authObj);\n\n await next();\n return;\n } catch {\n clearAuthCookies(c);\n return handleUnauthorized(c);\n }\n }\n\n return handleUnauthorized(c);\n }\n\n const authObj = await buildAuthObjectFromToken(token, jwksClient);\n if (!authObj) {\n return handleUnauthorized(c);\n }\n\n c.set(\"inaiAuth\", authObj);\n await next();\n };\n}\n\nexport function requireAuth(config: RequireAuthConfig = {}): MiddlewareHandler {\n return async function middleware(c, next) {\n const auth = getAuth(c);\n\n if (!auth?.userId) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n if (config.role || config.permission) {\n const hasAccess = auth.has({\n role: config.role,\n permission: config.permission,\n });\n\n if (!hasAccess) {\n return c.json({ error: \"Forbidden\" }, 403);\n }\n }\n\n await next();\n };\n}\n","import type { Context } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { AuthObject, TokenPair, UserResource, PlatformUserResource } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\nexport function getAuth(c: Context): AuthObject | null {\n return c.get(\"inaiAuth\") ?? null;\n}\n\nexport function getTokenFromContext(c: Context): string | null {\n const authHeader = c.req.header(\"Authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n return authHeader.slice(7);\n }\n\n return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;\n}\n\nexport function getRefreshTokenFromContext(c: Context): string | null {\n return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;\n}\n\nexport function setAuthCookies(\n c: Context,\n tokens: TokenPair,\n user: UserResource | PlatformUserResource,\n): void {\n const isProduction =\n 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 setCookie(c, 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 setCookie(c, 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 setCookie(\n c,\n COOKIE_AUTH_SESSION,\n 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 {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n },\n );\n}\n\nexport function clearAuthCookies(c: Context): void {\n deleteCookie(c, COOKIE_AUTH_TOKEN, { path: \"/\" });\n deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n deleteCookie(c, COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n"],"mappings":"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","names":["import_shared","authObj"]}
|
package/dist/middleware.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/middleware.ts
|
|
2
2
|
import { InAIAuthClient, buildAuthObjectFromToken } from "@inai-dev/backend";
|
|
3
|
-
import { isTokenExpired } from "@inai-dev/shared";
|
|
3
|
+
import { isTokenExpired, JWKSClient, DEFAULT_API_URL } from "@inai-dev/shared";
|
|
4
4
|
|
|
5
5
|
// src/helpers.ts
|
|
6
6
|
import { getCookie, setCookie, deleteCookie } from "hono/cookie";
|
|
@@ -90,6 +90,8 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
90
90
|
} = config;
|
|
91
91
|
const client = new InAIAuthClient(authClientConfig);
|
|
92
92
|
const isPlatform = authMode === "platform";
|
|
93
|
+
const jwksUrl = authClientConfig.jwksUrl ?? `${authClientConfig.apiUrl ?? DEFAULT_API_URL}/.well-known/jwks.json`;
|
|
94
|
+
const jwksClient = new JWKSClient(jwksUrl);
|
|
93
95
|
const defaultUnauthorized = (c) => c.json({ error: "Unauthorized" }, 401);
|
|
94
96
|
const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;
|
|
95
97
|
return async function middleware(c, next) {
|
|
@@ -107,7 +109,7 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
107
109
|
const tokens = isPlatform ? await client.platformRefresh(refreshToken) : await client.refresh(refreshToken);
|
|
108
110
|
const { data: user } = isPlatform ? await client.platformGetMe(tokens.access_token) : await client.getMe(tokens.access_token);
|
|
109
111
|
setAuthCookies(c, tokens, user);
|
|
110
|
-
const authObj2 = buildAuthObjectFromToken(tokens.access_token);
|
|
112
|
+
const authObj2 = await buildAuthObjectFromToken(tokens.access_token, jwksClient);
|
|
111
113
|
c.set("inaiAuth", authObj2);
|
|
112
114
|
await next();
|
|
113
115
|
return;
|
|
@@ -118,7 +120,7 @@ function inaiAuthMiddleware(config = {}) {
|
|
|
118
120
|
}
|
|
119
121
|
return handleUnauthorized(c);
|
|
120
122
|
}
|
|
121
|
-
const authObj = buildAuthObjectFromToken(token);
|
|
123
|
+
const authObj = await buildAuthObjectFromToken(token, jwksClient);
|
|
122
124
|
if (!authObj) {
|
|
123
125
|
return handleUnauthorized(c);
|
|
124
126
|
}
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts","../src/helpers.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"hono\";\nimport type { InAIAuthConfig } from \"@inai-dev/types\";\nimport { InAIAuthClient, buildAuthObjectFromToken } from \"@inai-dev/backend\";\nimport { isTokenExpired } from \"@inai-dev/shared\";\nimport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\nimport {\n getTokenFromContext,\n getRefreshTokenFromContext,\n setAuthCookies,\n clearAuthCookies,\n getAuth,\n} from \"./helpers\";\n\nfunction matchesRoute(pathname: string, patterns: string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n return pathname === pattern;\n });\n}\n\nfunction isPublicRoute(\n path: string,\n publicRoutes: string[] | ((path: string) => boolean),\n): boolean {\n if (typeof publicRoutes === \"function\") return publicRoutes(path);\n return matchesRoute(path, publicRoutes);\n}\n\nexport function inaiAuthMiddleware(\n config: InAIHonoMiddlewareConfig & InAIAuthConfig = {},\n): MiddlewareHandler {\n const {\n authMode = \"app\",\n publicRoutes = [],\n onUnauthorized,\n ...authClientConfig\n } = config;\n\n const client = new InAIAuthClient(authClientConfig);\n const isPlatform = authMode === \"platform\";\n\n const defaultUnauthorized = (c: Parameters<MiddlewareHandler>[0]) =>\n c.json({ error: \"Unauthorized\" }, 401);\n\n const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;\n\n return async function middleware(c, next) {\n const path = new URL(c.req.url).pathname;\n\n if (isPublicRoute(path, publicRoutes)) {\n c.set(\"inaiAuth\", null);\n await next();\n return;\n }\n\n const token = getTokenFromContext(c);\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (refreshToken) {\n try {\n const tokens = isPlatform\n ? await client.platformRefresh(refreshToken)\n : await client.refresh(refreshToken);\n const { data: user } = isPlatform\n ? await client.platformGetMe(tokens.access_token)\n : await client.getMe(tokens.access_token);\n setAuthCookies(c, tokens, user);\n\n const authObj = buildAuthObjectFromToken(tokens.access_token);\n c.set(\"inaiAuth\", authObj);\n\n await next();\n return;\n } catch {\n clearAuthCookies(c);\n return handleUnauthorized(c);\n }\n }\n\n return handleUnauthorized(c);\n }\n\n const authObj = buildAuthObjectFromToken(token);\n if (!authObj) {\n return handleUnauthorized(c);\n }\n\n c.set(\"inaiAuth\", authObj);\n await next();\n };\n}\n\nexport function requireAuth(config: RequireAuthConfig = {}): MiddlewareHandler {\n return async function middleware(c, next) {\n const auth = getAuth(c);\n\n if (!auth?.userId) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n if (config.role || config.permission) {\n const hasAccess = auth.has({\n role: config.role,\n permission: config.permission,\n });\n\n if (!hasAccess) {\n return c.json({ error: \"Forbidden\" }, 403);\n }\n }\n\n await next();\n };\n}\n","import type { Context } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { AuthObject, TokenPair, UserResource, PlatformUserResource } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\nexport function getAuth(c: Context): AuthObject | null {\n return c.get(\"inaiAuth\") ?? null;\n}\n\nexport function getTokenFromContext(c: Context): string | null {\n const authHeader = c.req.header(\"Authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n return authHeader.slice(7);\n }\n\n return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;\n}\n\nexport function getRefreshTokenFromContext(c: Context): string | null {\n return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;\n}\n\nexport function setAuthCookies(\n c: Context,\n tokens: TokenPair,\n user: UserResource | PlatformUserResource,\n): void {\n const isProduction =\n 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 setCookie(c, 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 setCookie(c, 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 setCookie(\n c,\n COOKIE_AUTH_SESSION,\n 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 {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n },\n );\n}\n\nexport function clearAuthCookies(c: Context): void {\n deleteCookie(c, COOKIE_AUTH_TOKEN, { path: \"/\" });\n deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n deleteCookie(c, COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n"],"mappings":";AAEA,SAAS,gBAAgB,gCAAgC;AACzD,SAAS,
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../src/helpers.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"hono\";\nimport type { InAIAuthConfig } from \"@inai-dev/types\";\nimport { InAIAuthClient, buildAuthObjectFromToken } from \"@inai-dev/backend\";\nimport { isTokenExpired, JWKSClient, DEFAULT_API_URL } from \"@inai-dev/shared\";\nimport type { InAIHonoMiddlewareConfig, RequireAuthConfig } from \"./types\";\nimport {\n getTokenFromContext,\n getRefreshTokenFromContext,\n setAuthCookies,\n clearAuthCookies,\n getAuth,\n} from \"./helpers\";\n\nfunction matchesRoute(pathname: string, patterns: string[]): boolean {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n return pathname === pattern;\n });\n}\n\nfunction isPublicRoute(\n path: string,\n publicRoutes: string[] | ((path: string) => boolean),\n): boolean {\n if (typeof publicRoutes === \"function\") return publicRoutes(path);\n return matchesRoute(path, publicRoutes);\n}\n\nexport function inaiAuthMiddleware(\n config: InAIHonoMiddlewareConfig & InAIAuthConfig = {},\n): MiddlewareHandler {\n const {\n authMode = \"app\",\n publicRoutes = [],\n onUnauthorized,\n ...authClientConfig\n } = config;\n\n const client = new InAIAuthClient(authClientConfig);\n const isPlatform = authMode === \"platform\";\n\n const jwksUrl = authClientConfig.jwksUrl\n ?? `${authClientConfig.apiUrl ?? DEFAULT_API_URL}/.well-known/jwks.json`;\n const jwksClient = new JWKSClient(jwksUrl);\n\n const defaultUnauthorized = (c: Parameters<MiddlewareHandler>[0]) =>\n c.json({ error: \"Unauthorized\" }, 401);\n\n const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;\n\n return async function middleware(c, next) {\n const path = new URL(c.req.url).pathname;\n\n if (isPublicRoute(path, publicRoutes)) {\n c.set(\"inaiAuth\", null);\n await next();\n return;\n }\n\n const token = getTokenFromContext(c);\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = getRefreshTokenFromContext(c);\n\n if (refreshToken) {\n try {\n const tokens = isPlatform\n ? await client.platformRefresh(refreshToken)\n : await client.refresh(refreshToken);\n const { data: user } = isPlatform\n ? await client.platformGetMe(tokens.access_token)\n : await client.getMe(tokens.access_token);\n setAuthCookies(c, tokens, user);\n\n const authObj = await buildAuthObjectFromToken(tokens.access_token, jwksClient);\n c.set(\"inaiAuth\", authObj);\n\n await next();\n return;\n } catch {\n clearAuthCookies(c);\n return handleUnauthorized(c);\n }\n }\n\n return handleUnauthorized(c);\n }\n\n const authObj = await buildAuthObjectFromToken(token, jwksClient);\n if (!authObj) {\n return handleUnauthorized(c);\n }\n\n c.set(\"inaiAuth\", authObj);\n await next();\n };\n}\n\nexport function requireAuth(config: RequireAuthConfig = {}): MiddlewareHandler {\n return async function middleware(c, next) {\n const auth = getAuth(c);\n\n if (!auth?.userId) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n if (config.role || config.permission) {\n const hasAccess = auth.has({\n role: config.role,\n permission: config.permission,\n });\n\n if (!hasAccess) {\n return c.json({ error: \"Forbidden\" }, 403);\n }\n }\n\n await next();\n };\n}\n","import type { Context } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { AuthObject, TokenPair, UserResource, PlatformUserResource } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n decodeJWTPayload,\n} from \"@inai-dev/shared\";\n\nexport function getAuth(c: Context): AuthObject | null {\n return c.get(\"inaiAuth\") ?? null;\n}\n\nexport function getTokenFromContext(c: Context): string | null {\n const authHeader = c.req.header(\"Authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n return authHeader.slice(7);\n }\n\n return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;\n}\n\nexport function getRefreshTokenFromContext(c: Context): string | null {\n return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;\n}\n\nexport function setAuthCookies(\n c: Context,\n tokens: TokenPair,\n user: UserResource | PlatformUserResource,\n): void {\n const isProduction =\n 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 setCookie(c, 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 setCookie(c, 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 setCookie(\n c,\n COOKIE_AUTH_SESSION,\n 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 {\n httpOnly: false,\n secure: isProduction,\n sameSite: \"Lax\",\n path: \"/\",\n maxAge: tokens.expires_in,\n },\n );\n}\n\nexport function clearAuthCookies(c: Context): void {\n deleteCookie(c, COOKIE_AUTH_TOKEN, { path: \"/\" });\n deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: \"/api/auth\" });\n deleteCookie(c, COOKIE_AUTH_SESSION, { path: \"/\" });\n}\n"],"mappings":"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","names":["authObj"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inai-dev/hono",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Hono integration for InAI Auth SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"prepublishOnly": "npm run build"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@inai-dev/types": "^1.
|
|
39
|
-
"@inai-dev/shared": "^1.
|
|
40
|
-
"@inai-dev/backend": "^1.
|
|
38
|
+
"@inai-dev/types": "^1.3.0",
|
|
39
|
+
"@inai-dev/shared": "^1.3.0",
|
|
40
|
+
"@inai-dev/backend": "^1.4.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"hono": ">=4.0.0"
|