@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.
@@ -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":[]}
@@ -1,5 +1,6 @@
1
- export { C as CreateAuthOptions, e as OAuth42AuthOptions, O as OAuth42Provider, c as createAuth, j as createHandlers, d as createMiddlewareConfig, f as getOAuth42Session, g as getServerSession, r as refreshAccessToken, w as withOAuth42Auth, i as withOAuth42ServerSideProps, h as withOAuth42Session } from '../middleware-B8dYrjZ1.mjs';
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';
@@ -1,5 +1,6 @@
1
- export { C as CreateAuthOptions, e as OAuth42AuthOptions, O as OAuth42Provider, c as createAuth, j as createHandlers, d as createMiddlewareConfig, f as getOAuth42Session, g as getServerSession, r as refreshAccessToken, w as withOAuth42Auth, i as withOAuth42ServerSideProps, h as withOAuth42Session } from '../middleware-B8dYrjZ1.js';
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';
@@ -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: NextAuth(authOptions)
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: process.env.NEXTAUTH_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
- let isAuthorized = !!token;
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
  }