@oauth42/next 0.2.5 → 0.2.6
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-B8dYrjZ1.d.mts → auth-C401ZFad.d.mts} +17 -31
- package/dist/{middleware-B8dYrjZ1.d.ts → auth-C401ZFad.d.ts} +17 -31
- package/dist/client/index.d.mts +15 -7
- package/dist/client/index.d.ts +15 -7
- package/dist/client/index.js +13 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +13 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +189 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +190 -12
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/index.d.mts +39 -0
- package/dist/middleware/index.d.ts +39 -0
- package/dist/middleware/index.js +166 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/index.mjs +138 -0
- package/dist/middleware/index.mjs.map +1 -0
- package/dist/server/index.d.mts +2 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +189 -11
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +192 -14
- package/dist/server/index.mjs.map +1 -1
- package/package.json +6 -1
- package/src/types/next-auth.d.ts +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/index.ts","../../src/server/middleware.ts"],"sourcesContent":["// Edge-compatible middleware exports\n// This file is separate from server/index.ts to avoid pulling in Node.js modules\n\nexport { withOAuth42Auth, createMiddlewareConfig } from '../server/middleware';\nexport type { OAuth42AuthOptions } from '../server/middleware';\n","import { NextRequest, NextResponse } from 'next/server';\nimport { getToken, encode } from 'next-auth/jwt';\n\nexport interface OAuth42AuthOptions {\n pages?: {\n signIn?: string;\n error?: string;\n };\n callbacks?: {\n authorized?: (params: { token: any; req: NextRequest }) => boolean | Promise<boolean>;\n };\n protectedPaths?: string[];\n publicPaths?: string[];\n /**\n * Cookie prefix for custom cookie names. Must match the prefix used in createAuth().\n * E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'\n */\n cookiePrefix?: string;\n}\n\n/**\n * Refresh tokens by calling the OAuth42 backend directly\n */\nasync function refreshTokens(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n issuer: string\n): Promise<{ success: boolean; accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string }> {\n try {\n const tokenUrl = `${issuer}/oauth2/token`;\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n console.error('[OAuth42 Middleware] Token refresh failed:', data);\n return { success: false, error: data.error || 'refresh_failed' };\n }\n\n console.log('[OAuth42 Middleware] Token refreshed successfully');\n return {\n success: true,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),\n };\n } catch (error) {\n console.error('[OAuth42 Middleware] Token refresh error:', error);\n return { success: false, error: 'refresh_error' };\n }\n}\n\n/**\n * Middleware helper for protecting routes with OAuth42\n *\n * This middleware handles:\n * 1. Route protection (redirect to login if no session)\n * 2. Token refresh (refresh expired tokens and update cookie)\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n const secret = process.env.NEXTAUTH_SECRET;\n const clientId = process.env.OAUTH42_CLIENT_ID;\n const clientSecret = process.env.OAUTH42_CLIENT_SECRET;\n const issuer = process.env.OAUTH42_ISSUER || 'https://localhost:8443';\n\n if (!secret) {\n console.warn('[OAuth42 Middleware] NEXTAUTH_SECRET not set');\n }\n\n return async function middleware(req: NextRequest) {\n // Build cookie name - if prefix is provided, use custom name\n const cookieName = options.cookiePrefix\n ? `${options.cookiePrefix}.session-token`\n : 'next-auth.session-token';\n\n const token = await getToken({\n req: req as any,\n secret,\n cookieName,\n });\n\n const pathname = req.nextUrl.pathname;\n\n // Check if path is explicitly public\n if (options.publicPaths?.some(path => pathname.startsWith(path))) {\n return NextResponse.next();\n }\n\n // Check if path needs protection\n const needsProtection = options.protectedPaths\n ? options.protectedPaths.some(path => pathname.startsWith(path))\n : true; // Default to protecting all paths\n\n if (!needsProtection) {\n return NextResponse.next();\n }\n\n // No token at all - redirect to sign in\n if (!token) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if access token is expired or expiring soon (60 second buffer)\n const expiresAt = token.expiresAt as number | undefined;\n const now = Math.floor(Date.now() / 1000);\n const bufferSeconds = 60;\n const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;\n\n if (needsRefresh && token.refreshToken && clientId && clientSecret) {\n console.log('[OAuth42 Middleware] Access token expired, refreshing...');\n\n const refreshed = await refreshTokens(\n token.refreshToken as string,\n clientId,\n clientSecret,\n issuer\n );\n\n if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {\n // Update the token with new values\n const updatedToken = {\n ...token,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n };\n\n // Re-encode the JWT\n const newJwt = await encode({\n token: updatedToken,\n secret: secret!,\n });\n\n // Create response and set the updated cookie\n const response = NextResponse.next();\n\n // Set cookie with same settings NextAuth uses\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n sameSite: 'lax',\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n });\n\n console.log('[OAuth42 Middleware] Cookie updated with refreshed tokens');\n return response;\n } else {\n // Refresh failed - redirect to sign in\n console.error('[OAuth42 Middleware] Refresh failed, redirecting to sign in');\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n url.searchParams.set('error', 'RefreshAccessTokenError');\n return NextResponse.redirect(url);\n }\n }\n\n // Check custom authorization callback\n if (options.callbacks?.authorized) {\n const isAuthorized = await options.callbacks.authorized({ token, req });\n if (!isAuthorized) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n }\n\n return NextResponse.next();\n };\n}\n\n/**\n * Helper to create middleware configuration\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[] = ['/protected'],\n publicPaths: string[] = ['/auth', '/api/auth']\n) {\n return {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - public folder\n */\n '/((?!_next/static|_next/image|favicon.ico|public).*)',\n ],\n protectedPaths,\n publicPaths,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// src/server/middleware.ts
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import { getToken, encode } from "next-auth/jwt";
|
|
4
|
+
async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
5
|
+
try {
|
|
6
|
+
const tokenUrl = `${issuer}/oauth2/token`;
|
|
7
|
+
const response = await fetch(tokenUrl, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: {
|
|
10
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
11
|
+
},
|
|
12
|
+
body: new URLSearchParams({
|
|
13
|
+
grant_type: "refresh_token",
|
|
14
|
+
refresh_token: refreshToken,
|
|
15
|
+
client_id: clientId,
|
|
16
|
+
client_secret: clientSecret
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
const data = await response.json();
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
console.error("[OAuth42 Middleware] Token refresh failed:", data);
|
|
22
|
+
return { success: false, error: data.error || "refresh_failed" };
|
|
23
|
+
}
|
|
24
|
+
console.log("[OAuth42 Middleware] Token refreshed successfully");
|
|
25
|
+
return {
|
|
26
|
+
success: true,
|
|
27
|
+
accessToken: data.access_token,
|
|
28
|
+
refreshToken: data.refresh_token,
|
|
29
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (data.expires_in || 3600)
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("[OAuth42 Middleware] Token refresh error:", error);
|
|
33
|
+
return { success: false, error: "refresh_error" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function withOAuth42Auth(options = {}) {
|
|
37
|
+
const secret = process.env.NEXTAUTH_SECRET;
|
|
38
|
+
const clientId = process.env.OAUTH42_CLIENT_ID;
|
|
39
|
+
const clientSecret = process.env.OAUTH42_CLIENT_SECRET;
|
|
40
|
+
const issuer = process.env.OAUTH42_ISSUER || "https://localhost:8443";
|
|
41
|
+
if (!secret) {
|
|
42
|
+
console.warn("[OAuth42 Middleware] NEXTAUTH_SECRET not set");
|
|
43
|
+
}
|
|
44
|
+
return async function middleware(req) {
|
|
45
|
+
const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
|
|
46
|
+
const token = await getToken({
|
|
47
|
+
req,
|
|
48
|
+
secret,
|
|
49
|
+
cookieName
|
|
50
|
+
});
|
|
51
|
+
const pathname = req.nextUrl.pathname;
|
|
52
|
+
if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
|
|
53
|
+
return NextResponse.next();
|
|
54
|
+
}
|
|
55
|
+
const needsProtection = options.protectedPaths ? options.protectedPaths.some((path) => pathname.startsWith(path)) : true;
|
|
56
|
+
if (!needsProtection) {
|
|
57
|
+
return NextResponse.next();
|
|
58
|
+
}
|
|
59
|
+
if (!token) {
|
|
60
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
61
|
+
const url = new URL(signInUrl, req.url);
|
|
62
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
63
|
+
return NextResponse.redirect(url);
|
|
64
|
+
}
|
|
65
|
+
const expiresAt = token.expiresAt;
|
|
66
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
67
|
+
const bufferSeconds = 60;
|
|
68
|
+
const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;
|
|
69
|
+
if (needsRefresh && token.refreshToken && clientId && clientSecret) {
|
|
70
|
+
console.log("[OAuth42 Middleware] Access token expired, refreshing...");
|
|
71
|
+
const refreshed = await refreshTokens(
|
|
72
|
+
token.refreshToken,
|
|
73
|
+
clientId,
|
|
74
|
+
clientSecret,
|
|
75
|
+
issuer
|
|
76
|
+
);
|
|
77
|
+
if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {
|
|
78
|
+
const updatedToken = {
|
|
79
|
+
...token,
|
|
80
|
+
accessToken: refreshed.accessToken,
|
|
81
|
+
refreshToken: refreshed.refreshToken,
|
|
82
|
+
expiresAt: refreshed.expiresAt
|
|
83
|
+
};
|
|
84
|
+
const newJwt = await encode({
|
|
85
|
+
token: updatedToken,
|
|
86
|
+
secret
|
|
87
|
+
});
|
|
88
|
+
const response = NextResponse.next();
|
|
89
|
+
response.cookies.set(cookieName, newJwt, {
|
|
90
|
+
httpOnly: true,
|
|
91
|
+
sameSite: "lax",
|
|
92
|
+
path: "/",
|
|
93
|
+
secure: process.env.NODE_ENV === "production"
|
|
94
|
+
});
|
|
95
|
+
console.log("[OAuth42 Middleware] Cookie updated with refreshed tokens");
|
|
96
|
+
return response;
|
|
97
|
+
} else {
|
|
98
|
+
console.error("[OAuth42 Middleware] Refresh failed, redirecting to sign in");
|
|
99
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
100
|
+
const url = new URL(signInUrl, req.url);
|
|
101
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
102
|
+
url.searchParams.set("error", "RefreshAccessTokenError");
|
|
103
|
+
return NextResponse.redirect(url);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (options.callbacks?.authorized) {
|
|
107
|
+
const isAuthorized = await options.callbacks.authorized({ token, req });
|
|
108
|
+
if (!isAuthorized) {
|
|
109
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
110
|
+
const url = new URL(signInUrl, req.url);
|
|
111
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
112
|
+
return NextResponse.redirect(url);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return NextResponse.next();
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function createMiddlewareConfig(protectedPaths = ["/protected"], publicPaths = ["/auth", "/api/auth"]) {
|
|
119
|
+
return {
|
|
120
|
+
matcher: [
|
|
121
|
+
/*
|
|
122
|
+
* Match all request paths except for the ones starting with:
|
|
123
|
+
* - _next/static (static files)
|
|
124
|
+
* - _next/image (image optimization files)
|
|
125
|
+
* - favicon.ico (favicon file)
|
|
126
|
+
* - public folder
|
|
127
|
+
*/
|
|
128
|
+
"/((?!_next/static|_next/image|favicon.ico|public).*)"
|
|
129
|
+
],
|
|
130
|
+
protectedPaths,
|
|
131
|
+
publicPaths
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
createMiddlewareConfig,
|
|
136
|
+
withOAuth42Auth
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/middleware.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\nimport { getToken, encode } from 'next-auth/jwt';\n\nexport interface OAuth42AuthOptions {\n pages?: {\n signIn?: string;\n error?: string;\n };\n callbacks?: {\n authorized?: (params: { token: any; req: NextRequest }) => boolean | Promise<boolean>;\n };\n protectedPaths?: string[];\n publicPaths?: string[];\n /**\n * Cookie prefix for custom cookie names. Must match the prefix used in createAuth().\n * E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'\n */\n cookiePrefix?: string;\n}\n\n/**\n * Refresh tokens by calling the OAuth42 backend directly\n */\nasync function refreshTokens(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n issuer: string\n): Promise<{ success: boolean; accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string }> {\n try {\n const tokenUrl = `${issuer}/oauth2/token`;\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n console.error('[OAuth42 Middleware] Token refresh failed:', data);\n return { success: false, error: data.error || 'refresh_failed' };\n }\n\n console.log('[OAuth42 Middleware] Token refreshed successfully');\n return {\n success: true,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),\n };\n } catch (error) {\n console.error('[OAuth42 Middleware] Token refresh error:', error);\n return { success: false, error: 'refresh_error' };\n }\n}\n\n/**\n * Middleware helper for protecting routes with OAuth42\n *\n * This middleware handles:\n * 1. Route protection (redirect to login if no session)\n * 2. Token refresh (refresh expired tokens and update cookie)\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n const secret = process.env.NEXTAUTH_SECRET;\n const clientId = process.env.OAUTH42_CLIENT_ID;\n const clientSecret = process.env.OAUTH42_CLIENT_SECRET;\n const issuer = process.env.OAUTH42_ISSUER || 'https://localhost:8443';\n\n if (!secret) {\n console.warn('[OAuth42 Middleware] NEXTAUTH_SECRET not set');\n }\n\n return async function middleware(req: NextRequest) {\n // Build cookie name - if prefix is provided, use custom name\n const cookieName = options.cookiePrefix\n ? `${options.cookiePrefix}.session-token`\n : 'next-auth.session-token';\n\n const token = await getToken({\n req: req as any,\n secret,\n cookieName,\n });\n\n const pathname = req.nextUrl.pathname;\n\n // Check if path is explicitly public\n if (options.publicPaths?.some(path => pathname.startsWith(path))) {\n return NextResponse.next();\n }\n\n // Check if path needs protection\n const needsProtection = options.protectedPaths\n ? options.protectedPaths.some(path => pathname.startsWith(path))\n : true; // Default to protecting all paths\n\n if (!needsProtection) {\n return NextResponse.next();\n }\n\n // No token at all - redirect to sign in\n if (!token) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if access token is expired or expiring soon (60 second buffer)\n const expiresAt = token.expiresAt as number | undefined;\n const now = Math.floor(Date.now() / 1000);\n const bufferSeconds = 60;\n const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;\n\n if (needsRefresh && token.refreshToken && clientId && clientSecret) {\n console.log('[OAuth42 Middleware] Access token expired, refreshing...');\n\n const refreshed = await refreshTokens(\n token.refreshToken as string,\n clientId,\n clientSecret,\n issuer\n );\n\n if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {\n // Update the token with new values\n const updatedToken = {\n ...token,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n };\n\n // Re-encode the JWT\n const newJwt = await encode({\n token: updatedToken,\n secret: secret!,\n });\n\n // Create response and set the updated cookie\n const response = NextResponse.next();\n\n // Set cookie with same settings NextAuth uses\n response.cookies.set(cookieName, newJwt, {\n httpOnly: true,\n sameSite: 'lax',\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n });\n\n console.log('[OAuth42 Middleware] Cookie updated with refreshed tokens');\n return response;\n } else {\n // Refresh failed - redirect to sign in\n console.error('[OAuth42 Middleware] Refresh failed, redirecting to sign in');\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n url.searchParams.set('error', 'RefreshAccessTokenError');\n return NextResponse.redirect(url);\n }\n }\n\n // Check custom authorization callback\n if (options.callbacks?.authorized) {\n const isAuthorized = await options.callbacks.authorized({ token, req });\n if (!isAuthorized) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n }\n\n return NextResponse.next();\n };\n}\n\n/**\n * Helper to create middleware configuration\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[] = ['/protected'],\n publicPaths: string[] = ['/auth', '/api/auth']\n) {\n return {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - public folder\n */\n '/((?!_next/static|_next/image|favicon.ico|public).*)',\n ],\n protectedPaths,\n publicPaths,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/server/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { C as CreateAuthOptions,
|
|
1
|
+
export { C as CreateAuthOptions, O as OAuth42Provider, c as createAuth, f as createHandlers, d as getOAuth42Session, g as getServerSession, r as refreshAccessToken, e as withOAuth42ServerSideProps, w as withOAuth42Session } from '../auth-C401ZFad.mjs';
|
|
2
2
|
export { default as NextAuth, NextAuthOptions } from 'next-auth';
|
|
3
|
+
export { OAuth42AuthOptions, createMiddlewareConfig, withOAuth42Auth } from '../middleware/index.mjs';
|
|
3
4
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
5
|
import 'next-auth/providers/oauth';
|
|
5
6
|
import 'next';
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { C as CreateAuthOptions,
|
|
1
|
+
export { C as CreateAuthOptions, O as OAuth42Provider, c as createAuth, f as createHandlers, d as getOAuth42Session, g as getServerSession, r as refreshAccessToken, e as withOAuth42ServerSideProps, w as withOAuth42Session } from '../auth-C401ZFad.js';
|
|
2
2
|
export { default as NextAuth, NextAuthOptions } from 'next-auth';
|
|
3
|
+
export { OAuth42AuthOptions, createMiddlewareConfig, withOAuth42Auth } from '../middleware/index.js';
|
|
3
4
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
5
|
import 'next-auth/providers/oauth';
|
|
5
6
|
import 'next';
|
package/dist/server/index.js
CHANGED
|
@@ -131,6 +131,7 @@ function withOAuth42ServerSideProps(getServerSideProps, authOptions) {
|
|
|
131
131
|
|
|
132
132
|
// src/server/auth.ts
|
|
133
133
|
var NextAuth = import_next_auth2.default.default || import_next_auth2.default;
|
|
134
|
+
var activeRefresh = null;
|
|
134
135
|
function createAuth(options = {}) {
|
|
135
136
|
const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;
|
|
136
137
|
const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;
|
|
@@ -151,11 +152,16 @@ function createAuth(options = {}) {
|
|
|
151
152
|
],
|
|
152
153
|
callbacks: {
|
|
153
154
|
async jwt({ token, account, profile }) {
|
|
155
|
+
console.log("[OAuth42 SDK] JWT callback called", { hasAccount: !!account, hasProfile: !!profile });
|
|
154
156
|
if (account) {
|
|
157
|
+
console.log("[OAuth42 SDK] Initial sign in - storing tokens in JWT");
|
|
155
158
|
token.accessToken = account.access_token;
|
|
156
159
|
token.refreshToken = account.refresh_token;
|
|
157
160
|
token.expiresAt = account.expires_at;
|
|
158
161
|
token.idToken = account.id_token;
|
|
162
|
+
token.clientId = clientId;
|
|
163
|
+
token.clientSecret = clientSecret;
|
|
164
|
+
token.issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER;
|
|
159
165
|
}
|
|
160
166
|
if (profile) {
|
|
161
167
|
const oauth42Profile = profile;
|
|
@@ -166,11 +172,16 @@ function createAuth(options = {}) {
|
|
|
166
172
|
if (options.callbacks?.jwt) {
|
|
167
173
|
return options.callbacks.jwt({ token, account, profile });
|
|
168
174
|
}
|
|
175
|
+
console.log("[OAuth42 SDK] JWT callback complete, returning token");
|
|
169
176
|
return token;
|
|
170
177
|
},
|
|
171
178
|
async session({ session, token }) {
|
|
179
|
+
console.log("[OAuth42 SDK] Session callback called", { hasToken: !!token, hasSession: !!session });
|
|
172
180
|
session.accessToken = token.accessToken;
|
|
173
181
|
session.idToken = token.idToken;
|
|
182
|
+
if (token.error) {
|
|
183
|
+
session.error = token.error;
|
|
184
|
+
}
|
|
174
185
|
if (session.user) {
|
|
175
186
|
session.user.email = token.email;
|
|
176
187
|
session.user.name = token.name;
|
|
@@ -180,9 +191,9 @@ function createAuth(options = {}) {
|
|
|
180
191
|
if (options.callbacks?.session) {
|
|
181
192
|
return options.callbacks.session({ session, token });
|
|
182
193
|
}
|
|
194
|
+
console.log("[OAuth42 SDK] Session callback complete, returning session");
|
|
183
195
|
return session;
|
|
184
|
-
}
|
|
185
|
-
...options.callbacks
|
|
196
|
+
}
|
|
186
197
|
},
|
|
187
198
|
pages: {
|
|
188
199
|
signIn: "/auth/signin",
|
|
@@ -195,11 +206,78 @@ function createAuth(options = {}) {
|
|
|
195
206
|
...options.session
|
|
196
207
|
},
|
|
197
208
|
debug: options.debug || process.env.NODE_ENV === "development",
|
|
198
|
-
secret: process.env.NEXTAUTH_SECRET
|
|
209
|
+
secret: process.env.NEXTAUTH_SECRET,
|
|
210
|
+
// Configure unique cookie names per app to prevent session conflicts on localhost
|
|
211
|
+
...options.cookiePrefix && {
|
|
212
|
+
cookies: {
|
|
213
|
+
sessionToken: {
|
|
214
|
+
name: `${options.cookiePrefix}.session-token`,
|
|
215
|
+
options: {
|
|
216
|
+
httpOnly: true,
|
|
217
|
+
sameSite: "lax",
|
|
218
|
+
path: "/",
|
|
219
|
+
secure: process.env.NODE_ENV === "production"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
callbackUrl: {
|
|
223
|
+
name: `${options.cookiePrefix}.callback-url`,
|
|
224
|
+
options: {
|
|
225
|
+
httpOnly: true,
|
|
226
|
+
sameSite: "lax",
|
|
227
|
+
path: "/",
|
|
228
|
+
secure: process.env.NODE_ENV === "production"
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
csrfToken: {
|
|
232
|
+
name: `${options.cookiePrefix}.csrf-token`,
|
|
233
|
+
options: {
|
|
234
|
+
httpOnly: true,
|
|
235
|
+
sameSite: "lax",
|
|
236
|
+
path: "/",
|
|
237
|
+
secure: process.env.NODE_ENV === "production"
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
// PKCE code_verifier cookie - essential for PKCE flow
|
|
241
|
+
pkceCodeVerifier: {
|
|
242
|
+
name: `${options.cookiePrefix}.pkce.code_verifier`,
|
|
243
|
+
options: {
|
|
244
|
+
httpOnly: true,
|
|
245
|
+
sameSite: "lax",
|
|
246
|
+
path: "/",
|
|
247
|
+
secure: process.env.NODE_ENV === "production",
|
|
248
|
+
maxAge: 900
|
|
249
|
+
// 15 minutes
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
// State cookie for OAuth CSRF protection
|
|
253
|
+
state: {
|
|
254
|
+
name: `${options.cookiePrefix}.state`,
|
|
255
|
+
options: {
|
|
256
|
+
httpOnly: true,
|
|
257
|
+
sameSite: "lax",
|
|
258
|
+
path: "/",
|
|
259
|
+
secure: process.env.NODE_ENV === "production",
|
|
260
|
+
maxAge: 900
|
|
261
|
+
// 15 minutes
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
// Nonce cookie for OpenID Connect
|
|
265
|
+
nonce: {
|
|
266
|
+
name: `${options.cookiePrefix}.nonce`,
|
|
267
|
+
options: {
|
|
268
|
+
httpOnly: true,
|
|
269
|
+
sameSite: "lax",
|
|
270
|
+
path: "/",
|
|
271
|
+
secure: process.env.NODE_ENV === "production"
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
199
276
|
};
|
|
277
|
+
const handler = NextAuth(authOptions);
|
|
200
278
|
return {
|
|
201
279
|
auth: authOptions,
|
|
202
|
-
handlers:
|
|
280
|
+
handlers: { GET: handler, POST: handler }
|
|
203
281
|
};
|
|
204
282
|
}
|
|
205
283
|
function createHandlers(authOptions) {
|
|
@@ -208,6 +286,18 @@ function createHandlers(authOptions) {
|
|
|
208
286
|
}
|
|
209
287
|
var getServerSession = getOAuth42Session;
|
|
210
288
|
async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
289
|
+
if (activeRefresh) {
|
|
290
|
+
console.log("[OAuth42] Refresh already in progress, waiting...");
|
|
291
|
+
return await activeRefresh;
|
|
292
|
+
}
|
|
293
|
+
activeRefresh = doRefresh(token, clientId, clientSecret, issuer);
|
|
294
|
+
try {
|
|
295
|
+
return await activeRefresh;
|
|
296
|
+
} finally {
|
|
297
|
+
activeRefresh = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function doRefresh(token, clientId, clientSecret, issuer) {
|
|
211
301
|
try {
|
|
212
302
|
const baseUrl = issuer || process.env.OAUTH42_ISSUER || "https://oauth42.com";
|
|
213
303
|
const tokenUrl = `${baseUrl}/oauth2/token`;
|
|
@@ -234,6 +324,7 @@ async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
|
234
324
|
if (!response.ok) {
|
|
235
325
|
throw refreshedTokens;
|
|
236
326
|
}
|
|
327
|
+
console.log("[OAuth42] Token refreshed successfully");
|
|
237
328
|
return {
|
|
238
329
|
...token,
|
|
239
330
|
accessToken: refreshedTokens.access_token,
|
|
@@ -244,7 +335,7 @@ async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
|
244
335
|
error: void 0
|
|
245
336
|
};
|
|
246
337
|
} catch (error) {
|
|
247
|
-
console.error("Failed to refresh access token:", error);
|
|
338
|
+
console.error("[OAuth42] Failed to refresh access token:", error);
|
|
248
339
|
return {
|
|
249
340
|
...token,
|
|
250
341
|
error: "RefreshAccessTokenError"
|
|
@@ -258,11 +349,52 @@ var import_next_auth3 = __toESM(require("next-auth"));
|
|
|
258
349
|
// src/server/middleware.ts
|
|
259
350
|
var import_server = require("next/server");
|
|
260
351
|
var import_jwt = require("next-auth/jwt");
|
|
352
|
+
async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
353
|
+
try {
|
|
354
|
+
const tokenUrl = `${issuer}/oauth2/token`;
|
|
355
|
+
const response = await fetch(tokenUrl, {
|
|
356
|
+
method: "POST",
|
|
357
|
+
headers: {
|
|
358
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
359
|
+
},
|
|
360
|
+
body: new URLSearchParams({
|
|
361
|
+
grant_type: "refresh_token",
|
|
362
|
+
refresh_token: refreshToken,
|
|
363
|
+
client_id: clientId,
|
|
364
|
+
client_secret: clientSecret
|
|
365
|
+
})
|
|
366
|
+
});
|
|
367
|
+
const data = await response.json();
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
console.error("[OAuth42 Middleware] Token refresh failed:", data);
|
|
370
|
+
return { success: false, error: data.error || "refresh_failed" };
|
|
371
|
+
}
|
|
372
|
+
console.log("[OAuth42 Middleware] Token refreshed successfully");
|
|
373
|
+
return {
|
|
374
|
+
success: true,
|
|
375
|
+
accessToken: data.access_token,
|
|
376
|
+
refreshToken: data.refresh_token,
|
|
377
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (data.expires_in || 3600)
|
|
378
|
+
};
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error("[OAuth42 Middleware] Token refresh error:", error);
|
|
381
|
+
return { success: false, error: "refresh_error" };
|
|
382
|
+
}
|
|
383
|
+
}
|
|
261
384
|
function withOAuth42Auth(options = {}) {
|
|
385
|
+
const secret = process.env.NEXTAUTH_SECRET;
|
|
386
|
+
const clientId = process.env.OAUTH42_CLIENT_ID;
|
|
387
|
+
const clientSecret = process.env.OAUTH42_CLIENT_SECRET;
|
|
388
|
+
const issuer = process.env.OAUTH42_ISSUER || "https://localhost:8443";
|
|
389
|
+
if (!secret) {
|
|
390
|
+
console.warn("[OAuth42 Middleware] NEXTAUTH_SECRET not set");
|
|
391
|
+
}
|
|
262
392
|
return async function middleware(req) {
|
|
393
|
+
const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
|
|
263
394
|
const token = await (0, import_jwt.getToken)({
|
|
264
395
|
req,
|
|
265
|
-
secret
|
|
396
|
+
secret,
|
|
397
|
+
cookieName
|
|
266
398
|
});
|
|
267
399
|
const pathname = req.nextUrl.pathname;
|
|
268
400
|
if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
|
|
@@ -272,16 +404,62 @@ function withOAuth42Auth(options = {}) {
|
|
|
272
404
|
if (!needsProtection) {
|
|
273
405
|
return import_server.NextResponse.next();
|
|
274
406
|
}
|
|
275
|
-
|
|
276
|
-
if (options.callbacks?.authorized) {
|
|
277
|
-
isAuthorized = await options.callbacks.authorized({ token, req });
|
|
278
|
-
}
|
|
279
|
-
if (!isAuthorized) {
|
|
407
|
+
if (!token) {
|
|
280
408
|
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
281
409
|
const url = new URL(signInUrl, req.url);
|
|
282
410
|
url.searchParams.set("callbackUrl", pathname);
|
|
283
411
|
return import_server.NextResponse.redirect(url);
|
|
284
412
|
}
|
|
413
|
+
const expiresAt = token.expiresAt;
|
|
414
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
415
|
+
const bufferSeconds = 60;
|
|
416
|
+
const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;
|
|
417
|
+
if (needsRefresh && token.refreshToken && clientId && clientSecret) {
|
|
418
|
+
console.log("[OAuth42 Middleware] Access token expired, refreshing...");
|
|
419
|
+
const refreshed = await refreshTokens(
|
|
420
|
+
token.refreshToken,
|
|
421
|
+
clientId,
|
|
422
|
+
clientSecret,
|
|
423
|
+
issuer
|
|
424
|
+
);
|
|
425
|
+
if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {
|
|
426
|
+
const updatedToken = {
|
|
427
|
+
...token,
|
|
428
|
+
accessToken: refreshed.accessToken,
|
|
429
|
+
refreshToken: refreshed.refreshToken,
|
|
430
|
+
expiresAt: refreshed.expiresAt
|
|
431
|
+
};
|
|
432
|
+
const newJwt = await (0, import_jwt.encode)({
|
|
433
|
+
token: updatedToken,
|
|
434
|
+
secret
|
|
435
|
+
});
|
|
436
|
+
const response = import_server.NextResponse.next();
|
|
437
|
+
response.cookies.set(cookieName, newJwt, {
|
|
438
|
+
httpOnly: true,
|
|
439
|
+
sameSite: "lax",
|
|
440
|
+
path: "/",
|
|
441
|
+
secure: process.env.NODE_ENV === "production"
|
|
442
|
+
});
|
|
443
|
+
console.log("[OAuth42 Middleware] Cookie updated with refreshed tokens");
|
|
444
|
+
return response;
|
|
445
|
+
} else {
|
|
446
|
+
console.error("[OAuth42 Middleware] Refresh failed, redirecting to sign in");
|
|
447
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
448
|
+
const url = new URL(signInUrl, req.url);
|
|
449
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
450
|
+
url.searchParams.set("error", "RefreshAccessTokenError");
|
|
451
|
+
return import_server.NextResponse.redirect(url);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (options.callbacks?.authorized) {
|
|
455
|
+
const isAuthorized = await options.callbacks.authorized({ token, req });
|
|
456
|
+
if (!isAuthorized) {
|
|
457
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
458
|
+
const url = new URL(signInUrl, req.url);
|
|
459
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
460
|
+
return import_server.NextResponse.redirect(url);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
285
463
|
return import_server.NextResponse.next();
|
|
286
464
|
};
|
|
287
465
|
}
|