@oauth42/next 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{middleware-B8dYrjZ1.d.mts → auth-C401ZFad.d.mts} +17 -31
- package/dist/{middleware-B8dYrjZ1.d.ts → auth-C401ZFad.d.ts} +17 -31
- package/dist/client/index.d.mts +15 -7
- package/dist/client/index.d.ts +15 -7
- package/dist/client/index.js +13 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +13 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +189 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +190 -12
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/index.d.mts +39 -0
- package/dist/middleware/index.d.ts +39 -0
- package/dist/middleware/index.js +166 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/index.mjs +138 -0
- package/dist/middleware/index.mjs.map +1 -0
- package/dist/server/index.d.mts +2 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +189 -11
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +192 -14
- package/dist/server/index.mjs.map +1 -1
- package/package.json +6 -1
- package/src/types/next-auth.d.ts +2 -0
package/dist/index.mjs
CHANGED
|
@@ -84,6 +84,7 @@ function withOAuth42ServerSideProps(getServerSideProps, authOptions) {
|
|
|
84
84
|
|
|
85
85
|
// src/server/auth.ts
|
|
86
86
|
var NextAuth = NextAuthDefault.default || NextAuthDefault;
|
|
87
|
+
var activeRefresh = null;
|
|
87
88
|
function createAuth(options = {}) {
|
|
88
89
|
const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;
|
|
89
90
|
const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;
|
|
@@ -104,11 +105,16 @@ function createAuth(options = {}) {
|
|
|
104
105
|
],
|
|
105
106
|
callbacks: {
|
|
106
107
|
async jwt({ token, account, profile }) {
|
|
108
|
+
console.log("[OAuth42 SDK] JWT callback called", { hasAccount: !!account, hasProfile: !!profile });
|
|
107
109
|
if (account) {
|
|
110
|
+
console.log("[OAuth42 SDK] Initial sign in - storing tokens in JWT");
|
|
108
111
|
token.accessToken = account.access_token;
|
|
109
112
|
token.refreshToken = account.refresh_token;
|
|
110
113
|
token.expiresAt = account.expires_at;
|
|
111
114
|
token.idToken = account.id_token;
|
|
115
|
+
token.clientId = clientId;
|
|
116
|
+
token.clientSecret = clientSecret;
|
|
117
|
+
token.issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER;
|
|
112
118
|
}
|
|
113
119
|
if (profile) {
|
|
114
120
|
const oauth42Profile = profile;
|
|
@@ -119,11 +125,16 @@ function createAuth(options = {}) {
|
|
|
119
125
|
if (options.callbacks?.jwt) {
|
|
120
126
|
return options.callbacks.jwt({ token, account, profile });
|
|
121
127
|
}
|
|
128
|
+
console.log("[OAuth42 SDK] JWT callback complete, returning token");
|
|
122
129
|
return token;
|
|
123
130
|
},
|
|
124
131
|
async session({ session, token }) {
|
|
132
|
+
console.log("[OAuth42 SDK] Session callback called", { hasToken: !!token, hasSession: !!session });
|
|
125
133
|
session.accessToken = token.accessToken;
|
|
126
134
|
session.idToken = token.idToken;
|
|
135
|
+
if (token.error) {
|
|
136
|
+
session.error = token.error;
|
|
137
|
+
}
|
|
127
138
|
if (session.user) {
|
|
128
139
|
session.user.email = token.email;
|
|
129
140
|
session.user.name = token.name;
|
|
@@ -133,9 +144,9 @@ function createAuth(options = {}) {
|
|
|
133
144
|
if (options.callbacks?.session) {
|
|
134
145
|
return options.callbacks.session({ session, token });
|
|
135
146
|
}
|
|
147
|
+
console.log("[OAuth42 SDK] Session callback complete, returning session");
|
|
136
148
|
return session;
|
|
137
|
-
}
|
|
138
|
-
...options.callbacks
|
|
149
|
+
}
|
|
139
150
|
},
|
|
140
151
|
pages: {
|
|
141
152
|
signIn: "/auth/signin",
|
|
@@ -148,15 +159,94 @@ function createAuth(options = {}) {
|
|
|
148
159
|
...options.session
|
|
149
160
|
},
|
|
150
161
|
debug: options.debug || process.env.NODE_ENV === "development",
|
|
151
|
-
secret: process.env.NEXTAUTH_SECRET
|
|
162
|
+
secret: process.env.NEXTAUTH_SECRET,
|
|
163
|
+
// Configure unique cookie names per app to prevent session conflicts on localhost
|
|
164
|
+
...options.cookiePrefix && {
|
|
165
|
+
cookies: {
|
|
166
|
+
sessionToken: {
|
|
167
|
+
name: `${options.cookiePrefix}.session-token`,
|
|
168
|
+
options: {
|
|
169
|
+
httpOnly: true,
|
|
170
|
+
sameSite: "lax",
|
|
171
|
+
path: "/",
|
|
172
|
+
secure: process.env.NODE_ENV === "production"
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
callbackUrl: {
|
|
176
|
+
name: `${options.cookiePrefix}.callback-url`,
|
|
177
|
+
options: {
|
|
178
|
+
httpOnly: true,
|
|
179
|
+
sameSite: "lax",
|
|
180
|
+
path: "/",
|
|
181
|
+
secure: process.env.NODE_ENV === "production"
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
csrfToken: {
|
|
185
|
+
name: `${options.cookiePrefix}.csrf-token`,
|
|
186
|
+
options: {
|
|
187
|
+
httpOnly: true,
|
|
188
|
+
sameSite: "lax",
|
|
189
|
+
path: "/",
|
|
190
|
+
secure: process.env.NODE_ENV === "production"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
// PKCE code_verifier cookie - essential for PKCE flow
|
|
194
|
+
pkceCodeVerifier: {
|
|
195
|
+
name: `${options.cookiePrefix}.pkce.code_verifier`,
|
|
196
|
+
options: {
|
|
197
|
+
httpOnly: true,
|
|
198
|
+
sameSite: "lax",
|
|
199
|
+
path: "/",
|
|
200
|
+
secure: process.env.NODE_ENV === "production",
|
|
201
|
+
maxAge: 900
|
|
202
|
+
// 15 minutes
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
// State cookie for OAuth CSRF protection
|
|
206
|
+
state: {
|
|
207
|
+
name: `${options.cookiePrefix}.state`,
|
|
208
|
+
options: {
|
|
209
|
+
httpOnly: true,
|
|
210
|
+
sameSite: "lax",
|
|
211
|
+
path: "/",
|
|
212
|
+
secure: process.env.NODE_ENV === "production",
|
|
213
|
+
maxAge: 900
|
|
214
|
+
// 15 minutes
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
// Nonce cookie for OpenID Connect
|
|
218
|
+
nonce: {
|
|
219
|
+
name: `${options.cookiePrefix}.nonce`,
|
|
220
|
+
options: {
|
|
221
|
+
httpOnly: true,
|
|
222
|
+
sameSite: "lax",
|
|
223
|
+
path: "/",
|
|
224
|
+
secure: process.env.NODE_ENV === "production"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
152
229
|
};
|
|
230
|
+
const handler = NextAuth(authOptions);
|
|
153
231
|
return {
|
|
154
232
|
auth: authOptions,
|
|
155
|
-
handlers:
|
|
233
|
+
handlers: { GET: handler, POST: handler }
|
|
156
234
|
};
|
|
157
235
|
}
|
|
158
236
|
var getServerSession = getOAuth42Session;
|
|
159
237
|
async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
238
|
+
if (activeRefresh) {
|
|
239
|
+
console.log("[OAuth42] Refresh already in progress, waiting...");
|
|
240
|
+
return await activeRefresh;
|
|
241
|
+
}
|
|
242
|
+
activeRefresh = doRefresh(token, clientId, clientSecret, issuer);
|
|
243
|
+
try {
|
|
244
|
+
return await activeRefresh;
|
|
245
|
+
} finally {
|
|
246
|
+
activeRefresh = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function doRefresh(token, clientId, clientSecret, issuer) {
|
|
160
250
|
try {
|
|
161
251
|
const baseUrl = issuer || process.env.OAUTH42_ISSUER || "https://oauth42.com";
|
|
162
252
|
const tokenUrl = `${baseUrl}/oauth2/token`;
|
|
@@ -183,6 +273,7 @@ async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
|
183
273
|
if (!response.ok) {
|
|
184
274
|
throw refreshedTokens;
|
|
185
275
|
}
|
|
276
|
+
console.log("[OAuth42] Token refreshed successfully");
|
|
186
277
|
return {
|
|
187
278
|
...token,
|
|
188
279
|
accessToken: refreshedTokens.access_token,
|
|
@@ -193,7 +284,7 @@ async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
|
193
284
|
error: void 0
|
|
194
285
|
};
|
|
195
286
|
} catch (error) {
|
|
196
|
-
console.error("Failed to refresh access token:", error);
|
|
287
|
+
console.error("[OAuth42] Failed to refresh access token:", error);
|
|
197
288
|
return {
|
|
198
289
|
...token,
|
|
199
290
|
error: "RefreshAccessTokenError"
|
|
@@ -203,12 +294,53 @@ async function refreshAccessToken(token, clientId, clientSecret, issuer) {
|
|
|
203
294
|
|
|
204
295
|
// src/server/middleware.ts
|
|
205
296
|
import { NextResponse } from "next/server";
|
|
206
|
-
import { getToken } from "next-auth/jwt";
|
|
297
|
+
import { getToken, encode } from "next-auth/jwt";
|
|
298
|
+
async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
299
|
+
try {
|
|
300
|
+
const tokenUrl = `${issuer}/oauth2/token`;
|
|
301
|
+
const response = await fetch(tokenUrl, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
305
|
+
},
|
|
306
|
+
body: new URLSearchParams({
|
|
307
|
+
grant_type: "refresh_token",
|
|
308
|
+
refresh_token: refreshToken,
|
|
309
|
+
client_id: clientId,
|
|
310
|
+
client_secret: clientSecret
|
|
311
|
+
})
|
|
312
|
+
});
|
|
313
|
+
const data = await response.json();
|
|
314
|
+
if (!response.ok) {
|
|
315
|
+
console.error("[OAuth42 Middleware] Token refresh failed:", data);
|
|
316
|
+
return { success: false, error: data.error || "refresh_failed" };
|
|
317
|
+
}
|
|
318
|
+
console.log("[OAuth42 Middleware] Token refreshed successfully");
|
|
319
|
+
return {
|
|
320
|
+
success: true,
|
|
321
|
+
accessToken: data.access_token,
|
|
322
|
+
refreshToken: data.refresh_token,
|
|
323
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (data.expires_in || 3600)
|
|
324
|
+
};
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error("[OAuth42 Middleware] Token refresh error:", error);
|
|
327
|
+
return { success: false, error: "refresh_error" };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
207
330
|
function withOAuth42Auth(options = {}) {
|
|
331
|
+
const secret = process.env.NEXTAUTH_SECRET;
|
|
332
|
+
const clientId = process.env.OAUTH42_CLIENT_ID;
|
|
333
|
+
const clientSecret = process.env.OAUTH42_CLIENT_SECRET;
|
|
334
|
+
const issuer = process.env.OAUTH42_ISSUER || "https://localhost:8443";
|
|
335
|
+
if (!secret) {
|
|
336
|
+
console.warn("[OAuth42 Middleware] NEXTAUTH_SECRET not set");
|
|
337
|
+
}
|
|
208
338
|
return async function middleware(req) {
|
|
339
|
+
const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
|
|
209
340
|
const token = await getToken({
|
|
210
341
|
req,
|
|
211
|
-
secret
|
|
342
|
+
secret,
|
|
343
|
+
cookieName
|
|
212
344
|
});
|
|
213
345
|
const pathname = req.nextUrl.pathname;
|
|
214
346
|
if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
|
|
@@ -218,16 +350,62 @@ function withOAuth42Auth(options = {}) {
|
|
|
218
350
|
if (!needsProtection) {
|
|
219
351
|
return NextResponse.next();
|
|
220
352
|
}
|
|
221
|
-
|
|
222
|
-
if (options.callbacks?.authorized) {
|
|
223
|
-
isAuthorized = await options.callbacks.authorized({ token, req });
|
|
224
|
-
}
|
|
225
|
-
if (!isAuthorized) {
|
|
353
|
+
if (!token) {
|
|
226
354
|
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
227
355
|
const url = new URL(signInUrl, req.url);
|
|
228
356
|
url.searchParams.set("callbackUrl", pathname);
|
|
229
357
|
return NextResponse.redirect(url);
|
|
230
358
|
}
|
|
359
|
+
const expiresAt = token.expiresAt;
|
|
360
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
361
|
+
const bufferSeconds = 60;
|
|
362
|
+
const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;
|
|
363
|
+
if (needsRefresh && token.refreshToken && clientId && clientSecret) {
|
|
364
|
+
console.log("[OAuth42 Middleware] Access token expired, refreshing...");
|
|
365
|
+
const refreshed = await refreshTokens(
|
|
366
|
+
token.refreshToken,
|
|
367
|
+
clientId,
|
|
368
|
+
clientSecret,
|
|
369
|
+
issuer
|
|
370
|
+
);
|
|
371
|
+
if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {
|
|
372
|
+
const updatedToken = {
|
|
373
|
+
...token,
|
|
374
|
+
accessToken: refreshed.accessToken,
|
|
375
|
+
refreshToken: refreshed.refreshToken,
|
|
376
|
+
expiresAt: refreshed.expiresAt
|
|
377
|
+
};
|
|
378
|
+
const newJwt = await encode({
|
|
379
|
+
token: updatedToken,
|
|
380
|
+
secret
|
|
381
|
+
});
|
|
382
|
+
const response = NextResponse.next();
|
|
383
|
+
response.cookies.set(cookieName, newJwt, {
|
|
384
|
+
httpOnly: true,
|
|
385
|
+
sameSite: "lax",
|
|
386
|
+
path: "/",
|
|
387
|
+
secure: process.env.NODE_ENV === "production"
|
|
388
|
+
});
|
|
389
|
+
console.log("[OAuth42 Middleware] Cookie updated with refreshed tokens");
|
|
390
|
+
return response;
|
|
391
|
+
} else {
|
|
392
|
+
console.error("[OAuth42 Middleware] Refresh failed, redirecting to sign in");
|
|
393
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
394
|
+
const url = new URL(signInUrl, req.url);
|
|
395
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
396
|
+
url.searchParams.set("error", "RefreshAccessTokenError");
|
|
397
|
+
return NextResponse.redirect(url);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (options.callbacks?.authorized) {
|
|
401
|
+
const isAuthorized = await options.callbacks.authorized({ token, req });
|
|
402
|
+
if (!isAuthorized) {
|
|
403
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
404
|
+
const url = new URL(signInUrl, req.url);
|
|
405
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
406
|
+
return NextResponse.redirect(url);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
231
409
|
return NextResponse.next();
|
|
232
410
|
};
|
|
233
411
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.ts","../src/server/auth.ts","../src/server/session.ts","../src/server/middleware.ts"],"sourcesContent":["import type { OAuthConfig, OAuthUserConfig } from 'next-auth/providers/oauth';\n\nexport interface OAuth42Profile {\n sub: string;\n email: string;\n email_verified?: boolean;\n name?: string;\n given_name?: string;\n family_name?: string;\n picture?: string;\n username?: string;\n id?: string;\n}\n\nexport interface OAuth42ProviderOptions {\n clientId: string;\n clientSecret: string;\n issuer?: string;\n authorizationUrl?: string;\n tokenUrl?: string;\n userinfoUrl?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n}\n\nexport function OAuth42Provider<P extends OAuth42Profile>(\n options: OAuthUserConfig<P> & Partial<OAuth42ProviderOptions>\n): OAuthConfig<P> {\n const issuer = options.issuer || process.env.OAUTH42_ISSUER || 'https://oauth42.com';\n const baseUrl = issuer.replace(/\\/$/, '');\n \n return {\n id: 'oauth42',\n name: 'OAuth42',\n type: 'oauth',\n version: '2.0',\n \n // Use OIDC discovery to automatically find endpoints\n wellKnown: `${baseUrl}/.well-known/openid-configuration`,\n \n // Also set individual endpoints for compatibility\n authorization: {\n url: `${baseUrl}/oauth2/authorize`,\n params: {\n scope: (options.scopes || ['openid', 'profile', 'email']).join(' '),\n response_type: 'code',\n },\n },\n token: `${baseUrl}/oauth2/token`,\n userinfo: `${baseUrl}/oauth2/userinfo`,\n \n client: {\n id: options.clientId,\n secret: options.clientSecret,\n token_endpoint_auth_method: 'client_secret_post',\n id_token_signed_response_alg: 'HS256', // OAuth42 uses HS256 for ID tokens\n },\n \n issuer: baseUrl,\n \n checks: options.pkceEnabled !== false ? ['pkce', 'state'] : ['state'],\n \n profile(profile: OAuth42Profile, tokens: any) {\n return {\n id: profile.sub || profile.id || profile.email,\n email: profile.email,\n emailVerified: profile.email_verified ? new Date() : null,\n name: profile.name || `${profile.given_name || ''} ${profile.family_name || ''}`.trim(),\n image: profile.picture,\n };\n },\n \n style: {\n logo: '/oauth42-logo.svg',\n bg: '#1e40af',\n text: '#ffffff',\n },\n \n options,\n };\n}","import NextAuthDefault from 'next-auth';\nimport type { NextAuthOptions } from 'next-auth';\nimport { OAuth42Provider, OAuth42Profile } from '../provider';\nimport { getOAuth42Session } from './session';\n\n// Handle both CommonJS and ESM exports\nconst NextAuth = (NextAuthDefault as any).default || NextAuthDefault;\n\nexport { type NextAuthOptions };\n\nexport interface CreateAuthOptions {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n debug?: boolean;\n callbacks?: NextAuthOptions['callbacks'];\n pages?: NextAuthOptions['pages'];\n session?: NextAuthOptions['session'];\n}\n\n/**\n * Create a pre-configured NextAuth instance for OAuth42\n * This provides a simplified setup with sensible defaults\n */\nexport function createAuth(options: CreateAuthOptions = {}) {\n const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;\n const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;\n \n if (!clientId || !clientSecret) {\n throw new Error(\n 'OAuth42 client credentials are required. ' +\n 'Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables ' +\n 'or pass them in the options.'\n );\n }\n \n const authOptions: NextAuthOptions = {\n providers: [\n OAuth42Provider({\n clientId,\n clientSecret,\n issuer: options.issuer,\n scopes: options.scopes,\n pkceEnabled: options.pkceEnabled,\n }),\n ],\n \n callbacks: {\n async jwt({ token, account, profile }) {\n // Store OAuth tokens in the JWT\n if (account) {\n token.accessToken = account.access_token;\n token.refreshToken = account.refresh_token;\n token.expiresAt = account.expires_at;\n token.idToken = account.id_token;\n }\n \n // Add user profile data\n if (profile) {\n const oauth42Profile = profile as OAuth42Profile;\n token.email = oauth42Profile.email;\n token.username = oauth42Profile.username;\n token.emailVerified = oauth42Profile.email_verified;\n }\n \n // Call custom callback if provided\n if (options.callbacks?.jwt) {\n return options.callbacks.jwt({ token, account, profile } as any);\n }\n \n return token;\n },\n \n async session({ session, token }) {\n // Add OAuth42-specific data to session\n session.accessToken = token.accessToken as string;\n session.idToken = token.idToken as string;\n\n if (session.user) {\n session.user.email = token.email as string;\n session.user.name = token.name as string;\n session.user.username = token.username as string;\n session.user.emailVerified = token.emailVerified as boolean;\n }\n\n // Call custom callback if provided\n if (options.callbacks?.session) {\n return options.callbacks.session({ session, token } as any);\n }\n\n return session;\n },\n \n ...options.callbacks,\n },\n \n pages: {\n signIn: '/auth/signin',\n signOut: '/auth/signout',\n error: '/auth/error',\n ...options.pages,\n },\n \n session: {\n strategy: 'jwt',\n ...options.session,\n },\n \n debug: options.debug || process.env.NODE_ENV === 'development',\n \n secret: process.env.NEXTAUTH_SECRET,\n };\n \n // Return the configuration and a function to create handlers\n return {\n auth: authOptions,\n handlers: NextAuth(authOptions),\n };\n}\n\n/**\n * Create NextAuth handlers for API routes\n */\nexport function createHandlers(authOptions: NextAuthOptions) {\n const handler = NextAuth(authOptions);\n return { GET: handler, POST: handler };\n}\n\n/**\n * Helper to get the current session server-side\n * @deprecated Use getOAuth42Session instead - this is now just an alias for backward compatibility\n * \n * This function is maintained for backward compatibility but internally\n * calls getOAuth42Session which properly handles both App Router and Pages Router\n */\nexport const getServerSession = getOAuth42Session;\n\n/**\n * Token refresh helper\n */\nexport async function refreshAccessToken(token: any, clientId: string, clientSecret: string, issuer?: string) {\n try {\n const baseUrl = issuer || process.env.OAUTH42_ISSUER || 'https://oauth42.com';\n const tokenUrl = `${baseUrl}/oauth2/token`;\n \n // In development, we need to handle self-signed certificates\n const fetchOptions: any = {\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: token.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n };\n \n // Add agent for self-signed certificates in development\n if (process.env.NODE_ENV !== 'production' && tokenUrl.startsWith('https://')) {\n const https = await import('https');\n fetchOptions.agent = new https.Agent({\n rejectUnauthorized: false\n });\n }\n \n const response = await fetch(tokenUrl, fetchOptions);\n const refreshedTokens = await response.json();\n \n if (!response.ok) {\n throw refreshedTokens;\n }\n \n return {\n ...token,\n accessToken: refreshedTokens.access_token,\n refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,\n // Store expiration time in seconds (Unix timestamp)\n expiresAt: Math.floor(Date.now() / 1000) + (refreshedTokens.expires_in || 3600),\n // Explicitly remove any error property on successful refresh\n error: undefined,\n };\n } catch (error) {\n console.error('Failed to refresh access token:', error);\n return {\n ...token,\n error: 'RefreshAccessTokenError',\n };\n }\n}","import { getServerSession as getNextAuthSession } from 'next-auth';\nimport { NextAuthOptions } from 'next-auth';\nimport { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';\n\n/**\n * Get the OAuth42 session server-side\n * \n * This is the primary method for retrieving sessions in OAuth42 SDK.\n * Supports both Pages Router and App Router:\n * \n * App Router:\n * ```ts\n * const session = await getOAuth42Session(authOptions);\n * ```\n * \n * Pages Router:\n * ```ts\n * const session = await getOAuth42Session(req, res, authOptions);\n * ```\n */\nexport async function getOAuth42Session(\n ...args: \n | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions]\n | [NextApiRequest, NextApiResponse, NextAuthOptions]\n | [NextAuthOptions]\n) {\n return getNextAuthSession(...args as any);\n}\n\n/**\n * Helper for protecting API routes\n */\nexport function withOAuth42Session(\n handler: (req: NextApiRequest, res: NextApiResponse, session: any) => Promise<void> | void,\n authOptions: NextAuthOptions\n) {\n return async (req: NextApiRequest, res: NextApiResponse) => {\n const session = await getOAuth42Session(req, res, authOptions);\n \n if (!session) {\n return res.status(401).json({ error: 'Unauthorized' });\n }\n \n return handler(req, res, session);\n };\n}\n\n/**\n * Helper for protecting server-side props\n */\nexport function withOAuth42ServerSideProps(\n getServerSideProps: (\n context: GetServerSidePropsContext,\n session: any\n ) => Promise<any>,\n authOptions: NextAuthOptions\n) {\n return async (context: GetServerSidePropsContext) => {\n const session = await getOAuth42Session(\n context.req,\n context.res,\n authOptions\n );\n \n if (!session) {\n return {\n redirect: {\n destination: '/auth/signin',\n permanent: false,\n },\n };\n }\n \n return getServerSideProps(context, session);\n };\n}","import { NextRequest, NextResponse } from 'next/server';\nimport { getToken } 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\n/**\n * Middleware helper for protecting routes with OAuth42\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n return async function middleware(req: NextRequest) {\n const token = await getToken({ \n req: req as any, \n secret: process.env.NEXTAUTH_SECRET \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 // Check authorization\n let isAuthorized = !!token;\n \n if (options.callbacks?.authorized) {\n isAuthorized = await options.callbacks.authorized({ token, req });\n }\n \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 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}"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts","../src/server/auth.ts","../src/server/session.ts","../src/server/middleware.ts"],"sourcesContent":["import type { OAuthConfig, OAuthUserConfig } from 'next-auth/providers/oauth';\n\nexport interface OAuth42Profile {\n sub: string;\n email: string;\n email_verified?: boolean;\n name?: string;\n given_name?: string;\n family_name?: string;\n picture?: string;\n username?: string;\n id?: string;\n}\n\nexport interface OAuth42ProviderOptions {\n clientId: string;\n clientSecret: string;\n issuer?: string;\n authorizationUrl?: string;\n tokenUrl?: string;\n userinfoUrl?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n}\n\nexport function OAuth42Provider<P extends OAuth42Profile>(\n options: OAuthUserConfig<P> & Partial<OAuth42ProviderOptions>\n): OAuthConfig<P> {\n const issuer = options.issuer || process.env.OAUTH42_ISSUER || 'https://oauth42.com';\n const baseUrl = issuer.replace(/\\/$/, '');\n \n return {\n id: 'oauth42',\n name: 'OAuth42',\n type: 'oauth',\n version: '2.0',\n \n // Use OIDC discovery to automatically find endpoints\n wellKnown: `${baseUrl}/.well-known/openid-configuration`,\n \n // Also set individual endpoints for compatibility\n authorization: {\n url: `${baseUrl}/oauth2/authorize`,\n params: {\n scope: (options.scopes || ['openid', 'profile', 'email']).join(' '),\n response_type: 'code',\n },\n },\n token: `${baseUrl}/oauth2/token`,\n userinfo: `${baseUrl}/oauth2/userinfo`,\n \n client: {\n id: options.clientId,\n secret: options.clientSecret,\n token_endpoint_auth_method: 'client_secret_post',\n id_token_signed_response_alg: 'HS256', // OAuth42 uses HS256 for ID tokens\n },\n \n issuer: baseUrl,\n \n checks: options.pkceEnabled !== false ? ['pkce', 'state'] : ['state'],\n \n profile(profile: OAuth42Profile, tokens: any) {\n return {\n id: profile.sub || profile.id || profile.email,\n email: profile.email,\n emailVerified: profile.email_verified ? new Date() : null,\n name: profile.name || `${profile.given_name || ''} ${profile.family_name || ''}`.trim(),\n image: profile.picture,\n };\n },\n \n style: {\n logo: '/oauth42-logo.svg',\n bg: '#1e40af',\n text: '#ffffff',\n },\n \n options,\n };\n}","import NextAuthDefault from 'next-auth';\nimport type { NextAuthOptions } from 'next-auth';\nimport { OAuth42Provider, OAuth42Profile } from '../provider';\nimport { getOAuth42Session } from './session';\n\n// Handle both CommonJS and ESM exports\nconst NextAuth = (NextAuthDefault as any).default || NextAuthDefault;\n\nexport { type NextAuthOptions };\n\n// Simple per-process lock for explicit refresh via getAccessToken()\nlet activeRefresh: Promise<any> | null = null;\n\nexport interface CreateAuthOptions {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n scopes?: string[];\n pkceEnabled?: boolean;\n debug?: boolean;\n callbacks?: NextAuthOptions['callbacks'];\n pages?: NextAuthOptions['pages'];\n session?: NextAuthOptions['session'];\n /**\n * Unique prefix for cookie names to allow multiple apps on the same domain.\n * Each app should use a different prefix (e.g., 'portal', 'admin', 'bond').\n * This prevents session cookie conflicts when running multiple apps on localhost.\n */\n cookiePrefix?: string;\n}\n\n/**\n * Create a pre-configured NextAuth instance for OAuth42\n * This provides a simplified setup with sensible defaults\n */\nexport function createAuth(options: CreateAuthOptions = {}) {\n const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;\n const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;\n \n if (!clientId || !clientSecret) {\n throw new Error(\n 'OAuth42 client credentials are required. ' +\n 'Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables ' +\n 'or pass them in the options.'\n );\n }\n \n const authOptions: NextAuthOptions = {\n providers: [\n OAuth42Provider({\n clientId,\n clientSecret,\n issuer: options.issuer,\n scopes: options.scopes,\n pkceEnabled: options.pkceEnabled,\n }),\n ],\n \n callbacks: {\n async jwt({ token, account, profile }) {\n console.log('[OAuth42 SDK] JWT callback called', { hasAccount: !!account, hasProfile: !!profile });\n\n // Initial sign in - store OAuth tokens in the JWT\n if (account) {\n console.log('[OAuth42 SDK] Initial sign in - storing tokens in JWT');\n token.accessToken = account.access_token;\n token.refreshToken = account.refresh_token;\n token.expiresAt = account.expires_at;\n token.idToken = account.id_token;\n token.clientId = clientId;\n token.clientSecret = clientSecret;\n token.issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER;\n }\n\n // Add user profile data\n if (profile) {\n const oauth42Profile = profile as OAuth42Profile;\n token.email = oauth42Profile.email;\n token.username = oauth42Profile.username;\n token.emailVerified = oauth42Profile.email_verified;\n }\n\n // NOTE: Token refresh is handled by middleware (withOAuth42Auth)\n // Middleware can properly set cookies after refresh, which JWT callback cannot\n // do in Next.js App Router Server Components.\n\n // Call custom callback if provided\n if (options.callbacks?.jwt) {\n return options.callbacks.jwt({ token, account, profile } as any);\n }\n\n console.log('[OAuth42 SDK] JWT callback complete, returning token');\n return token;\n },\n\n async session({ session, token }) {\n console.log('[OAuth42 SDK] Session callback called', { hasToken: !!token, hasSession: !!session });\n\n // Add OAuth42-specific data to session\n session.accessToken = token.accessToken as string;\n session.idToken = token.idToken as string;\n\n // Pass through any token refresh errors to the client\n if (token.error) {\n session.error = token.error as string;\n }\n\n if (session.user) {\n session.user.email = token.email as string;\n session.user.name = token.name as string;\n session.user.username = token.username as string;\n session.user.emailVerified = token.emailVerified as boolean;\n }\n\n // Call custom callback if provided\n if (options.callbacks?.session) {\n return options.callbacks.session({ session, token } as any);\n }\n\n console.log('[OAuth42 SDK] Session callback complete, returning session');\n return session;\n },\n },\n \n pages: {\n signIn: '/auth/signin',\n signOut: '/auth/signout',\n error: '/auth/error',\n ...options.pages,\n },\n \n session: {\n strategy: 'jwt',\n ...options.session,\n },\n \n debug: options.debug || process.env.NODE_ENV === 'development',\n\n secret: process.env.NEXTAUTH_SECRET,\n\n // Configure unique cookie names per app to prevent session conflicts on localhost\n ...(options.cookiePrefix && {\n cookies: {\n sessionToken: {\n name: `${options.cookiePrefix}.session-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n callbackUrl: {\n name: `${options.cookiePrefix}.callback-url`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n csrfToken: {\n name: `${options.cookiePrefix}.csrf-token`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n // PKCE code_verifier cookie - essential for PKCE flow\n pkceCodeVerifier: {\n name: `${options.cookiePrefix}.pkce.code_verifier`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // State cookie for OAuth CSRF protection\n state: {\n name: `${options.cookiePrefix}.state`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: 900, // 15 minutes\n },\n },\n // Nonce cookie for OpenID Connect\n nonce: {\n name: `${options.cookiePrefix}.nonce`,\n options: {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n },\n }),\n };\n \n // Return the configuration and handlers for API routes\n const handler = NextAuth(authOptions);\n return {\n auth: authOptions,\n handlers: { GET: handler, POST: handler },\n };\n}\n\n/**\n * Create NextAuth handlers for API routes\n */\nexport function createHandlers(authOptions: NextAuthOptions) {\n const handler = NextAuth(authOptions);\n return { GET: handler, POST: handler };\n}\n\n/**\n * Helper to get the current session server-side\n * @deprecated Use getOAuth42Session instead - this is now just an alias for backward compatibility\n * \n * This function is maintained for backward compatibility but internally\n * calls getOAuth42Session which properly handles both App Router and Pages Router\n */\nexport const getServerSession = getOAuth42Session;\n\n/**\n * Token refresh helper with simple per-process locking\n *\n * The lock prevents multiple concurrent refresh calls from the same process,\n * reducing unnecessary token churn. The backend also has a 10-second grace\n * period for blacklisted tokens, so concurrent requests across processes\n * will still succeed.\n */\nexport async function refreshAccessToken(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n // If a refresh is already in progress, wait for it\n if (activeRefresh) {\n console.log('[OAuth42] Refresh already in progress, waiting...');\n return await activeRefresh;\n }\n\n // Start the refresh and store the promise\n activeRefresh = doRefresh(token, clientId, clientSecret, issuer);\n try {\n return await activeRefresh;\n } finally {\n activeRefresh = null;\n }\n}\n\nasync function doRefresh(token: any, clientId: string, clientSecret: string, issuer?: string): Promise<any> {\n try {\n const baseUrl = issuer || process.env.OAUTH42_ISSUER || 'https://oauth42.com';\n const tokenUrl = `${baseUrl}/oauth2/token`;\n\n // In development, we need to handle self-signed certificates\n const fetchOptions: any = {\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: token.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n };\n\n // Add agent for self-signed certificates in development\n if (process.env.NODE_ENV !== 'production' && tokenUrl.startsWith('https://')) {\n const https = await import('https');\n fetchOptions.agent = new https.Agent({\n rejectUnauthorized: false\n });\n }\n\n const response = await fetch(tokenUrl, fetchOptions);\n const refreshedTokens = await response.json();\n\n if (!response.ok) {\n throw refreshedTokens;\n }\n\n console.log('[OAuth42] Token refreshed successfully');\n return {\n ...token,\n accessToken: refreshedTokens.access_token,\n refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,\n // Store expiration time in seconds (Unix timestamp)\n expiresAt: Math.floor(Date.now() / 1000) + (refreshedTokens.expires_in || 3600),\n // Explicitly remove any error property on successful refresh\n error: undefined,\n };\n } catch (error) {\n console.error('[OAuth42] Failed to refresh access token:', error);\n return {\n ...token,\n error: 'RefreshAccessTokenError',\n };\n }\n}","import { getServerSession as getNextAuthSession } from 'next-auth';\nimport { NextAuthOptions } from 'next-auth';\nimport { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';\n\n/**\n * Get the OAuth42 session server-side\n * \n * This is the primary method for retrieving sessions in OAuth42 SDK.\n * Supports both Pages Router and App Router:\n * \n * App Router:\n * ```ts\n * const session = await getOAuth42Session(authOptions);\n * ```\n * \n * Pages Router:\n * ```ts\n * const session = await getOAuth42Session(req, res, authOptions);\n * ```\n */\nexport async function getOAuth42Session(\n ...args: \n | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res'], NextAuthOptions]\n | [NextApiRequest, NextApiResponse, NextAuthOptions]\n | [NextAuthOptions]\n) {\n return getNextAuthSession(...args as any);\n}\n\n/**\n * Helper for protecting API routes\n */\nexport function withOAuth42Session(\n handler: (req: NextApiRequest, res: NextApiResponse, session: any) => Promise<void> | void,\n authOptions: NextAuthOptions\n) {\n return async (req: NextApiRequest, res: NextApiResponse) => {\n const session = await getOAuth42Session(req, res, authOptions);\n \n if (!session) {\n return res.status(401).json({ error: 'Unauthorized' });\n }\n \n return handler(req, res, session);\n };\n}\n\n/**\n * Helper for protecting server-side props\n */\nexport function withOAuth42ServerSideProps(\n getServerSideProps: (\n context: GetServerSidePropsContext,\n session: any\n ) => Promise<any>,\n authOptions: NextAuthOptions\n) {\n return async (context: GetServerSidePropsContext) => {\n const session = await getOAuth42Session(\n context.req,\n context.res,\n authOptions\n );\n \n if (!session) {\n return {\n redirect: {\n destination: '/auth/signin',\n permanent: false,\n },\n };\n }\n \n return getServerSideProps(context, session);\n };\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,39 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface OAuth42AuthOptions {
|
|
4
|
+
pages?: {
|
|
5
|
+
signIn?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
};
|
|
8
|
+
callbacks?: {
|
|
9
|
+
authorized?: (params: {
|
|
10
|
+
token: any;
|
|
11
|
+
req: NextRequest;
|
|
12
|
+
}) => boolean | Promise<boolean>;
|
|
13
|
+
};
|
|
14
|
+
protectedPaths?: string[];
|
|
15
|
+
publicPaths?: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Cookie prefix for custom cookie names. Must match the prefix used in createAuth().
|
|
18
|
+
* E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'
|
|
19
|
+
*/
|
|
20
|
+
cookiePrefix?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Middleware helper for protecting routes with OAuth42
|
|
24
|
+
*
|
|
25
|
+
* This middleware handles:
|
|
26
|
+
* 1. Route protection (redirect to login if no session)
|
|
27
|
+
* 2. Token refresh (refresh expired tokens and update cookie)
|
|
28
|
+
*/
|
|
29
|
+
declare function withOAuth42Auth(options?: OAuth42AuthOptions): (req: NextRequest) => Promise<NextResponse<unknown>>;
|
|
30
|
+
/**
|
|
31
|
+
* Helper to create middleware configuration
|
|
32
|
+
*/
|
|
33
|
+
declare function createMiddlewareConfig(protectedPaths?: string[], publicPaths?: string[]): {
|
|
34
|
+
matcher: string[];
|
|
35
|
+
protectedPaths: string[];
|
|
36
|
+
publicPaths: string[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { type OAuth42AuthOptions, createMiddlewareConfig, withOAuth42Auth };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface OAuth42AuthOptions {
|
|
4
|
+
pages?: {
|
|
5
|
+
signIn?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
};
|
|
8
|
+
callbacks?: {
|
|
9
|
+
authorized?: (params: {
|
|
10
|
+
token: any;
|
|
11
|
+
req: NextRequest;
|
|
12
|
+
}) => boolean | Promise<boolean>;
|
|
13
|
+
};
|
|
14
|
+
protectedPaths?: string[];
|
|
15
|
+
publicPaths?: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Cookie prefix for custom cookie names. Must match the prefix used in createAuth().
|
|
18
|
+
* E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'
|
|
19
|
+
*/
|
|
20
|
+
cookiePrefix?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Middleware helper for protecting routes with OAuth42
|
|
24
|
+
*
|
|
25
|
+
* This middleware handles:
|
|
26
|
+
* 1. Route protection (redirect to login if no session)
|
|
27
|
+
* 2. Token refresh (refresh expired tokens and update cookie)
|
|
28
|
+
*/
|
|
29
|
+
declare function withOAuth42Auth(options?: OAuth42AuthOptions): (req: NextRequest) => Promise<NextResponse<unknown>>;
|
|
30
|
+
/**
|
|
31
|
+
* Helper to create middleware configuration
|
|
32
|
+
*/
|
|
33
|
+
declare function createMiddlewareConfig(protectedPaths?: string[], publicPaths?: string[]): {
|
|
34
|
+
matcher: string[];
|
|
35
|
+
protectedPaths: string[];
|
|
36
|
+
publicPaths: string[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { type OAuth42AuthOptions, createMiddlewareConfig, withOAuth42Auth };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/middleware/index.ts
|
|
21
|
+
var middleware_exports = {};
|
|
22
|
+
__export(middleware_exports, {
|
|
23
|
+
createMiddlewareConfig: () => createMiddlewareConfig,
|
|
24
|
+
withOAuth42Auth: () => withOAuth42Auth
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(middleware_exports);
|
|
27
|
+
|
|
28
|
+
// src/server/middleware.ts
|
|
29
|
+
var import_server = require("next/server");
|
|
30
|
+
var import_jwt = require("next-auth/jwt");
|
|
31
|
+
async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
32
|
+
try {
|
|
33
|
+
const tokenUrl = `${issuer}/oauth2/token`;
|
|
34
|
+
const response = await fetch(tokenUrl, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
38
|
+
},
|
|
39
|
+
body: new URLSearchParams({
|
|
40
|
+
grant_type: "refresh_token",
|
|
41
|
+
refresh_token: refreshToken,
|
|
42
|
+
client_id: clientId,
|
|
43
|
+
client_secret: clientSecret
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
const data = await response.json();
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
console.error("[OAuth42 Middleware] Token refresh failed:", data);
|
|
49
|
+
return { success: false, error: data.error || "refresh_failed" };
|
|
50
|
+
}
|
|
51
|
+
console.log("[OAuth42 Middleware] Token refreshed successfully");
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
accessToken: data.access_token,
|
|
55
|
+
refreshToken: data.refresh_token,
|
|
56
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (data.expires_in || 3600)
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("[OAuth42 Middleware] Token refresh error:", error);
|
|
60
|
+
return { success: false, error: "refresh_error" };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function withOAuth42Auth(options = {}) {
|
|
64
|
+
const secret = process.env.NEXTAUTH_SECRET;
|
|
65
|
+
const clientId = process.env.OAUTH42_CLIENT_ID;
|
|
66
|
+
const clientSecret = process.env.OAUTH42_CLIENT_SECRET;
|
|
67
|
+
const issuer = process.env.OAUTH42_ISSUER || "https://localhost:8443";
|
|
68
|
+
if (!secret) {
|
|
69
|
+
console.warn("[OAuth42 Middleware] NEXTAUTH_SECRET not set");
|
|
70
|
+
}
|
|
71
|
+
return async function middleware(req) {
|
|
72
|
+
const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
|
|
73
|
+
const token = await (0, import_jwt.getToken)({
|
|
74
|
+
req,
|
|
75
|
+
secret,
|
|
76
|
+
cookieName
|
|
77
|
+
});
|
|
78
|
+
const pathname = req.nextUrl.pathname;
|
|
79
|
+
if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
|
|
80
|
+
return import_server.NextResponse.next();
|
|
81
|
+
}
|
|
82
|
+
const needsProtection = options.protectedPaths ? options.protectedPaths.some((path) => pathname.startsWith(path)) : true;
|
|
83
|
+
if (!needsProtection) {
|
|
84
|
+
return import_server.NextResponse.next();
|
|
85
|
+
}
|
|
86
|
+
if (!token) {
|
|
87
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
88
|
+
const url = new URL(signInUrl, req.url);
|
|
89
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
90
|
+
return import_server.NextResponse.redirect(url);
|
|
91
|
+
}
|
|
92
|
+
const expiresAt = token.expiresAt;
|
|
93
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
94
|
+
const bufferSeconds = 60;
|
|
95
|
+
const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;
|
|
96
|
+
if (needsRefresh && token.refreshToken && clientId && clientSecret) {
|
|
97
|
+
console.log("[OAuth42 Middleware] Access token expired, refreshing...");
|
|
98
|
+
const refreshed = await refreshTokens(
|
|
99
|
+
token.refreshToken,
|
|
100
|
+
clientId,
|
|
101
|
+
clientSecret,
|
|
102
|
+
issuer
|
|
103
|
+
);
|
|
104
|
+
if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {
|
|
105
|
+
const updatedToken = {
|
|
106
|
+
...token,
|
|
107
|
+
accessToken: refreshed.accessToken,
|
|
108
|
+
refreshToken: refreshed.refreshToken,
|
|
109
|
+
expiresAt: refreshed.expiresAt
|
|
110
|
+
};
|
|
111
|
+
const newJwt = await (0, import_jwt.encode)({
|
|
112
|
+
token: updatedToken,
|
|
113
|
+
secret
|
|
114
|
+
});
|
|
115
|
+
const response = import_server.NextResponse.next();
|
|
116
|
+
response.cookies.set(cookieName, newJwt, {
|
|
117
|
+
httpOnly: true,
|
|
118
|
+
sameSite: "lax",
|
|
119
|
+
path: "/",
|
|
120
|
+
secure: process.env.NODE_ENV === "production"
|
|
121
|
+
});
|
|
122
|
+
console.log("[OAuth42 Middleware] Cookie updated with refreshed tokens");
|
|
123
|
+
return response;
|
|
124
|
+
} else {
|
|
125
|
+
console.error("[OAuth42 Middleware] Refresh failed, redirecting to sign in");
|
|
126
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
127
|
+
const url = new URL(signInUrl, req.url);
|
|
128
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
129
|
+
url.searchParams.set("error", "RefreshAccessTokenError");
|
|
130
|
+
return import_server.NextResponse.redirect(url);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (options.callbacks?.authorized) {
|
|
134
|
+
const isAuthorized = await options.callbacks.authorized({ token, req });
|
|
135
|
+
if (!isAuthorized) {
|
|
136
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
137
|
+
const url = new URL(signInUrl, req.url);
|
|
138
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
139
|
+
return import_server.NextResponse.redirect(url);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return import_server.NextResponse.next();
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function createMiddlewareConfig(protectedPaths = ["/protected"], publicPaths = ["/auth", "/api/auth"]) {
|
|
146
|
+
return {
|
|
147
|
+
matcher: [
|
|
148
|
+
/*
|
|
149
|
+
* Match all request paths except for the ones starting with:
|
|
150
|
+
* - _next/static (static files)
|
|
151
|
+
* - _next/image (image optimization files)
|
|
152
|
+
* - favicon.ico (favicon file)
|
|
153
|
+
* - public folder
|
|
154
|
+
*/
|
|
155
|
+
"/((?!_next/static|_next/image|favicon.ico|public).*)"
|
|
156
|
+
],
|
|
157
|
+
protectedPaths,
|
|
158
|
+
publicPaths
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
162
|
+
0 && (module.exports = {
|
|
163
|
+
createMiddlewareConfig,
|
|
164
|
+
withOAuth42Auth
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=index.js.map
|