@inai-dev/nextjs 0.1.0 → 1.1.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/nextjs
2
2
 
3
- Full Next.js integration for InAI Auth. Includes middleware, server-side helpers, API route handlers, and re-exports all React hooks/components.
3
+ Full Next.js integration for InAI Auth. Includes middleware, server-side auth helpers, API route handlers, React hooks, and UI components.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,72 +8,410 @@ Full Next.js integration for InAI Auth. Includes middleware, server-side helpers
8
8
  npm install @inai-dev/nextjs
9
9
  ```
10
10
 
11
- ## Setup
12
-
13
- ### 1. Environment Variables
11
+ ## Environment Variables
14
12
 
15
13
  ```env
14
+ # Required — your publishable key (client-side accessible)
16
15
  NEXT_PUBLIC_INAI_PUBLISHABLE_KEY=pk_live_...
17
- INAI_SECRET_KEY=sk_live_...
16
+
17
+ # Optional — API URL overrides (defaults to https://apiauth.inai.dev)
18
+ INAI_API_URL=https://apiauth.inai.dev
19
+ NEXT_PUBLIC_INAI_API_URL=https://apiauth.inai.dev
18
20
  ```
19
21
 
20
- ### 2. Middleware
22
+ The API URL is resolved in this order:
23
+ 1. Explicit config via `configureAuth({ apiUrl: "..." })`
24
+ 2. `INAI_API_URL` or `NEXT_PUBLIC_INAI_API_URL` environment variable
25
+ 3. Default: `https://apiauth.inai.dev`
26
+
27
+ ## Setup
28
+
29
+ ### 1. Middleware
21
30
 
22
31
  ```ts
23
32
  // middleware.ts
24
- import { authMiddleware } from "@inai-dev/nextjs/middleware";
33
+ import { inaiAuthMiddleware } from "@inai-dev/nextjs/middleware";
25
34
 
26
- export default authMiddleware({
27
- publishableKey: process.env.NEXT_PUBLIC_INAI_PUBLISHABLE_KEY!,
28
- publicRoutes: ["/", "/about", "/sign-in"],
35
+ export default inaiAuthMiddleware({
36
+ publicRoutes: ["/", "/about", "/login"],
37
+ signInUrl: "/login",
29
38
  });
30
39
 
31
40
  export const config = { matcher: ["/((?!_next|static|favicon.ico).*)"] };
32
41
  ```
33
42
 
34
- ### 3. Provider
43
+ ### 2. Provider
35
44
 
36
45
  ```tsx
37
46
  // app/layout.tsx
38
- import { InAIProvider } from "@inai-dev/nextjs";
47
+ import { InAIAuthProvider } from "@inai-dev/nextjs";
39
48
 
40
- export default function RootLayout({ children }) {
41
- return <InAIProvider>{children}</InAIProvider>;
49
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
50
+ return (
51
+ <html>
52
+ <body>
53
+ <InAIAuthProvider>{children}</InAIAuthProvider>
54
+ </body>
55
+ </html>
56
+ );
42
57
  }
43
58
  ```
44
59
 
45
- ### 4. Server-Side Auth
60
+ ### 3. API Routes
61
+
62
+ ```ts
63
+ // app/api/auth/[...inai]/route.ts
64
+ import { createAuthRoutes } from "@inai-dev/nextjs/server";
65
+
66
+ export const { GET, POST } = createAuthRoutes();
67
+ ```
68
+
69
+ Handles the following endpoints automatically:
70
+ - `POST /api/auth/login` — User login
71
+ - `POST /api/auth/register` — User registration
72
+ - `POST /api/auth/mfa-challenge` — MFA verification
73
+ - `POST /api/auth/refresh` — Token refresh
74
+ - `POST /api/auth/logout` — User logout
75
+
76
+ #### Platform API Routes
77
+
78
+ For multi-tenant platform authentication:
79
+
80
+ ```ts
81
+ // app/api/auth/[...inai]/route.ts
82
+ import { createPlatformAuthRoutes } from "@inai-dev/nextjs/server";
83
+
84
+ export const { GET, POST } = createPlatformAuthRoutes();
85
+ ```
86
+
87
+ ## Server-Side Auth
88
+
89
+ ### `auth()`
90
+
91
+ Returns a `ServerAuthObject` with the current authentication state.
46
92
 
47
93
  ```ts
48
- // app/dashboard/page.tsx
49
94
  import { auth } from "@inai-dev/nextjs/server";
50
95
 
51
96
  export default async function Dashboard() {
52
- const session = await auth();
53
- if (!session) redirect("/sign-in");
54
- return <p>Welcome {session.user.email}</p>;
97
+ const { userId, has, protect, redirectToSignIn, getToken } = await auth();
98
+
99
+ // Check if user is authenticated
100
+ if (!userId) {
101
+ redirectToSignIn({ returnTo: "/dashboard" });
102
+ }
103
+
104
+ // Check roles/permissions
105
+ if (has({ role: "admin" })) {
106
+ // admin-only logic
107
+ }
108
+
109
+ // Protect — throws redirect if unauthorized
110
+ const authed = protect({ permission: "posts:write" });
111
+
112
+ // Get the access token
113
+ const token = await getToken();
114
+
115
+ return <p>User: {userId}</p>;
55
116
  }
56
117
  ```
57
118
 
58
- ### 5. API Route Handlers
119
+ **`ServerAuthObject`:**
120
+
121
+ | Property | Type | Description |
122
+ |---|---|---|
123
+ | `userId` | `string \| null` | Current user ID |
124
+ | `tenantId` | `string \| null` | Tenant ID |
125
+ | `appId` | `string \| null` | Application ID |
126
+ | `envId` | `string \| null` | Environment ID |
127
+ | `orgId` | `string \| null` | Active organization ID |
128
+ | `orgRole` | `string \| null` | Role in active organization |
129
+ | `sessionId` | `string \| null` | Session ID |
130
+ | `getToken()` | `() => Promise<string \| null>` | Get the access token |
131
+ | `has(params)` | `({ role?, permission? }) => boolean` | Check role or permission |
132
+ | `protect(params?)` | `({ role?, permission?, redirectTo? }) => ProtectedAuthObject` | Assert auth or redirect |
133
+ | `redirectToSignIn(opts?)` | `({ returnTo? }) => never` | Redirect to sign-in page |
134
+
135
+ ### `currentUser()`
136
+
137
+ Returns the full user object, or `null` if not authenticated.
59
138
 
60
139
  ```ts
61
- // app/api/auth/[...inai]/route.ts
62
- import { handleAuthRoutes } from "@inai-dev/nextjs";
63
- export const { GET, POST } = handleAuthRoutes();
140
+ import { currentUser } from "@inai-dev/nextjs/server";
141
+
142
+ export default async function Profile() {
143
+ const user = await currentUser();
144
+ if (!user) return null;
145
+
146
+ return <p>{user.email}</p>;
147
+ }
148
+
149
+ // Force a fresh fetch from the API (bypasses cached session)
150
+ const freshUser = await currentUser({ fresh: true });
151
+ ```
152
+
153
+ ## React Hooks
154
+
155
+ All hooks are imported from `@inai-dev/nextjs`.
156
+
157
+ ### `useAuth()`
158
+
159
+ ```ts
160
+ const { isLoaded, isSignedIn, userId, has, signOut } = useAuth();
161
+
162
+ has({ role: "admin" }); // check role
163
+ has({ permission: "read" }); // check permission
164
+ await signOut();
165
+ ```
166
+
167
+ ### `useUser()`
168
+
169
+ ```ts
170
+ const { isLoaded, isSignedIn, user } = useUser();
171
+ // user: UserResource | null
172
+ ```
173
+
174
+ ### `useSession()`
175
+
176
+ ```ts
177
+ const { isLoaded, isSignedIn, userId, tenantId, orgId, orgRole } = useSession();
178
+ ```
179
+
180
+ ### `useOrganization()`
181
+
182
+ ```ts
183
+ const { isLoaded, orgId, orgRole } = useOrganization();
184
+ ```
185
+
186
+ ### `useSignIn()`
187
+
188
+ ```ts
189
+ const { signIn, isLoading, error, status, reset } = useSignIn();
190
+
191
+ await signIn.create({ identifier: "user@example.com", password: "..." });
192
+ // status: "idle" | "loading" | "needs_mfa" | "complete" | "error"
193
+
194
+ // MFA flow
195
+ await signIn.attemptMFA({ code: "123456" });
196
+ ```
197
+
198
+ ### `useSignUp()`
199
+
200
+ ```ts
201
+ const { signUp, isLoading, error, status, reset } = useSignUp();
202
+
203
+ await signUp.create({
204
+ email: "user@example.com",
205
+ password: "...",
206
+ firstName: "Jane",
207
+ lastName: "Doe",
208
+ });
209
+ // status: "idle" | "loading" | "needs_email_verification" | "complete" | "error"
210
+ ```
211
+
212
+ ## React Components
213
+
214
+ All components are imported from `@inai-dev/nextjs`.
215
+
216
+ ### `<Protect>`
217
+
218
+ Renders children only if the user has the required role or permission.
219
+
220
+ ```tsx
221
+ <Protect role="admin" fallback={<p>Access denied</p>}>
222
+ <AdminPanel />
223
+ </Protect>
224
+
225
+ <Protect permission="posts:write">
226
+ <Editor />
227
+ </Protect>
228
+ ```
229
+
230
+ ### `<SignedIn>` / `<SignedOut>`
231
+
232
+ Conditional rendering based on authentication state.
233
+
234
+ ```tsx
235
+ <SignedIn>
236
+ <p>Welcome back!</p>
237
+ </SignedIn>
238
+ <SignedOut>
239
+ <p>Please sign in.</p>
240
+ </SignedOut>
241
+ ```
242
+
243
+ ### `<PermissionGate>`
244
+
245
+ Permission-based access control.
246
+
247
+ ```tsx
248
+ <PermissionGate permission="billing:manage" fallback={<p>No access</p>}>
249
+ <BillingSettings />
250
+ </PermissionGate>
251
+ ```
252
+
253
+ ### `<UserButton>`
254
+
255
+ User profile menu with avatar and dropdown.
256
+
257
+ ```tsx
258
+ <UserButton
259
+ afterSignOutUrl="/"
260
+ showName
261
+ menuItems={[{ label: "Settings", onClick: () => router.push("/settings") }]}
262
+ appearance={{ buttonSize: 36, buttonBg: "#1a1a2e" }}
263
+ />
264
+ ```
265
+
266
+ ### `<SignIn>`
267
+
268
+ Sign-in form with MFA support.
269
+
270
+ ```tsx
271
+ <SignIn
272
+ redirectUrl="/dashboard"
273
+ onSuccess={() => console.log("Signed in!")}
274
+ onMFARequired={(mfaToken) => router.push("/mfa")}
275
+ />
276
+ ```
277
+
278
+ ### `<OrganizationSwitcher>`
279
+
280
+ Organization switching dropdown.
281
+
282
+ ```tsx
283
+ <OrganizationSwitcher />
284
+ ```
285
+
286
+ ## Advanced Configuration
287
+
288
+ ### `configureAuth()` / `getAuthConfig()`
289
+
290
+ Set global configuration early in your app (e.g., in `layout.tsx` or a server initialization file).
291
+
292
+ ```ts
293
+ import { configureAuth, getAuthConfig } from "@inai-dev/nextjs/server";
294
+
295
+ configureAuth({
296
+ signInUrl: "/login",
297
+ signUpUrl: "/register",
298
+ afterSignInUrl: "/dashboard",
299
+ afterSignOutUrl: "/",
300
+ apiUrl: "https://apiauth.inai.dev",
301
+ publishableKey: "pk_live_...",
302
+ });
303
+
304
+ const config = getAuthConfig();
305
+ // { signInUrl, signUpUrl, afterSignInUrl, afterSignOutUrl, apiUrl, publishableKey }
306
+ ```
307
+
308
+ ### `createRouteMatcher()`
309
+
310
+ Create a reusable route matcher for middleware logic.
311
+
312
+ ```ts
313
+ import { createRouteMatcher } from "@inai-dev/nextjs/middleware";
314
+
315
+ const isPublic = createRouteMatcher(["/", "/about", "/api/(.*)"]);
316
+ const isAdmin = createRouteMatcher(["/admin(.*)"]);
317
+ ```
318
+
319
+ ### `withInAIAuth()`
320
+
321
+ Compose InAI auth with your existing middleware.
322
+
323
+ ```ts
324
+ import { withInAIAuth } from "@inai-dev/nextjs/middleware";
325
+
326
+ export default withInAIAuth(
327
+ (req) => {
328
+ // Your custom middleware logic
329
+ return NextResponse.next();
330
+ },
331
+ {
332
+ publicRoutes: ["/", "/login"],
333
+ signInUrl: "/login",
334
+ beforeAuth: (req) => {
335
+ // Runs before auth check
336
+ },
337
+ afterAuth: (auth, req) => {
338
+ // Runs after auth check
339
+ if (auth.userId && req.nextUrl.pathname === "/login") {
340
+ return NextResponse.redirect(new URL("/dashboard", req.url));
341
+ }
342
+ },
343
+ }
344
+ );
64
345
  ```
65
346
 
66
- ## Exports
347
+ ## Exports Reference
67
348
 
68
- - `@inai-dev/nextjs` — Provider, React hooks, API route handler
69
- - `@inai-dev/nextjs/server` — `auth()`, `currentUser()`, server-side helpers
70
- - `@inai-dev/nextjs/middleware` — `authMiddleware()`
349
+ ### `@inai-dev/nextjs`
71
350
 
72
- ## Documentation
351
+ | Export | Kind | Description |
352
+ |---|---|---|
353
+ | `InAIAuthProvider` | Component | Auth context provider |
354
+ | `Protect` | Component | Role/permission gate |
355
+ | `SignedIn` | Component | Renders when signed in |
356
+ | `SignedOut` | Component | Renders when signed out |
357
+ | `PermissionGate` | Component | Permission-based gate |
358
+ | `UserButton` | Component | User profile menu |
359
+ | `SignIn` | Component | Sign-in form |
360
+ | `OrganizationSwitcher` | Component | Org switcher |
361
+ | `useAuth` | Hook | Auth state & actions |
362
+ | `useUser` | Hook | User data |
363
+ | `useSession` | Hook | Session info |
364
+ | `useOrganization` | Hook | Organization data |
365
+ | `useSignIn` | Hook | Sign-in flow |
366
+ | `useSignUp` | Hook | Sign-up flow |
367
+ | `COOKIE_AUTH_TOKEN` | Constant | `"auth_token"` |
368
+ | `COOKIE_REFRESH_TOKEN` | Constant | `"refresh_token"` |
369
+ | `COOKIE_AUTH_SESSION` | Constant | `"auth_session"` |
73
370
 
74
- - [Getting Started](https://github.com/inai-dev/sdk/blob/main/docs/getting-started.md)
75
- - [Next.js Integration](https://github.com/inai-dev/sdk/blob/main/docs/nextjs-integration.md)
76
- - [API Reference](https://github.com/inai-dev/sdk/blob/main/docs/api-reference.md)
371
+ ### `@inai-dev/nextjs/server`
372
+
373
+ | Export | Kind | Description |
374
+ |---|---|---|
375
+ | `auth` | Function | Get `ServerAuthObject` |
376
+ | `currentUser` | Function | Get current user |
377
+ | `createAuthRoutes` | Function | App user auth routes |
378
+ | `createPlatformAuthRoutes` | Function | Platform auth routes |
379
+ | `configureAuth` | Function | Set global config |
380
+ | `getAuthConfig` | Function | Get resolved config |
381
+ | `setAuthCookies` | Function | Set auth cookies |
382
+ | `clearAuthCookies` | Function | Clear auth cookies |
383
+ | `getAuthTokenFromCookies` | Function | Get access token |
384
+ | `getRefreshTokenFromCookies` | Function | Get refresh token |
385
+
386
+ ### `@inai-dev/nextjs/middleware`
387
+
388
+ | Export | Kind | Description |
389
+ |---|---|---|
390
+ | `inaiAuthMiddleware` | Function | Auth middleware |
391
+ | `withInAIAuth` | Function | Compose middleware |
392
+ | `createRouteMatcher` | Function | Route pattern matcher |
393
+
394
+ ## Exported Types
395
+
396
+ ```ts
397
+ import type {
398
+ AuthObject,
399
+ ServerAuthObject,
400
+ ProtectedAuthObject,
401
+ UserResource,
402
+ PlatformUserResource,
403
+ SessionResource,
404
+ OrganizationResource,
405
+ InAIAuthConfig,
406
+ InAIAuthErrorBody,
407
+ SignInResult,
408
+ SignUpResult,
409
+ } from "@inai-dev/nextjs";
410
+
411
+ import type {
412
+ InAIMiddlewareConfig,
413
+ } from "@inai-dev/nextjs/middleware";
414
+ ```
77
415
 
78
416
  ## License
79
417
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Re-export all React hooks, components, and providers\nexport {\n InAIAuthProvider,\n useAuth,\n useUser,\n useSession,\n useOrganization,\n useSignIn,\n useSignUp,\n Protect,\n SignedIn,\n SignedOut,\n PermissionGate,\n UserButton,\n SignIn,\n OrganizationSwitcher,\n} from \"@inai-dev/react\";\n\n// Cookie constants\nexport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n} from \"@inai-dev/shared\";\n\n// Re-export types\nexport type {\n AuthObject,\n ServerAuthObject,\n ProtectedAuthObject,\n UserResource,\n SessionResource,\n OrganizationResource,\n InAIAuthConfig,\n InAIAuthErrorBody,\n SignInResult,\n SignUpResult,\n} from \"@inai-dev/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAeO;AAGP,oBAIO;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Re-export all React hooks, components, and providers\nexport {\n InAIAuthProvider,\n useAuth,\n useUser,\n useSession,\n useOrganization,\n useSignIn,\n useSignUp,\n Protect,\n SignedIn,\n SignedOut,\n PermissionGate,\n UserButton,\n SignIn,\n OrganizationSwitcher,\n} from \"@inai-dev/react\";\n\n// Cookie constants\nexport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n} from \"@inai-dev/shared\";\n\n// Re-export types\nexport type {\n AuthObject,\n ServerAuthObject,\n ProtectedAuthObject,\n UserResource,\n PlatformUserResource,\n SessionResource,\n OrganizationResource,\n InAIAuthConfig,\n InAIAuthErrorBody,\n SignInResult,\n SignUpResult,\n} from \"@inai-dev/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAeO;AAGP,oBAIO;","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { InAIAuthProvider, OrganizationSwitcher, PermissionGate, Protect, SignIn, SignedIn, SignedOut, UserButton, useAuth, useOrganization, useSession, useSignIn, useSignUp, useUser } from '@inai-dev/react';
2
2
  export { COOKIE_AUTH_SESSION, COOKIE_AUTH_TOKEN, COOKIE_REFRESH_TOKEN } from '@inai-dev/shared';
3
- export { AuthObject, InAIAuthConfig, InAIAuthErrorBody, OrganizationResource, ProtectedAuthObject, ServerAuthObject, SessionResource, SignInResult, SignUpResult, UserResource } from '@inai-dev/types';
3
+ export { AuthObject, InAIAuthConfig, InAIAuthErrorBody, OrganizationResource, PlatformUserResource, ProtectedAuthObject, ServerAuthObject, SessionResource, SignInResult, SignUpResult, UserResource } from '@inai-dev/types';
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { InAIAuthProvider, OrganizationSwitcher, PermissionGate, Protect, SignIn, SignedIn, SignedOut, UserButton, useAuth, useOrganization, useSession, useSignIn, useSignUp, useUser } from '@inai-dev/react';
2
2
  export { COOKIE_AUTH_SESSION, COOKIE_AUTH_TOKEN, COOKIE_REFRESH_TOKEN } from '@inai-dev/shared';
3
- export { AuthObject, InAIAuthConfig, InAIAuthErrorBody, OrganizationResource, ProtectedAuthObject, ServerAuthObject, SessionResource, SignInResult, SignUpResult, UserResource } from '@inai-dev/types';
3
+ export { AuthObject, InAIAuthConfig, InAIAuthErrorBody, OrganizationResource, PlatformUserResource, ProtectedAuthObject, ServerAuthObject, SessionResource, SignInResult, SignUpResult, UserResource } from '@inai-dev/types';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Re-export all React hooks, components, and providers\nexport {\n InAIAuthProvider,\n useAuth,\n useUser,\n useSession,\n useOrganization,\n useSignIn,\n useSignUp,\n Protect,\n SignedIn,\n SignedOut,\n PermissionGate,\n UserButton,\n SignIn,\n OrganizationSwitcher,\n} from \"@inai-dev/react\";\n\n// Cookie constants\nexport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n} from \"@inai-dev/shared\";\n\n// Re-export types\nexport type {\n AuthObject,\n ServerAuthObject,\n ProtectedAuthObject,\n UserResource,\n SessionResource,\n OrganizationResource,\n InAIAuthConfig,\n InAIAuthErrorBody,\n SignInResult,\n SignUpResult,\n} from \"@inai-dev/types\";\n"],"mappings":";;;AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Re-export all React hooks, components, and providers\nexport {\n InAIAuthProvider,\n useAuth,\n useUser,\n useSession,\n useOrganization,\n useSignIn,\n useSignUp,\n Protect,\n SignedIn,\n SignedOut,\n PermissionGate,\n UserButton,\n SignIn,\n OrganizationSwitcher,\n} from \"@inai-dev/react\";\n\n// Cookie constants\nexport {\n COOKIE_AUTH_TOKEN,\n COOKIE_REFRESH_TOKEN,\n COOKIE_AUTH_SESSION,\n} from \"@inai-dev/shared\";\n\n// Re-export types\nexport type {\n AuthObject,\n ServerAuthObject,\n ProtectedAuthObject,\n UserResource,\n PlatformUserResource,\n SessionResource,\n OrganizationResource,\n InAIAuthConfig,\n InAIAuthErrorBody,\n SignInResult,\n SignUpResult,\n} from \"@inai-dev/types\";\n"],"mappings":";;;AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
@@ -75,28 +75,73 @@ function buildAuthObject(token, claims) {
75
75
  }
76
76
  };
77
77
  }
78
- async function runAuthCheck(req, signInUrl) {
78
+ async function runAuthCheck(req, signInUrl, apiUrl) {
79
79
  const { pathname } = req.nextUrl;
80
80
  const token = req.cookies.get(import_shared.COOKIE_AUTH_TOKEN)?.value;
81
81
  if (!token || (0, import_shared.isTokenExpired)(token)) {
82
82
  const refreshToken = req.cookies.get(import_shared.COOKIE_REFRESH_TOKEN)?.value;
83
83
  if (refreshToken) {
84
84
  try {
85
- const refreshUrl = new URL("/api/auth/refresh", req.url);
86
- const refreshRes = await fetch(refreshUrl.toString(), {
87
- method: "POST",
88
- headers: {
89
- "Content-Type": "application/json",
90
- Cookie: req.headers.get("cookie") ?? ""
85
+ if (apiUrl) {
86
+ const refreshRes = await fetch(`${apiUrl}/api/platform/auth/refresh`, {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/json" },
89
+ body: JSON.stringify({ refresh_token: refreshToken })
90
+ });
91
+ if (refreshRes.ok) {
92
+ const newTokens = await refreshRes.json();
93
+ const meRes = await fetch(`${apiUrl}/api/platform/auth/me`, {
94
+ headers: { Authorization: `Bearer ${newTokens.access_token}` }
95
+ });
96
+ if (meRes.ok) {
97
+ const meData = await meRes.json();
98
+ const newUser = meData.data ?? meData;
99
+ const isProduction = process.env.NODE_ENV === "production";
100
+ const response2 = import_server.NextResponse.next();
101
+ response2.cookies.set(import_shared.COOKIE_AUTH_TOKEN, newTokens.access_token, {
102
+ httpOnly: true,
103
+ secure: isProduction,
104
+ sameSite: "lax",
105
+ path: "/",
106
+ maxAge: newTokens.expires_in
107
+ });
108
+ response2.cookies.set(import_shared.COOKIE_REFRESH_TOKEN, newTokens.refresh_token, {
109
+ httpOnly: true,
110
+ secure: isProduction,
111
+ sameSite: "strict",
112
+ path: "/api/auth",
113
+ maxAge: 7 * 24 * 60 * 60
114
+ });
115
+ response2.cookies.set(import_shared.COOKIE_AUTH_SESSION, JSON.stringify({
116
+ user: newUser,
117
+ expiresAt: new Date(Date.now() + newTokens.expires_in * 1e3).toISOString()
118
+ }), {
119
+ httpOnly: false,
120
+ secure: isProduction,
121
+ sameSite: "lax",
122
+ path: "/",
123
+ maxAge: newTokens.expires_in
124
+ });
125
+ return { authObj: null, response: response2 };
126
+ }
91
127
  }
92
- });
93
- if (refreshRes.ok) {
94
- const response2 = import_server.NextResponse.next();
95
- const setCookies = refreshRes.headers.getSetCookie?.() ?? [];
96
- for (const cookie of setCookies) {
97
- response2.headers.append("Set-Cookie", cookie);
128
+ } else {
129
+ const refreshUrl = new URL("/api/auth/refresh", req.url);
130
+ const refreshRes = await fetch(refreshUrl.toString(), {
131
+ method: "POST",
132
+ headers: {
133
+ "Content-Type": "application/json",
134
+ Cookie: req.headers.get("cookie") ?? ""
135
+ }
136
+ });
137
+ if (refreshRes.ok) {
138
+ const response2 = import_server.NextResponse.next();
139
+ const setCookies = refreshRes.headers.getSetCookie?.() ?? [];
140
+ for (const cookie of setCookies) {
141
+ response2.headers.append("Set-Cookie", cookie);
142
+ }
143
+ return { authObj: null, response: response2 };
98
144
  }
99
- return { authObj: null, response: response2 };
100
145
  }
101
146
  } catch {
102
147
  }
@@ -126,6 +171,7 @@ async function runAuthCheck(req, signInUrl) {
126
171
  }
127
172
  function inaiAuthMiddleware(config = {}) {
128
173
  const {
174
+ apiUrl = import_shared.DEFAULT_API_URL,
129
175
  publicRoutes = [],
130
176
  signInUrl = "/login",
131
177
  beforeAuth,
@@ -140,7 +186,7 @@ function inaiAuthMiddleware(config = {}) {
140
186
  if (isPublicRoute(req, publicRoutes, builtinPublic)) {
141
187
  return import_server.NextResponse.next();
142
188
  }
143
- const { authObj, response } = await runAuthCheck(req, signInUrl);
189
+ const { authObj, response } = await runAuthCheck(req, signInUrl, apiUrl);
144
190
  if (response) return response;
145
191
  if (!authObj)
146
192
  return import_server.NextResponse.redirect(new URL(signInUrl, req.url));
@@ -153,6 +199,7 @@ function inaiAuthMiddleware(config = {}) {
153
199
  }
154
200
  function withInAIAuth(wrappedMiddleware, config = {}) {
155
201
  const {
202
+ apiUrl = import_shared.DEFAULT_API_URL,
156
203
  publicRoutes = [],
157
204
  signInUrl = "/login",
158
205
  beforeAuth,
@@ -166,7 +213,7 @@ function withInAIAuth(wrappedMiddleware, config = {}) {
166
213
  }
167
214
  const isPublic = isPublicRoute(req, publicRoutes, builtinPublic);
168
215
  if (!isPublic) {
169
- const { authObj, response } = await runAuthCheck(req, signInUrl);
216
+ const { authObj, response } = await runAuthCheck(req, signInUrl, apiUrl);
170
217
  if (response) return response;
171
218
  if (!authObj)
172
219
  return import_server.NextResponse.redirect(new URL(signInUrl, req.url));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\nimport type { AuthObject } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_AUTH_SESSION,\n COOKIE_REFRESH_TOKEN,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\nexport interface InAIMiddlewareConfig {\n publicRoutes?: string[] | ((req: NextRequest) => boolean);\n signInUrl?: string;\n beforeAuth?: (req: NextRequest) => NextResponse | void;\n afterAuth?: (auth: AuthObject, req: NextRequest) => NextResponse | void;\n}\n\nexport function createRouteMatcher(\n patterns: (string | RegExp)[],\n): (req: NextRequest) => boolean {\n const matchers = patterns.map((pattern) => {\n if (pattern instanceof RegExp) return pattern;\n let regexStr = pattern;\n if (regexStr.endsWith(\"*\") && !regexStr.includes(\"(\")) {\n regexStr = regexStr.slice(0, -1) + \".*\";\n }\n return new RegExp(`^${regexStr}$`);\n });\n\n return (req: NextRequest) => {\n const pathname = req.nextUrl.pathname;\n return matchers.some((m) => m.test(pathname));\n };\n}\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 req: NextRequest,\n publicRoutes: string[] | ((req: NextRequest) => boolean),\n builtinPublic: string[],\n): boolean {\n const pathname = req.nextUrl.pathname;\n if (matchesRoute(pathname, builtinPublic)) return true;\n if (typeof publicRoutes === \"function\") return publicRoutes(req);\n return matchesRoute(pathname, publicRoutes);\n}\n\nfunction buildAuthObject(\n token: string,\n claims: NonNullable<ReturnType<typeof getClaimsFromToken>>,\n): AuthObject {\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\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\nasync function runAuthCheck(\n req: NextRequest,\n signInUrl: string,\n): Promise<{ authObj: AuthObject | null; response?: NextResponse }> {\n const { pathname } = req.nextUrl;\n const token = req.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = req.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n if (refreshToken) {\n try {\n const refreshUrl = new URL(\"/api/auth/refresh\", req.url);\n const refreshRes = await fetch(refreshUrl.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Cookie: req.headers.get(\"cookie\") ?? \"\",\n },\n });\n if (refreshRes.ok) {\n const response = NextResponse.next();\n const setCookies = refreshRes.headers.getSetCookie?.() ?? [];\n for (const cookie of setCookies) {\n response.headers.append(\"Set-Cookie\", cookie);\n }\n return { authObj: null, response };\n }\n } catch {\n // Refresh failed, fall through to redirect\n }\n }\n\n const response = NextResponse.redirect(\n new URL(\n `${signInUrl}?returnTo=${encodeURIComponent(pathname)}`,\n req.url,\n ),\n );\n response.cookies.set(COOKIE_AUTH_TOKEN, \"\", { path: \"/\", maxAge: 0 });\n response.cookies.set(COOKIE_REFRESH_TOKEN, \"\", {\n path: \"/api/auth\",\n maxAge: 0,\n });\n response.cookies.set(COOKIE_AUTH_SESSION, \"\", { path: \"/\", maxAge: 0 });\n return { authObj: null, response };\n }\n\n const claims = getClaimsFromToken(token);\n if (!claims) {\n return {\n authObj: null,\n response: NextResponse.redirect(new URL(signInUrl, req.url)),\n };\n }\n\n return { authObj: buildAuthObject(token, claims) };\n}\n\nexport function inaiAuthMiddleware(config: InAIMiddlewareConfig = {}) {\n const {\n publicRoutes = [],\n signInUrl = \"/login\",\n beforeAuth,\n afterAuth,\n } = config;\n\n const builtinPublic = [\"/_next/*\", \"/favicon.ico\", \"/api/*\", signInUrl];\n\n return async function middleware(\n req: NextRequest,\n ): Promise<NextResponse> {\n if (beforeAuth) {\n const result = beforeAuth(req);\n if (result) return result;\n }\n\n if (isPublicRoute(req, publicRoutes, builtinPublic)) {\n return NextResponse.next();\n }\n\n const { authObj, response } = await runAuthCheck(req, signInUrl);\n if (response) return response;\n if (!authObj)\n return NextResponse.redirect(new URL(signInUrl, req.url));\n\n if (afterAuth) {\n const result = afterAuth(authObj, req);\n if (result) return result;\n }\n\n return NextResponse.next();\n };\n}\n\nexport function withInAIAuth(\n wrappedMiddleware: (\n req: NextRequest,\n ) => NextResponse | Response | Promise<NextResponse | Response>,\n config: InAIMiddlewareConfig = {},\n): (req: NextRequest) => Promise<NextResponse> {\n const {\n publicRoutes = [],\n signInUrl = \"/login\",\n beforeAuth,\n afterAuth,\n } = config;\n\n const builtinPublic = [\"/_next/*\", \"/favicon.ico\", \"/api/*\", signInUrl];\n\n return async function middleware(\n req: NextRequest,\n ): Promise<NextResponse> {\n if (beforeAuth) {\n const result = beforeAuth(req);\n if (result) return result;\n }\n\n const isPublic = isPublicRoute(req, publicRoutes, builtinPublic);\n\n if (!isPublic) {\n const { authObj, response } = await runAuthCheck(req, signInUrl);\n if (response) return response;\n if (!authObj)\n return NextResponse.redirect(new URL(signInUrl, req.url));\n\n if (afterAuth) {\n const result = afterAuth(authObj, req);\n if (result) return result;\n }\n\n const authHeader = JSON.stringify({\n userId: authObj.userId,\n tenantId: authObj.tenantId,\n appId: authObj.appId,\n envId: authObj.envId,\n orgId: authObj.orgId,\n orgRole: authObj.orgRole,\n });\n req.headers.set(\"x-inai-auth\", authHeader);\n }\n\n const wrappedResponse = await wrappedMiddleware(req);\n if (wrappedResponse instanceof NextResponse) return wrappedResponse;\n return new NextResponse(wrappedResponse.body, wrappedResponse);\n };\n}\n"],"mappings":"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","names":["response"]}
1
+ {"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\nimport type { AuthObject } from \"@inai-dev/types\";\nimport {\n COOKIE_AUTH_TOKEN,\n COOKIE_AUTH_SESSION,\n COOKIE_REFRESH_TOKEN,\n DEFAULT_API_URL,\n getClaimsFromToken,\n isTokenExpired,\n} from \"@inai-dev/shared\";\n\nexport interface InAIMiddlewareConfig {\n apiUrl?: string;\n publicRoutes?: string[] | ((req: NextRequest) => boolean);\n signInUrl?: string;\n beforeAuth?: (req: NextRequest) => NextResponse | void;\n afterAuth?: (auth: AuthObject, req: NextRequest) => NextResponse | void;\n}\n\nexport function createRouteMatcher(\n patterns: (string | RegExp)[],\n): (req: NextRequest) => boolean {\n const matchers = patterns.map((pattern) => {\n if (pattern instanceof RegExp) return pattern;\n let regexStr = pattern;\n if (regexStr.endsWith(\"*\") && !regexStr.includes(\"(\")) {\n regexStr = regexStr.slice(0, -1) + \".*\";\n }\n return new RegExp(`^${regexStr}$`);\n });\n\n return (req: NextRequest) => {\n const pathname = req.nextUrl.pathname;\n return matchers.some((m) => m.test(pathname));\n };\n}\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 req: NextRequest,\n publicRoutes: string[] | ((req: NextRequest) => boolean),\n builtinPublic: string[],\n): boolean {\n const pathname = req.nextUrl.pathname;\n if (matchesRoute(pathname, builtinPublic)) return true;\n if (typeof publicRoutes === \"function\") return publicRoutes(req);\n return matchesRoute(pathname, publicRoutes);\n}\n\nfunction buildAuthObject(\n token: string,\n claims: NonNullable<ReturnType<typeof getClaimsFromToken>>,\n): AuthObject {\n const roles = claims.roles ?? [];\n const permissions = claims.permissions ?? [];\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\nasync function runAuthCheck(\n req: NextRequest,\n signInUrl: string,\n apiUrl?: string,\n): Promise<{ authObj: AuthObject | null; response?: NextResponse }> {\n const { pathname } = req.nextUrl;\n const token = req.cookies.get(COOKIE_AUTH_TOKEN)?.value;\n\n if (!token || isTokenExpired(token)) {\n const refreshToken = req.cookies.get(COOKIE_REFRESH_TOKEN)?.value;\n if (refreshToken) {\n try {\n if (apiUrl) {\n const refreshRes = await fetch(`${apiUrl}/api/platform/auth/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refresh_token: refreshToken }),\n });\n if (refreshRes.ok) {\n const newTokens = await refreshRes.json() as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n const meRes = await fetch(`${apiUrl}/api/platform/auth/me`, {\n headers: { Authorization: `Bearer ${newTokens.access_token}` },\n });\n if (meRes.ok) {\n const meData = await meRes.json();\n const newUser = meData.data ?? meData;\n const isProduction = process.env.NODE_ENV === \"production\";\n const response = NextResponse.next();\n response.cookies.set(COOKIE_AUTH_TOKEN, newTokens.access_token, {\n httpOnly: true, secure: isProduction, sameSite: \"lax\",\n path: \"/\", maxAge: newTokens.expires_in,\n });\n response.cookies.set(COOKIE_REFRESH_TOKEN, newTokens.refresh_token, {\n httpOnly: true, secure: isProduction, sameSite: \"strict\",\n path: \"/api/auth\", maxAge: 7 * 24 * 60 * 60,\n });\n response.cookies.set(COOKIE_AUTH_SESSION, JSON.stringify({\n user: newUser,\n expiresAt: new Date(Date.now() + newTokens.expires_in * 1000).toISOString(),\n }), {\n httpOnly: false, secure: isProduction, sameSite: \"lax\",\n path: \"/\", maxAge: newTokens.expires_in,\n });\n return { authObj: null, response };\n }\n }\n } else {\n const refreshUrl = new URL(\"/api/auth/refresh\", req.url);\n const refreshRes = await fetch(refreshUrl.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Cookie: req.headers.get(\"cookie\") ?? \"\",\n },\n });\n if (refreshRes.ok) {\n const response = NextResponse.next();\n const setCookies = refreshRes.headers.getSetCookie?.() ?? [];\n for (const cookie of setCookies) {\n response.headers.append(\"Set-Cookie\", cookie);\n }\n return { authObj: null, response };\n }\n }\n } catch {\n // Refresh failed, fall through to redirect\n }\n }\n\n const response = NextResponse.redirect(\n new URL(\n `${signInUrl}?returnTo=${encodeURIComponent(pathname)}`,\n req.url,\n ),\n );\n response.cookies.set(COOKIE_AUTH_TOKEN, \"\", { path: \"/\", maxAge: 0 });\n response.cookies.set(COOKIE_REFRESH_TOKEN, \"\", {\n path: \"/api/auth\",\n maxAge: 0,\n });\n response.cookies.set(COOKIE_AUTH_SESSION, \"\", { path: \"/\", maxAge: 0 });\n return { authObj: null, response };\n }\n\n const claims = getClaimsFromToken(token);\n if (!claims) {\n return {\n authObj: null,\n response: NextResponse.redirect(new URL(signInUrl, req.url)),\n };\n }\n\n return { authObj: buildAuthObject(token, claims) };\n}\n\nexport function inaiAuthMiddleware(config: InAIMiddlewareConfig = {}) {\n const {\n apiUrl = DEFAULT_API_URL,\n publicRoutes = [],\n signInUrl = \"/login\",\n beforeAuth,\n afterAuth,\n } = config;\n\n const builtinPublic = [\"/_next/*\", \"/favicon.ico\", \"/api/*\", signInUrl];\n\n return async function middleware(\n req: NextRequest,\n ): Promise<NextResponse> {\n if (beforeAuth) {\n const result = beforeAuth(req);\n if (result) return result;\n }\n\n if (isPublicRoute(req, publicRoutes, builtinPublic)) {\n return NextResponse.next();\n }\n\n const { authObj, response } = await runAuthCheck(req, signInUrl, apiUrl);\n if (response) return response;\n if (!authObj)\n return NextResponse.redirect(new URL(signInUrl, req.url));\n\n if (afterAuth) {\n const result = afterAuth(authObj, req);\n if (result) return result;\n }\n\n return NextResponse.next();\n };\n}\n\nexport function withInAIAuth(\n wrappedMiddleware: (\n req: NextRequest,\n ) => NextResponse | Response | Promise<NextResponse | Response>,\n config: InAIMiddlewareConfig = {},\n): (req: NextRequest) => Promise<NextResponse> {\n const {\n apiUrl = DEFAULT_API_URL,\n publicRoutes = [],\n signInUrl = \"/login\",\n beforeAuth,\n afterAuth,\n } = config;\n\n const builtinPublic = [\"/_next/*\", \"/favicon.ico\", \"/api/*\", signInUrl];\n\n return async function middleware(\n req: NextRequest,\n ): Promise<NextResponse> {\n if (beforeAuth) {\n const result = beforeAuth(req);\n if (result) return result;\n }\n\n const isPublic = isPublicRoute(req, publicRoutes, builtinPublic);\n\n if (!isPublic) {\n const { authObj, response } = await runAuthCheck(req, signInUrl, apiUrl);\n if (response) return response;\n if (!authObj)\n return NextResponse.redirect(new URL(signInUrl, req.url));\n\n if (afterAuth) {\n const result = afterAuth(authObj, req);\n if (result) return result;\n }\n\n const authHeader = JSON.stringify({\n userId: authObj.userId,\n tenantId: authObj.tenantId,\n appId: authObj.appId,\n envId: authObj.envId,\n orgId: authObj.orgId,\n orgRole: authObj.orgRole,\n });\n req.headers.set(\"x-inai-auth\", authHeader);\n }\n\n const wrappedResponse = await wrappedMiddleware(req);\n if (wrappedResponse instanceof NextResponse) return wrappedResponse;\n return new NextResponse(wrappedResponse.body, wrappedResponse);\n };\n}\n"],"mappings":"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","names":["response"]}
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
2
2
  import { AuthObject } from '@inai-dev/types';
3
3
 
4
4
  interface InAIMiddlewareConfig {
5
+ apiUrl?: string;
5
6
  publicRoutes?: string[] | ((req: NextRequest) => boolean);
6
7
  signInUrl?: string;
7
8
  beforeAuth?: (req: NextRequest) => NextResponse | void;
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
2
2
  import { AuthObject } from '@inai-dev/types';
3
3
 
4
4
  interface InAIMiddlewareConfig {
5
+ apiUrl?: string;
5
6
  publicRoutes?: string[] | ((req: NextRequest) => boolean);
6
7
  signInUrl?: string;
7
8
  beforeAuth?: (req: NextRequest) => NextResponse | void;