@geekapps/auth-nextjs 0.1.4 → 0.1.5
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/dist/middleware.d.ts +4 -0
- package/dist/middleware.js +3 -2
- package/dist/middleware.js.map +1 -1
- package/package.json +1 -1
package/dist/middleware.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ type AuthMiddlewareConfig = {
|
|
|
10
10
|
cookiePrefix?: string;
|
|
11
11
|
secureCookies?: boolean;
|
|
12
12
|
cookieDomain?: string;
|
|
13
|
+
/** Base URL of this app (e.g. "https://meuapp.com"). When set, overrides origin
|
|
14
|
+
* detection from request headers — useful when running behind Docker or without
|
|
15
|
+
* a reverse proxy that sets x-forwarded-host. */
|
|
16
|
+
appUrl?: string;
|
|
13
17
|
};
|
|
14
18
|
declare function createAuthMiddleware(input?: AuthMiddlewareConfig): (request: NextRequest) => Promise<NextResponse>;
|
|
15
19
|
|
package/dist/middleware.js
CHANGED
|
@@ -42,7 +42,8 @@ function decodeJwtExp(token) {
|
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
function requestOrigin(request) {
|
|
45
|
+
function requestOrigin(request, appUrl) {
|
|
46
|
+
if (appUrl) return appUrl.replace(/\/$/, "");
|
|
46
47
|
const forwardedProto = request.headers.get("x-forwarded-proto");
|
|
47
48
|
const forwardedHost = request.headers.get("x-forwarded-host");
|
|
48
49
|
const host = forwardedHost ?? request.headers.get("host");
|
|
@@ -86,7 +87,7 @@ function createAuthMiddleware(input = {}) {
|
|
|
86
87
|
}
|
|
87
88
|
const accessToken = activeIdx > 0 ? request.cookies.get(slotAt(activeIdx))?.value ?? null : null;
|
|
88
89
|
const refreshToken = activeIdx > 0 ? request.cookies.get(slotRt(activeIdx))?.value ?? null : null;
|
|
89
|
-
const origin = requestOrigin(request);
|
|
90
|
+
const origin = requestOrigin(request, input.appUrl);
|
|
90
91
|
const signInUrl = new URL(`${origin}${basePath}/sign-in`);
|
|
91
92
|
const redirectTo = request.nextUrl.pathname + request.nextUrl.search;
|
|
92
93
|
if (redirectTo && redirectTo !== "/") {
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import { NextResponse, type NextRequest } from \"next/server\";\nimport { createRemoteJWKSet, jwtVerify } from \"jose\";\n\nexport type AuthMiddlewareConfig = {\n issuer?: string;\n basePath?: string;\n /**\n * @deprecated cookiePrefix is now auto-derived from GEEKAPPS_AUTH_CLIENT_ID.\n * This field is kept for backwards compatibility but is ignored.\n */\n cookiePrefix?: string;\n secureCookies?: boolean;\n cookieDomain?: string;\n};\n\ntype TokenLikeResponse = {\n access_token?: string;\n accessToken?: string;\n refresh_token?: string;\n refreshToken?: string;\n id_token?: string;\n idToken?: string;\n};\n\nfunction env(name: string) {\n const value = process.env[name];\n return value && value.trim().length > 0 ? value : undefined;\n}\n\nfunction normalizeBaseUrl(url: string) {\n return url.replace(/\\/$/, \"\");\n}\n\nfunction normalizeBasePath(path = \"/api/auth\") {\n if (!path.startsWith(\"/\")) return `/${path}`;\n return path.replace(/\\/$/, \"\");\n}\n\nfunction resolveIssuer(input: AuthMiddlewareConfig) {\n return normalizeBaseUrl(\n input.issuer ??\n env(\"GEEKAPPS_AUTH_ISSUER\") ??\n env(\"GEEKAPPS_AUTH_API_URL\") ??\n \"http://localhost:3020\",\n );\n}\n\nfunction hashClientId(clientId: string): string {\n let h = 5381;\n for (let i = 0; i < clientId.length; i++) {\n h = ((h << 5) + h) ^ (clientId.charCodeAt(i) & 0xff);\n h = h >>> 0;\n }\n return h.toString(16).padStart(8, \"0\");\n}\n\nfunction deriveCookiePrefix(clientId: string | undefined): string {\n if (!clientId) return \"gk\";\n return `gk_${hashClientId(clientId)}`;\n}\n\n\nconst MAX_REFRESH_ATTEMPTS = 3;\n\nfunction decodeJwtExp(token: string): number | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n const payload = parts[1]!;\n const padded = payload + \"=\".repeat((4 - (payload.length % 4)) % 4);\n const decoded = JSON.parse(atob(padded)) as { exp?: number };\n return typeof decoded.exp === \"number\" ? decoded.exp : null;\n } catch {\n return null;\n }\n}\n\nfunction requestOrigin(request: NextRequest) {\n const forwardedProto = request.headers.get(\"x-forwarded-proto\");\n const forwardedHost = request.headers.get(\"x-forwarded-host\");\n const host = forwardedHost ?? request.headers.get(\"host\");\n if (host) {\n return `${forwardedProto ?? request.nextUrl.protocol.replace(\":\", \"\")}://${host}`;\n }\n return request.nextUrl.origin;\n}\n\nconst _jwksCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getRemoteJWKS(issuer: string) {\n if (!_jwksCache.has(issuer)) {\n _jwksCache.set(issuer, createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`)));\n }\n return _jwksCache.get(issuer)!;\n}\n\nasync function verifyAccessToken(token: string, issuer: string): Promise<boolean> {\n try {\n await jwtVerify(token, getRemoteJWKS(issuer), { algorithms: [\"RS256\"], issuer });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function createAuthMiddleware(input: AuthMiddlewareConfig = {}) {\n const issuer = resolveIssuer(input);\n const basePath = normalizeBasePath(input.basePath);\n const clientId = env(\"GEEKAPPS_AUTH_CLIENT_ID\");\n const prefix = deriveCookiePrefix(clientId);\n\n const activeCookie = `${prefix}_active`;\n const countCookie = `${prefix}_count`;\n const tokenPubCookie = `${prefix}_tp`; // legacy — cleared on next refresh\n const slotAt = (n: number) => `${prefix}_at${n}`;\n const slotRt = (n: number) => `${prefix}_rt${n}`;\n\n return async function authMiddleware(request: NextRequest): Promise<NextResponse> {\n const countRaw = request.cookies.get(countCookie)?.value;\n const count = countRaw ? Number.parseInt(countRaw, 10) : 0;\n\n const activeRaw = request.cookies.get(activeCookie)?.value;\n let activeIdx = activeRaw ? Number.parseInt(activeRaw, 10) : 1;\n if (Number.isNaN(activeIdx) || activeIdx < 1 || activeIdx > count) {\n activeIdx = count > 0 ? 1 : 0;\n }\n\n const accessToken = activeIdx > 0 ? (request.cookies.get(slotAt(activeIdx))?.value ?? null) : null;\n const refreshToken = activeIdx > 0 ? (request.cookies.get(slotRt(activeIdx))?.value ?? null) : null;\n\n const origin = requestOrigin(request);\n const signInUrl = new URL(`${origin}${basePath}/sign-in`);\n const redirectTo = request.nextUrl.pathname + request.nextUrl.search;\n if (redirectTo && redirectTo !== \"/\") {\n signInUrl.searchParams.set(\"redirectTo\", redirectTo);\n }\n\n if (!accessToken && !refreshToken) {\n return NextResponse.redirect(signInUrl);\n }\n\n // Fast-path: check exp before hitting JWKS (avoids network on every request)\n const exp = accessToken ? decodeJwtExp(accessToken) : null;\n const nowSec = Math.floor(Date.now() / 1000);\n const expiredOrMissing = !exp || exp - nowSec < 60;\n\n if (!expiredOrMissing) {\n // Verify signature against app-api JWKS\n const valid = await verifyAccessToken(accessToken!, issuer);\n if (valid) return NextResponse.next();\n }\n\n if (!refreshToken) {\n return NextResponse.redirect(signInUrl);\n }\n\n const clientSecret = env(\"GEEKAPPS_AUTH_CLIENT_SECRET\") ?? \"\";\n const basicAuth = btoa(`${clientId ?? \"\"}:${clientSecret}`);\n\n let newAccessToken: string | null = null;\n let newRefreshToken: string | null = null;\n\n for (let attempt = 1; attempt <= MAX_REFRESH_ATTEMPTS; attempt++) {\n try {\n const refreshResponse = await fetch(`${issuer}/auth/refresh`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n authorization: `Basic ${basicAuth}`,\n },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n }),\n cache: \"no-store\",\n });\n\n if (!refreshResponse.ok) break;\n\n const data = (await refreshResponse.json()) as TokenLikeResponse;\n newAccessToken = data.accessToken ?? data.access_token ?? null;\n newRefreshToken = data.refreshToken ?? data.refresh_token ?? null;\n if (newAccessToken) break;\n } catch {\n if (attempt === MAX_REFRESH_ATTEMPTS) break;\n }\n }\n\n if (!newAccessToken) {\n // Refresh failed — clear the active slot and redirect to sign-in\n const response = NextResponse.redirect(signInUrl);\n const clearOpts = {\n httpOnly: true,\n secure: input.secureCookies ?? process.env.NODE_ENV === \"production\",\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 0,\n };\n response.cookies.set(slotAt(activeIdx), \"\", clearOpts);\n response.cookies.set(slotRt(activeIdx), \"\", clearOpts);\n response.cookies.set(`${prefix}_m${activeIdx}`, \"\", clearOpts);\n response.cookies.set(tokenPubCookie, \"\", { ...clearOpts, httpOnly: false });\n response.cookies.set(activeCookie, \"0\", clearOpts);\n return response;\n }\n\n const cookieOptions = {\n httpOnly: true,\n secure: input.secureCookies ?? process.env.NODE_ENV === \"production\",\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 60 * 60 * 24 * 30,\n ...(input.cookieDomain ? { domain: input.cookieDomain } : {}),\n };\n\n // Forward new token via request header so server actions in the same\n // request can read it — response cookies are not visible within the same request.\n const requestHeaders = new Headers(request.headers);\n requestHeaders.set(\"x-gk-access-token\", newAccessToken);\n\n const response = NextResponse.next({ request: { headers: requestHeaders } });\n response.cookies.set(slotAt(activeIdx), newAccessToken, cookieOptions);\n if (newRefreshToken) response.cookies.set(slotRt(activeIdx), newRefreshToken, cookieOptions);\n // Clear legacy _tp cookie to reduce header size\n if (request.cookies.get(tokenPubCookie)?.value) {\n response.cookies.set(tokenPubCookie, \"\", { httpOnly: false, maxAge: 0, path: \"/\" });\n }\n\n return response;\n };\n}\n"],"mappings":"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","names":["response"]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import { NextResponse, type NextRequest } from \"next/server\";\nimport { createRemoteJWKSet, jwtVerify } from \"jose\";\n\nexport type AuthMiddlewareConfig = {\n issuer?: string;\n basePath?: string;\n /**\n * @deprecated cookiePrefix is now auto-derived from GEEKAPPS_AUTH_CLIENT_ID.\n * This field is kept for backwards compatibility but is ignored.\n */\n cookiePrefix?: string;\n secureCookies?: boolean;\n cookieDomain?: string;\n /** Base URL of this app (e.g. \"https://meuapp.com\"). When set, overrides origin\n * detection from request headers — useful when running behind Docker or without\n * a reverse proxy that sets x-forwarded-host. */\n appUrl?: string;\n};\n\ntype TokenLikeResponse = {\n access_token?: string;\n accessToken?: string;\n refresh_token?: string;\n refreshToken?: string;\n id_token?: string;\n idToken?: string;\n};\n\nfunction env(name: string) {\n const value = process.env[name];\n return value && value.trim().length > 0 ? value : undefined;\n}\n\nfunction normalizeBaseUrl(url: string) {\n return url.replace(/\\/$/, \"\");\n}\n\nfunction normalizeBasePath(path = \"/api/auth\") {\n if (!path.startsWith(\"/\")) return `/${path}`;\n return path.replace(/\\/$/, \"\");\n}\n\nfunction resolveIssuer(input: AuthMiddlewareConfig) {\n return normalizeBaseUrl(\n input.issuer ??\n env(\"GEEKAPPS_AUTH_ISSUER\") ??\n env(\"GEEKAPPS_AUTH_API_URL\") ??\n \"http://localhost:3020\",\n );\n}\n\nfunction hashClientId(clientId: string): string {\n let h = 5381;\n for (let i = 0; i < clientId.length; i++) {\n h = ((h << 5) + h) ^ (clientId.charCodeAt(i) & 0xff);\n h = h >>> 0;\n }\n return h.toString(16).padStart(8, \"0\");\n}\n\nfunction deriveCookiePrefix(clientId: string | undefined): string {\n if (!clientId) return \"gk\";\n return `gk_${hashClientId(clientId)}`;\n}\n\n\nconst MAX_REFRESH_ATTEMPTS = 3;\n\nfunction decodeJwtExp(token: string): number | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n const payload = parts[1]!;\n const padded = payload + \"=\".repeat((4 - (payload.length % 4)) % 4);\n const decoded = JSON.parse(atob(padded)) as { exp?: number };\n return typeof decoded.exp === \"number\" ? decoded.exp : null;\n } catch {\n return null;\n }\n}\n\nfunction requestOrigin(request: NextRequest, appUrl?: string) {\n if (appUrl) return appUrl.replace(/\\/$/, \"\");\n const forwardedProto = request.headers.get(\"x-forwarded-proto\");\n const forwardedHost = request.headers.get(\"x-forwarded-host\");\n const host = forwardedHost ?? request.headers.get(\"host\");\n if (host) {\n return `${forwardedProto ?? request.nextUrl.protocol.replace(\":\", \"\")}://${host}`;\n }\n return request.nextUrl.origin;\n}\n\nconst _jwksCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getRemoteJWKS(issuer: string) {\n if (!_jwksCache.has(issuer)) {\n _jwksCache.set(issuer, createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`)));\n }\n return _jwksCache.get(issuer)!;\n}\n\nasync function verifyAccessToken(token: string, issuer: string): Promise<boolean> {\n try {\n await jwtVerify(token, getRemoteJWKS(issuer), { algorithms: [\"RS256\"], issuer });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function createAuthMiddleware(input: AuthMiddlewareConfig = {}) {\n const issuer = resolveIssuer(input);\n const basePath = normalizeBasePath(input.basePath);\n const clientId = env(\"GEEKAPPS_AUTH_CLIENT_ID\");\n const prefix = deriveCookiePrefix(clientId);\n\n const activeCookie = `${prefix}_active`;\n const countCookie = `${prefix}_count`;\n const tokenPubCookie = `${prefix}_tp`; // legacy — cleared on next refresh\n const slotAt = (n: number) => `${prefix}_at${n}`;\n const slotRt = (n: number) => `${prefix}_rt${n}`;\n\n return async function authMiddleware(request: NextRequest): Promise<NextResponse> {\n const countRaw = request.cookies.get(countCookie)?.value;\n const count = countRaw ? Number.parseInt(countRaw, 10) : 0;\n\n const activeRaw = request.cookies.get(activeCookie)?.value;\n let activeIdx = activeRaw ? Number.parseInt(activeRaw, 10) : 1;\n if (Number.isNaN(activeIdx) || activeIdx < 1 || activeIdx > count) {\n activeIdx = count > 0 ? 1 : 0;\n }\n\n const accessToken = activeIdx > 0 ? (request.cookies.get(slotAt(activeIdx))?.value ?? null) : null;\n const refreshToken = activeIdx > 0 ? (request.cookies.get(slotRt(activeIdx))?.value ?? null) : null;\n\n const origin = requestOrigin(request, input.appUrl);\n const signInUrl = new URL(`${origin}${basePath}/sign-in`);\n const redirectTo = request.nextUrl.pathname + request.nextUrl.search;\n if (redirectTo && redirectTo !== \"/\") {\n signInUrl.searchParams.set(\"redirectTo\", redirectTo);\n }\n\n if (!accessToken && !refreshToken) {\n return NextResponse.redirect(signInUrl);\n }\n\n // Fast-path: check exp before hitting JWKS (avoids network on every request)\n const exp = accessToken ? decodeJwtExp(accessToken) : null;\n const nowSec = Math.floor(Date.now() / 1000);\n const expiredOrMissing = !exp || exp - nowSec < 60;\n\n if (!expiredOrMissing) {\n // Verify signature against app-api JWKS\n const valid = await verifyAccessToken(accessToken!, issuer);\n if (valid) return NextResponse.next();\n }\n\n if (!refreshToken) {\n return NextResponse.redirect(signInUrl);\n }\n\n const clientSecret = env(\"GEEKAPPS_AUTH_CLIENT_SECRET\") ?? \"\";\n const basicAuth = btoa(`${clientId ?? \"\"}:${clientSecret}`);\n\n let newAccessToken: string | null = null;\n let newRefreshToken: string | null = null;\n\n for (let attempt = 1; attempt <= MAX_REFRESH_ATTEMPTS; attempt++) {\n try {\n const refreshResponse = await fetch(`${issuer}/auth/refresh`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n authorization: `Basic ${basicAuth}`,\n },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n }),\n cache: \"no-store\",\n });\n\n if (!refreshResponse.ok) break;\n\n const data = (await refreshResponse.json()) as TokenLikeResponse;\n newAccessToken = data.accessToken ?? data.access_token ?? null;\n newRefreshToken = data.refreshToken ?? data.refresh_token ?? null;\n if (newAccessToken) break;\n } catch {\n if (attempt === MAX_REFRESH_ATTEMPTS) break;\n }\n }\n\n if (!newAccessToken) {\n // Refresh failed — clear the active slot and redirect to sign-in\n const response = NextResponse.redirect(signInUrl);\n const clearOpts = {\n httpOnly: true,\n secure: input.secureCookies ?? process.env.NODE_ENV === \"production\",\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 0,\n };\n response.cookies.set(slotAt(activeIdx), \"\", clearOpts);\n response.cookies.set(slotRt(activeIdx), \"\", clearOpts);\n response.cookies.set(`${prefix}_m${activeIdx}`, \"\", clearOpts);\n response.cookies.set(tokenPubCookie, \"\", { ...clearOpts, httpOnly: false });\n response.cookies.set(activeCookie, \"0\", clearOpts);\n return response;\n }\n\n const cookieOptions = {\n httpOnly: true,\n secure: input.secureCookies ?? process.env.NODE_ENV === \"production\",\n sameSite: \"lax\" as const,\n path: \"/\",\n maxAge: 60 * 60 * 24 * 30,\n ...(input.cookieDomain ? { domain: input.cookieDomain } : {}),\n };\n\n // Forward new token via request header so server actions in the same\n // request can read it — response cookies are not visible within the same request.\n const requestHeaders = new Headers(request.headers);\n requestHeaders.set(\"x-gk-access-token\", newAccessToken);\n\n const response = NextResponse.next({ request: { headers: requestHeaders } });\n response.cookies.set(slotAt(activeIdx), newAccessToken, cookieOptions);\n if (newRefreshToken) response.cookies.set(slotRt(activeIdx), newRefreshToken, cookieOptions);\n // Clear legacy _tp cookie to reduce header size\n if (request.cookies.get(tokenPubCookie)?.value) {\n response.cookies.set(tokenPubCookie, \"\", { httpOnly: false, maxAge: 0, path: \"/\" });\n }\n\n return response;\n };\n}\n"],"mappings":"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","names":["response"]}
|