@oauth42/next 0.2.4 → 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 +42 -8
- package/dist/client/index.d.ts +42 -8
- package/dist/client/index.js +15 -12
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +14 -12
- 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 +190 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -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 +190 -11
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +193 -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,22 +172,28 @@ 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;
|
|
187
|
+
session.user.name = token.name;
|
|
176
188
|
session.user.username = token.username;
|
|
177
189
|
session.user.emailVerified = token.emailVerified;
|
|
178
190
|
}
|
|
179
191
|
if (options.callbacks?.session) {
|
|
180
192
|
return options.callbacks.session({ session, token });
|
|
181
193
|
}
|
|
194
|
+
console.log("[OAuth42 SDK] Session callback complete, returning session");
|
|
182
195
|
return session;
|
|
183
|
-
}
|
|
184
|
-
...options.callbacks
|
|
196
|
+
}
|
|
185
197
|
},
|
|
186
198
|
pages: {
|
|
187
199
|
signIn: "/auth/signin",
|
|
@@ -194,11 +206,78 @@ function createAuth(options = {}) {
|
|
|
194
206
|
...options.session
|
|
195
207
|
},
|
|
196
208
|
debug: options.debug || process.env.NODE_ENV === "development",
|
|
197
|
-
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
|
+
}
|
|
198
276
|
};
|
|
277
|
+
const handler = NextAuth(authOptions);
|
|
199
278
|
return {
|
|
200
279
|
auth: authOptions,
|
|
201
|
-
handlers:
|
|
280
|
+
handlers: { GET: handler, POST: handler }
|
|
202
281
|
};
|
|
203
282
|
}
|
|
204
283
|
function createHandlers(authOptions) {
|
|
@@ -207,6 +286,18 @@ function createHandlers(authOptions) {
|
|
|
207
286
|
}
|
|
208
287
|
var getServerSession = getOAuth42Session;
|
|
209
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) {
|
|
210
301
|
try {
|
|
211
302
|
const baseUrl = issuer || process.env.OAUTH42_ISSUER || "https://oauth42.com";
|
|
212
303
|
const tokenUrl = `${baseUrl}/oauth2/token`;
|
|
@@ -233,6 +324,7 @@ async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
|
233
324
|
if (!response.ok) {
|
|
234
325
|
throw refreshedTokens;
|
|
235
326
|
}
|
|
327
|
+
console.log("[OAuth42] Token refreshed successfully");
|
|
236
328
|
return {
|
|
237
329
|
...token,
|
|
238
330
|
accessToken: refreshedTokens.access_token,
|
|
@@ -243,7 +335,7 @@ async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
|
243
335
|
error: void 0
|
|
244
336
|
};
|
|
245
337
|
} catch (error) {
|
|
246
|
-
console.error("Failed to refresh access token:", error);
|
|
338
|
+
console.error("[OAuth42] Failed to refresh access token:", error);
|
|
247
339
|
return {
|
|
248
340
|
...token,
|
|
249
341
|
error: "RefreshAccessTokenError"
|
|
@@ -257,11 +349,52 @@ var import_next_auth3 = __toESM(require("next-auth"));
|
|
|
257
349
|
// src/server/middleware.ts
|
|
258
350
|
var import_server = require("next/server");
|
|
259
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
|
+
}
|
|
260
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
|
+
}
|
|
261
392
|
return async function middleware(req) {
|
|
393
|
+
const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
|
|
262
394
|
const token = await (0, import_jwt.getToken)({
|
|
263
395
|
req,
|
|
264
|
-
secret
|
|
396
|
+
secret,
|
|
397
|
+
cookieName
|
|
265
398
|
});
|
|
266
399
|
const pathname = req.nextUrl.pathname;
|
|
267
400
|
if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
|
|
@@ -271,16 +404,62 @@ function withOAuth42Auth(options = {}) {
|
|
|
271
404
|
if (!needsProtection) {
|
|
272
405
|
return import_server.NextResponse.next();
|
|
273
406
|
}
|
|
274
|
-
|
|
275
|
-
if (options.callbacks?.authorized) {
|
|
276
|
-
isAuthorized = await options.callbacks.authorized({ token, req });
|
|
277
|
-
}
|
|
278
|
-
if (!isAuthorized) {
|
|
407
|
+
if (!token) {
|
|
279
408
|
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
280
409
|
const url = new URL(signInUrl, req.url);
|
|
281
410
|
url.searchParams.set("callbackUrl", pathname);
|
|
282
411
|
return import_server.NextResponse.redirect(url);
|
|
283
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
|
+
}
|
|
284
463
|
return import_server.NextResponse.next();
|
|
285
464
|
};
|
|
286
465
|
}
|