@inai-dev/hono 1.0.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.
@@ -0,0 +1,151 @@
1
+ // src/middleware.ts
2
+ import { InAIAuthClient, buildAuthObjectFromToken } from "@inai-dev/backend";
3
+ import { isTokenExpired } from "@inai-dev/shared";
4
+
5
+ // src/helpers.ts
6
+ import { getCookie, setCookie, deleteCookie } from "hono/cookie";
7
+ import {
8
+ COOKIE_AUTH_TOKEN,
9
+ COOKIE_REFRESH_TOKEN,
10
+ COOKIE_AUTH_SESSION,
11
+ decodeJWTPayload
12
+ } from "@inai-dev/shared";
13
+ function getAuth(c) {
14
+ return c.get("inaiAuth") ?? null;
15
+ }
16
+ function getTokenFromContext(c) {
17
+ const authHeader = c.req.header("Authorization");
18
+ if (authHeader?.startsWith("Bearer ")) {
19
+ return authHeader.slice(7);
20
+ }
21
+ return getCookie(c, COOKIE_AUTH_TOKEN) ?? null;
22
+ }
23
+ function getRefreshTokenFromContext(c) {
24
+ return getCookie(c, COOKIE_REFRESH_TOKEN) ?? null;
25
+ }
26
+ function setAuthCookies(c, tokens, user) {
27
+ const isProduction = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
28
+ const claims = decodeJWTPayload(tokens.access_token);
29
+ const expiresAt = claims ? new Date(claims.exp * 1e3).toISOString() : new Date(Date.now() + tokens.expires_in * 1e3).toISOString();
30
+ setCookie(c, COOKIE_AUTH_TOKEN, tokens.access_token, {
31
+ httpOnly: true,
32
+ secure: isProduction,
33
+ sameSite: "Lax",
34
+ path: "/",
35
+ maxAge: tokens.expires_in
36
+ });
37
+ setCookie(c, COOKIE_REFRESH_TOKEN, tokens.refresh_token, {
38
+ httpOnly: true,
39
+ secure: isProduction,
40
+ sameSite: "Strict",
41
+ path: "/api/auth",
42
+ maxAge: 7 * 24 * 60 * 60
43
+ });
44
+ setCookie(
45
+ c,
46
+ COOKIE_AUTH_SESSION,
47
+ JSON.stringify({
48
+ user,
49
+ expiresAt,
50
+ permissions: claims?.permissions ?? [],
51
+ orgId: claims?.org_id,
52
+ orgRole: claims?.org_role,
53
+ appId: claims?.app_id,
54
+ envId: claims?.env_id
55
+ }),
56
+ {
57
+ httpOnly: false,
58
+ secure: isProduction,
59
+ sameSite: "Lax",
60
+ path: "/",
61
+ maxAge: tokens.expires_in
62
+ }
63
+ );
64
+ }
65
+ function clearAuthCookies(c) {
66
+ deleteCookie(c, COOKIE_AUTH_TOKEN, { path: "/" });
67
+ deleteCookie(c, COOKIE_REFRESH_TOKEN, { path: "/api/auth" });
68
+ deleteCookie(c, COOKIE_AUTH_SESSION, { path: "/" });
69
+ }
70
+
71
+ // src/middleware.ts
72
+ function matchesRoute(pathname, patterns) {
73
+ return patterns.some((pattern) => {
74
+ if (pattern.endsWith("*")) {
75
+ return pathname.startsWith(pattern.slice(0, -1));
76
+ }
77
+ return pathname === pattern;
78
+ });
79
+ }
80
+ function isPublicRoute(path, publicRoutes) {
81
+ if (typeof publicRoutes === "function") return publicRoutes(path);
82
+ return matchesRoute(path, publicRoutes);
83
+ }
84
+ function inaiAuthMiddleware(config = {}) {
85
+ const {
86
+ authMode = "app",
87
+ publicRoutes = [],
88
+ onUnauthorized,
89
+ ...authClientConfig
90
+ } = config;
91
+ const client = new InAIAuthClient(authClientConfig);
92
+ const isPlatform = authMode === "platform";
93
+ const defaultUnauthorized = (c) => c.json({ error: "Unauthorized" }, 401);
94
+ const handleUnauthorized = onUnauthorized ?? defaultUnauthorized;
95
+ return async function middleware(c, next) {
96
+ const path = new URL(c.req.url).pathname;
97
+ if (isPublicRoute(path, publicRoutes)) {
98
+ c.set("inaiAuth", null);
99
+ await next();
100
+ return;
101
+ }
102
+ const token = getTokenFromContext(c);
103
+ if (!token || isTokenExpired(token)) {
104
+ const refreshToken = getRefreshTokenFromContext(c);
105
+ if (refreshToken) {
106
+ try {
107
+ const tokens = isPlatform ? await client.platformRefresh(refreshToken) : await client.refresh(refreshToken);
108
+ const { data: user } = isPlatform ? await client.platformGetMe(tokens.access_token) : await client.getMe(tokens.access_token);
109
+ setAuthCookies(c, tokens, user);
110
+ const authObj2 = buildAuthObjectFromToken(tokens.access_token);
111
+ c.set("inaiAuth", authObj2);
112
+ await next();
113
+ return;
114
+ } catch {
115
+ clearAuthCookies(c);
116
+ return handleUnauthorized(c);
117
+ }
118
+ }
119
+ return handleUnauthorized(c);
120
+ }
121
+ const authObj = buildAuthObjectFromToken(token);
122
+ if (!authObj) {
123
+ return handleUnauthorized(c);
124
+ }
125
+ c.set("inaiAuth", authObj);
126
+ await next();
127
+ };
128
+ }
129
+ function requireAuth(config = {}) {
130
+ return async function middleware(c, next) {
131
+ const auth = getAuth(c);
132
+ if (!auth?.userId) {
133
+ return c.json({ error: "Unauthorized" }, 401);
134
+ }
135
+ if (config.role || config.permission) {
136
+ const hasAccess = auth.has({
137
+ role: config.role,
138
+ permission: config.permission
139
+ });
140
+ if (!hasAccess) {
141
+ return c.json({ error: "Forbidden" }, 403);
142
+ }
143
+ }
144
+ await next();
145
+ };
146
+ }
147
+ export {
148
+ inaiAuthMiddleware,
149
+ requireAuth
150
+ };
151
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +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":"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","names":["authObj"]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@inai-dev/hono",
3
+ "version": "1.0.0",
4
+ "description": "Hono integration for InAI Auth SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./middleware": {
16
+ "types": "./dist/middleware.d.ts",
17
+ "import": "./dist/middleware.js",
18
+ "require": "./dist/middleware.cjs"
19
+ },
20
+ "./api-routes": {
21
+ "types": "./dist/api-routes.d.ts",
22
+ "import": "./dist/api-routes.js",
23
+ "require": "./dist/api-routes.cjs"
24
+ }
25
+ },
26
+ "sideEffects": false,
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "dev": "tsup --watch",
33
+ "clean": "rm -rf dist",
34
+ "typecheck": "tsc --noEmit",
35
+ "prepublishOnly": "npm run build"
36
+ },
37
+ "dependencies": {
38
+ "@inai-dev/types": "^1.1.0",
39
+ "@inai-dev/shared": "^1.1.0",
40
+ "@inai-dev/backend": "^1.2.0"
41
+ },
42
+ "peerDependencies": {
43
+ "hono": ">=4.0.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "hono": {
47
+ "optional": false
48
+ }
49
+ },
50
+ "devDependencies": {
51
+ "hono": "^4.0.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "author": "InAI <contact@inai.dev>",
57
+ "license": "MIT",
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "https://github.com/InAI-Team/inai-auth-sdk.git",
61
+ "directory": "packages/hono"
62
+ },
63
+ "homepage": "https://inai.dev/",
64
+ "bugs": "https://github.com/InAI-Team/inai-auth-sdk/issues",
65
+ "keywords": [
66
+ "inai",
67
+ "auth",
68
+ "hono",
69
+ "middleware",
70
+ "multi-tenant",
71
+ "cloudflare-workers"
72
+ ]
73
+ }