@inai-dev/nextjs 0.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 +80 -0
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.cjs +198 -0
- package/dist/middleware.cjs.map +1 -0
- package/dist/middleware.d.cts +14 -0
- package/dist/middleware.d.ts +14 -0
- package/dist/middleware.js +177 -0
- package/dist/middleware.js.map +1 -0
- package/dist/server.cjs +528 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +81 -0
- package/dist/server.d.ts +81 -0
- package/dist/server.js +511 -0
- package/dist/server.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @inai-dev/nextjs
|
|
2
|
+
|
|
3
|
+
Full Next.js integration for InAI Auth. Includes middleware, server-side helpers, API route handlers, and re-exports all React hooks/components.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @inai-dev/nextjs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Environment Variables
|
|
14
|
+
|
|
15
|
+
```env
|
|
16
|
+
NEXT_PUBLIC_INAI_PUBLISHABLE_KEY=pk_live_...
|
|
17
|
+
INAI_SECRET_KEY=sk_live_...
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. Middleware
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// middleware.ts
|
|
24
|
+
import { authMiddleware } from "@inai-dev/nextjs/middleware";
|
|
25
|
+
|
|
26
|
+
export default authMiddleware({
|
|
27
|
+
publishableKey: process.env.NEXT_PUBLIC_INAI_PUBLISHABLE_KEY!,
|
|
28
|
+
publicRoutes: ["/", "/about", "/sign-in"],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const config = { matcher: ["/((?!_next|static|favicon.ico).*)"] };
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. Provider
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// app/layout.tsx
|
|
38
|
+
import { InAIProvider } from "@inai-dev/nextjs";
|
|
39
|
+
|
|
40
|
+
export default function RootLayout({ children }) {
|
|
41
|
+
return <InAIProvider>{children}</InAIProvider>;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 4. Server-Side Auth
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// app/dashboard/page.tsx
|
|
49
|
+
import { auth } from "@inai-dev/nextjs/server";
|
|
50
|
+
|
|
51
|
+
export default async function Dashboard() {
|
|
52
|
+
const session = await auth();
|
|
53
|
+
if (!session) redirect("/sign-in");
|
|
54
|
+
return <p>Welcome {session.user.email}</p>;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 5. API Route Handlers
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// app/api/auth/[...inai]/route.ts
|
|
62
|
+
import { handleAuthRoutes } from "@inai-dev/nextjs";
|
|
63
|
+
export const { GET, POST } = handleAuthRoutes();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Exports
|
|
67
|
+
|
|
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()`
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
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)
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
[MIT](../../LICENSE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var src_exports = {};
|
|
23
|
+
__export(src_exports, {
|
|
24
|
+
COOKIE_AUTH_SESSION: () => import_shared.COOKIE_AUTH_SESSION,
|
|
25
|
+
COOKIE_AUTH_TOKEN: () => import_shared.COOKIE_AUTH_TOKEN,
|
|
26
|
+
COOKIE_REFRESH_TOKEN: () => import_shared.COOKIE_REFRESH_TOKEN,
|
|
27
|
+
InAIAuthProvider: () => import_react.InAIAuthProvider,
|
|
28
|
+
OrganizationSwitcher: () => import_react.OrganizationSwitcher,
|
|
29
|
+
PermissionGate: () => import_react.PermissionGate,
|
|
30
|
+
Protect: () => import_react.Protect,
|
|
31
|
+
SignIn: () => import_react.SignIn,
|
|
32
|
+
SignedIn: () => import_react.SignedIn,
|
|
33
|
+
SignedOut: () => import_react.SignedOut,
|
|
34
|
+
UserButton: () => import_react.UserButton,
|
|
35
|
+
useAuth: () => import_react.useAuth,
|
|
36
|
+
useOrganization: () => import_react.useOrganization,
|
|
37
|
+
useSession: () => import_react.useSession,
|
|
38
|
+
useSignIn: () => import_react.useSignIn,
|
|
39
|
+
useSignUp: () => import_react.useSignUp,
|
|
40
|
+
useUser: () => import_react.useUser
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(src_exports);
|
|
43
|
+
var import_react = require("@inai-dev/react");
|
|
44
|
+
var import_shared = require("@inai-dev/shared");
|
|
45
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
46
|
+
0 && (module.exports = {
|
|
47
|
+
COOKIE_AUTH_SESSION,
|
|
48
|
+
COOKIE_AUTH_TOKEN,
|
|
49
|
+
COOKIE_REFRESH_TOKEN,
|
|
50
|
+
InAIAuthProvider,
|
|
51
|
+
OrganizationSwitcher,
|
|
52
|
+
PermissionGate,
|
|
53
|
+
Protect,
|
|
54
|
+
SignIn,
|
|
55
|
+
SignedIn,
|
|
56
|
+
SignedOut,
|
|
57
|
+
UserButton,
|
|
58
|
+
useAuth,
|
|
59
|
+
useOrganization,
|
|
60
|
+
useSession,
|
|
61
|
+
useSignIn,
|
|
62
|
+
useSignUp,
|
|
63
|
+
useUser
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { InAIAuthProvider, OrganizationSwitcher, PermissionGate, Protect, SignIn, SignedIn, SignedOut, UserButton, useAuth, useOrganization, useSession, useSignIn, useSignUp, useUser } from '@inai-dev/react';
|
|
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';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { InAIAuthProvider, OrganizationSwitcher, PermissionGate, Protect, SignIn, SignedIn, SignedOut, UserButton, useAuth, useOrganization, useSession, useSignIn, useSignUp, useUser } from '@inai-dev/react';
|
|
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';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import {
|
|
5
|
+
InAIAuthProvider,
|
|
6
|
+
useAuth,
|
|
7
|
+
useUser,
|
|
8
|
+
useSession,
|
|
9
|
+
useOrganization,
|
|
10
|
+
useSignIn,
|
|
11
|
+
useSignUp,
|
|
12
|
+
Protect,
|
|
13
|
+
SignedIn,
|
|
14
|
+
SignedOut,
|
|
15
|
+
PermissionGate,
|
|
16
|
+
UserButton,
|
|
17
|
+
SignIn,
|
|
18
|
+
OrganizationSwitcher
|
|
19
|
+
} from "@inai-dev/react";
|
|
20
|
+
import {
|
|
21
|
+
COOKIE_AUTH_TOKEN,
|
|
22
|
+
COOKIE_REFRESH_TOKEN,
|
|
23
|
+
COOKIE_AUTH_SESSION
|
|
24
|
+
} from "@inai-dev/shared";
|
|
25
|
+
export {
|
|
26
|
+
COOKIE_AUTH_SESSION,
|
|
27
|
+
COOKIE_AUTH_TOKEN,
|
|
28
|
+
COOKIE_REFRESH_TOKEN,
|
|
29
|
+
InAIAuthProvider,
|
|
30
|
+
OrganizationSwitcher,
|
|
31
|
+
PermissionGate,
|
|
32
|
+
Protect,
|
|
33
|
+
SignIn,
|
|
34
|
+
SignedIn,
|
|
35
|
+
SignedOut,
|
|
36
|
+
UserButton,
|
|
37
|
+
useAuth,
|
|
38
|
+
useOrganization,
|
|
39
|
+
useSession,
|
|
40
|
+
useSignIn,
|
|
41
|
+
useSignUp,
|
|
42
|
+
useUser
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/middleware.ts
|
|
21
|
+
var middleware_exports = {};
|
|
22
|
+
__export(middleware_exports, {
|
|
23
|
+
createRouteMatcher: () => createRouteMatcher,
|
|
24
|
+
inaiAuthMiddleware: () => inaiAuthMiddleware,
|
|
25
|
+
withInAIAuth: () => withInAIAuth
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(middleware_exports);
|
|
28
|
+
var import_server = require("next/server");
|
|
29
|
+
var import_shared = require("@inai-dev/shared");
|
|
30
|
+
function createRouteMatcher(patterns) {
|
|
31
|
+
const matchers = patterns.map((pattern) => {
|
|
32
|
+
if (pattern instanceof RegExp) return pattern;
|
|
33
|
+
let regexStr = pattern;
|
|
34
|
+
if (regexStr.endsWith("*") && !regexStr.includes("(")) {
|
|
35
|
+
regexStr = regexStr.slice(0, -1) + ".*";
|
|
36
|
+
}
|
|
37
|
+
return new RegExp(`^${regexStr}$`);
|
|
38
|
+
});
|
|
39
|
+
return (req) => {
|
|
40
|
+
const pathname = req.nextUrl.pathname;
|
|
41
|
+
return matchers.some((m) => m.test(pathname));
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function matchesRoute(pathname, patterns) {
|
|
45
|
+
return patterns.some((pattern) => {
|
|
46
|
+
if (pattern.endsWith("*")) {
|
|
47
|
+
return pathname.startsWith(pattern.slice(0, -1));
|
|
48
|
+
}
|
|
49
|
+
return pathname === pattern;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function isPublicRoute(req, publicRoutes, builtinPublic) {
|
|
53
|
+
const pathname = req.nextUrl.pathname;
|
|
54
|
+
if (matchesRoute(pathname, builtinPublic)) return true;
|
|
55
|
+
if (typeof publicRoutes === "function") return publicRoutes(req);
|
|
56
|
+
return matchesRoute(pathname, publicRoutes);
|
|
57
|
+
}
|
|
58
|
+
function buildAuthObject(token, claims) {
|
|
59
|
+
const roles = claims.roles ?? [];
|
|
60
|
+
const permissions = claims.permissions ?? [];
|
|
61
|
+
return {
|
|
62
|
+
userId: claims.sub,
|
|
63
|
+
tenantId: claims.tenant_id,
|
|
64
|
+
appId: claims.app_id ?? null,
|
|
65
|
+
envId: claims.env_id ?? null,
|
|
66
|
+
orgId: claims.org_id ?? null,
|
|
67
|
+
orgRole: claims.org_role ?? null,
|
|
68
|
+
sessionId: null,
|
|
69
|
+
getToken: async () => token,
|
|
70
|
+
has: (params) => {
|
|
71
|
+
if (params.role && roles.includes(params.role)) return true;
|
|
72
|
+
if (params.permission && permissions.includes(params.permission))
|
|
73
|
+
return true;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function runAuthCheck(req, signInUrl) {
|
|
79
|
+
const { pathname } = req.nextUrl;
|
|
80
|
+
const token = req.cookies.get(import_shared.COOKIE_AUTH_TOKEN)?.value;
|
|
81
|
+
if (!token || (0, import_shared.isTokenExpired)(token)) {
|
|
82
|
+
const refreshToken = req.cookies.get(import_shared.COOKIE_REFRESH_TOKEN)?.value;
|
|
83
|
+
if (refreshToken) {
|
|
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") ?? ""
|
|
91
|
+
}
|
|
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);
|
|
98
|
+
}
|
|
99
|
+
return { authObj: null, response: response2 };
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const response = import_server.NextResponse.redirect(
|
|
105
|
+
new URL(
|
|
106
|
+
`${signInUrl}?returnTo=${encodeURIComponent(pathname)}`,
|
|
107
|
+
req.url
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
response.cookies.set(import_shared.COOKIE_AUTH_TOKEN, "", { path: "/", maxAge: 0 });
|
|
111
|
+
response.cookies.set(import_shared.COOKIE_REFRESH_TOKEN, "", {
|
|
112
|
+
path: "/api/auth",
|
|
113
|
+
maxAge: 0
|
|
114
|
+
});
|
|
115
|
+
response.cookies.set(import_shared.COOKIE_AUTH_SESSION, "", { path: "/", maxAge: 0 });
|
|
116
|
+
return { authObj: null, response };
|
|
117
|
+
}
|
|
118
|
+
const claims = (0, import_shared.getClaimsFromToken)(token);
|
|
119
|
+
if (!claims) {
|
|
120
|
+
return {
|
|
121
|
+
authObj: null,
|
|
122
|
+
response: import_server.NextResponse.redirect(new URL(signInUrl, req.url))
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return { authObj: buildAuthObject(token, claims) };
|
|
126
|
+
}
|
|
127
|
+
function inaiAuthMiddleware(config = {}) {
|
|
128
|
+
const {
|
|
129
|
+
publicRoutes = [],
|
|
130
|
+
signInUrl = "/login",
|
|
131
|
+
beforeAuth,
|
|
132
|
+
afterAuth
|
|
133
|
+
} = config;
|
|
134
|
+
const builtinPublic = ["/_next/*", "/favicon.ico", "/api/*", signInUrl];
|
|
135
|
+
return async function middleware(req) {
|
|
136
|
+
if (beforeAuth) {
|
|
137
|
+
const result = beforeAuth(req);
|
|
138
|
+
if (result) return result;
|
|
139
|
+
}
|
|
140
|
+
if (isPublicRoute(req, publicRoutes, builtinPublic)) {
|
|
141
|
+
return import_server.NextResponse.next();
|
|
142
|
+
}
|
|
143
|
+
const { authObj, response } = await runAuthCheck(req, signInUrl);
|
|
144
|
+
if (response) return response;
|
|
145
|
+
if (!authObj)
|
|
146
|
+
return import_server.NextResponse.redirect(new URL(signInUrl, req.url));
|
|
147
|
+
if (afterAuth) {
|
|
148
|
+
const result = afterAuth(authObj, req);
|
|
149
|
+
if (result) return result;
|
|
150
|
+
}
|
|
151
|
+
return import_server.NextResponse.next();
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function withInAIAuth(wrappedMiddleware, config = {}) {
|
|
155
|
+
const {
|
|
156
|
+
publicRoutes = [],
|
|
157
|
+
signInUrl = "/login",
|
|
158
|
+
beforeAuth,
|
|
159
|
+
afterAuth
|
|
160
|
+
} = config;
|
|
161
|
+
const builtinPublic = ["/_next/*", "/favicon.ico", "/api/*", signInUrl];
|
|
162
|
+
return async function middleware(req) {
|
|
163
|
+
if (beforeAuth) {
|
|
164
|
+
const result = beforeAuth(req);
|
|
165
|
+
if (result) return result;
|
|
166
|
+
}
|
|
167
|
+
const isPublic = isPublicRoute(req, publicRoutes, builtinPublic);
|
|
168
|
+
if (!isPublic) {
|
|
169
|
+
const { authObj, response } = await runAuthCheck(req, signInUrl);
|
|
170
|
+
if (response) return response;
|
|
171
|
+
if (!authObj)
|
|
172
|
+
return import_server.NextResponse.redirect(new URL(signInUrl, req.url));
|
|
173
|
+
if (afterAuth) {
|
|
174
|
+
const result = afterAuth(authObj, req);
|
|
175
|
+
if (result) return result;
|
|
176
|
+
}
|
|
177
|
+
const authHeader = JSON.stringify({
|
|
178
|
+
userId: authObj.userId,
|
|
179
|
+
tenantId: authObj.tenantId,
|
|
180
|
+
appId: authObj.appId,
|
|
181
|
+
envId: authObj.envId,
|
|
182
|
+
orgId: authObj.orgId,
|
|
183
|
+
orgRole: authObj.orgRole
|
|
184
|
+
});
|
|
185
|
+
req.headers.set("x-inai-auth", authHeader);
|
|
186
|
+
}
|
|
187
|
+
const wrappedResponse = await wrappedMiddleware(req);
|
|
188
|
+
if (wrappedResponse instanceof import_server.NextResponse) return wrappedResponse;
|
|
189
|
+
return new import_server.NextResponse(wrappedResponse.body, wrappedResponse);
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
193
|
+
0 && (module.exports = {
|
|
194
|
+
createRouteMatcher,
|
|
195
|
+
inaiAuthMiddleware,
|
|
196
|
+
withInAIAuth
|
|
197
|
+
});
|
|
198
|
+
//# sourceMappingURL=middleware.cjs.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { AuthObject } from '@inai-dev/types';
|
|
3
|
+
|
|
4
|
+
interface InAIMiddlewareConfig {
|
|
5
|
+
publicRoutes?: string[] | ((req: NextRequest) => boolean);
|
|
6
|
+
signInUrl?: string;
|
|
7
|
+
beforeAuth?: (req: NextRequest) => NextResponse | void;
|
|
8
|
+
afterAuth?: (auth: AuthObject, req: NextRequest) => NextResponse | void;
|
|
9
|
+
}
|
|
10
|
+
declare function createRouteMatcher(patterns: (string | RegExp)[]): (req: NextRequest) => boolean;
|
|
11
|
+
declare function inaiAuthMiddleware(config?: InAIMiddlewareConfig): (req: NextRequest) => Promise<NextResponse>;
|
|
12
|
+
declare function withInAIAuth(wrappedMiddleware: (req: NextRequest) => NextResponse | Response | Promise<NextResponse | Response>, config?: InAIMiddlewareConfig): (req: NextRequest) => Promise<NextResponse>;
|
|
13
|
+
|
|
14
|
+
export { type InAIMiddlewareConfig, createRouteMatcher, inaiAuthMiddleware, withInAIAuth };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { AuthObject } from '@inai-dev/types';
|
|
3
|
+
|
|
4
|
+
interface InAIMiddlewareConfig {
|
|
5
|
+
publicRoutes?: string[] | ((req: NextRequest) => boolean);
|
|
6
|
+
signInUrl?: string;
|
|
7
|
+
beforeAuth?: (req: NextRequest) => NextResponse | void;
|
|
8
|
+
afterAuth?: (auth: AuthObject, req: NextRequest) => NextResponse | void;
|
|
9
|
+
}
|
|
10
|
+
declare function createRouteMatcher(patterns: (string | RegExp)[]): (req: NextRequest) => boolean;
|
|
11
|
+
declare function inaiAuthMiddleware(config?: InAIMiddlewareConfig): (req: NextRequest) => Promise<NextResponse>;
|
|
12
|
+
declare function withInAIAuth(wrappedMiddleware: (req: NextRequest) => NextResponse | Response | Promise<NextResponse | Response>, config?: InAIMiddlewareConfig): (req: NextRequest) => Promise<NextResponse>;
|
|
13
|
+
|
|
14
|
+
export { type InAIMiddlewareConfig, createRouteMatcher, inaiAuthMiddleware, withInAIAuth };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/middleware.ts
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import {
|
|
4
|
+
COOKIE_AUTH_TOKEN,
|
|
5
|
+
COOKIE_AUTH_SESSION,
|
|
6
|
+
COOKIE_REFRESH_TOKEN,
|
|
7
|
+
getClaimsFromToken,
|
|
8
|
+
isTokenExpired
|
|
9
|
+
} from "@inai-dev/shared";
|
|
10
|
+
function createRouteMatcher(patterns) {
|
|
11
|
+
const matchers = patterns.map((pattern) => {
|
|
12
|
+
if (pattern instanceof RegExp) return pattern;
|
|
13
|
+
let regexStr = pattern;
|
|
14
|
+
if (regexStr.endsWith("*") && !regexStr.includes("(")) {
|
|
15
|
+
regexStr = regexStr.slice(0, -1) + ".*";
|
|
16
|
+
}
|
|
17
|
+
return new RegExp(`^${regexStr}$`);
|
|
18
|
+
});
|
|
19
|
+
return (req) => {
|
|
20
|
+
const pathname = req.nextUrl.pathname;
|
|
21
|
+
return matchers.some((m) => m.test(pathname));
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function matchesRoute(pathname, patterns) {
|
|
25
|
+
return patterns.some((pattern) => {
|
|
26
|
+
if (pattern.endsWith("*")) {
|
|
27
|
+
return pathname.startsWith(pattern.slice(0, -1));
|
|
28
|
+
}
|
|
29
|
+
return pathname === pattern;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function isPublicRoute(req, publicRoutes, builtinPublic) {
|
|
33
|
+
const pathname = req.nextUrl.pathname;
|
|
34
|
+
if (matchesRoute(pathname, builtinPublic)) return true;
|
|
35
|
+
if (typeof publicRoutes === "function") return publicRoutes(req);
|
|
36
|
+
return matchesRoute(pathname, publicRoutes);
|
|
37
|
+
}
|
|
38
|
+
function buildAuthObject(token, claims) {
|
|
39
|
+
const roles = claims.roles ?? [];
|
|
40
|
+
const permissions = claims.permissions ?? [];
|
|
41
|
+
return {
|
|
42
|
+
userId: claims.sub,
|
|
43
|
+
tenantId: claims.tenant_id,
|
|
44
|
+
appId: claims.app_id ?? null,
|
|
45
|
+
envId: claims.env_id ?? null,
|
|
46
|
+
orgId: claims.org_id ?? null,
|
|
47
|
+
orgRole: claims.org_role ?? null,
|
|
48
|
+
sessionId: null,
|
|
49
|
+
getToken: async () => token,
|
|
50
|
+
has: (params) => {
|
|
51
|
+
if (params.role && roles.includes(params.role)) return true;
|
|
52
|
+
if (params.permission && permissions.includes(params.permission))
|
|
53
|
+
return true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function runAuthCheck(req, signInUrl) {
|
|
59
|
+
const { pathname } = req.nextUrl;
|
|
60
|
+
const token = req.cookies.get(COOKIE_AUTH_TOKEN)?.value;
|
|
61
|
+
if (!token || isTokenExpired(token)) {
|
|
62
|
+
const refreshToken = req.cookies.get(COOKIE_REFRESH_TOKEN)?.value;
|
|
63
|
+
if (refreshToken) {
|
|
64
|
+
try {
|
|
65
|
+
const refreshUrl = new URL("/api/auth/refresh", req.url);
|
|
66
|
+
const refreshRes = await fetch(refreshUrl.toString(), {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
Cookie: req.headers.get("cookie") ?? ""
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
if (refreshRes.ok) {
|
|
74
|
+
const response2 = NextResponse.next();
|
|
75
|
+
const setCookies = refreshRes.headers.getSetCookie?.() ?? [];
|
|
76
|
+
for (const cookie of setCookies) {
|
|
77
|
+
response2.headers.append("Set-Cookie", cookie);
|
|
78
|
+
}
|
|
79
|
+
return { authObj: null, response: response2 };
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const response = NextResponse.redirect(
|
|
85
|
+
new URL(
|
|
86
|
+
`${signInUrl}?returnTo=${encodeURIComponent(pathname)}`,
|
|
87
|
+
req.url
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
response.cookies.set(COOKIE_AUTH_TOKEN, "", { path: "/", maxAge: 0 });
|
|
91
|
+
response.cookies.set(COOKIE_REFRESH_TOKEN, "", {
|
|
92
|
+
path: "/api/auth",
|
|
93
|
+
maxAge: 0
|
|
94
|
+
});
|
|
95
|
+
response.cookies.set(COOKIE_AUTH_SESSION, "", { path: "/", maxAge: 0 });
|
|
96
|
+
return { authObj: null, response };
|
|
97
|
+
}
|
|
98
|
+
const claims = getClaimsFromToken(token);
|
|
99
|
+
if (!claims) {
|
|
100
|
+
return {
|
|
101
|
+
authObj: null,
|
|
102
|
+
response: NextResponse.redirect(new URL(signInUrl, req.url))
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { authObj: buildAuthObject(token, claims) };
|
|
106
|
+
}
|
|
107
|
+
function inaiAuthMiddleware(config = {}) {
|
|
108
|
+
const {
|
|
109
|
+
publicRoutes = [],
|
|
110
|
+
signInUrl = "/login",
|
|
111
|
+
beforeAuth,
|
|
112
|
+
afterAuth
|
|
113
|
+
} = config;
|
|
114
|
+
const builtinPublic = ["/_next/*", "/favicon.ico", "/api/*", signInUrl];
|
|
115
|
+
return async function middleware(req) {
|
|
116
|
+
if (beforeAuth) {
|
|
117
|
+
const result = beforeAuth(req);
|
|
118
|
+
if (result) return result;
|
|
119
|
+
}
|
|
120
|
+
if (isPublicRoute(req, publicRoutes, builtinPublic)) {
|
|
121
|
+
return NextResponse.next();
|
|
122
|
+
}
|
|
123
|
+
const { authObj, response } = await runAuthCheck(req, signInUrl);
|
|
124
|
+
if (response) return response;
|
|
125
|
+
if (!authObj)
|
|
126
|
+
return NextResponse.redirect(new URL(signInUrl, req.url));
|
|
127
|
+
if (afterAuth) {
|
|
128
|
+
const result = afterAuth(authObj, req);
|
|
129
|
+
if (result) return result;
|
|
130
|
+
}
|
|
131
|
+
return NextResponse.next();
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function withInAIAuth(wrappedMiddleware, config = {}) {
|
|
135
|
+
const {
|
|
136
|
+
publicRoutes = [],
|
|
137
|
+
signInUrl = "/login",
|
|
138
|
+
beforeAuth,
|
|
139
|
+
afterAuth
|
|
140
|
+
} = config;
|
|
141
|
+
const builtinPublic = ["/_next/*", "/favicon.ico", "/api/*", signInUrl];
|
|
142
|
+
return async function middleware(req) {
|
|
143
|
+
if (beforeAuth) {
|
|
144
|
+
const result = beforeAuth(req);
|
|
145
|
+
if (result) return result;
|
|
146
|
+
}
|
|
147
|
+
const isPublic = isPublicRoute(req, publicRoutes, builtinPublic);
|
|
148
|
+
if (!isPublic) {
|
|
149
|
+
const { authObj, response } = await runAuthCheck(req, signInUrl);
|
|
150
|
+
if (response) return response;
|
|
151
|
+
if (!authObj)
|
|
152
|
+
return NextResponse.redirect(new URL(signInUrl, req.url));
|
|
153
|
+
if (afterAuth) {
|
|
154
|
+
const result = afterAuth(authObj, req);
|
|
155
|
+
if (result) return result;
|
|
156
|
+
}
|
|
157
|
+
const authHeader = JSON.stringify({
|
|
158
|
+
userId: authObj.userId,
|
|
159
|
+
tenantId: authObj.tenantId,
|
|
160
|
+
appId: authObj.appId,
|
|
161
|
+
envId: authObj.envId,
|
|
162
|
+
orgId: authObj.orgId,
|
|
163
|
+
orgRole: authObj.orgRole
|
|
164
|
+
});
|
|
165
|
+
req.headers.set("x-inai-auth", authHeader);
|
|
166
|
+
}
|
|
167
|
+
const wrappedResponse = await wrappedMiddleware(req);
|
|
168
|
+
if (wrappedResponse instanceof NextResponse) return wrappedResponse;
|
|
169
|
+
return new NextResponse(wrappedResponse.body, wrappedResponse);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
createRouteMatcher,
|
|
174
|
+
inaiAuthMiddleware,
|
|
175
|
+
withInAIAuth
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +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"]}
|