@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.
@@ -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,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: NextAuth(authOptions)
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: process.env.NEXTAUTH_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
- let isAuthorized = !!token;
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
  }