@coreiam/auth 1.0.15 → 1.0.391

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.
@@ -1,19 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NextHandlers = exports.coreIamMiddleware = exports.getSession = exports.logoutActionSdk = exports.registerActionSdk = exports.loginActionSdk = exports.useAuth = exports.CoreIAMProvider = void 0;
3
+ exports.OAuthHandlers = exports.NextHandlers = exports.verifyOtpActionSdk = exports.requestOtpActionSdk = exports.disableMfaActionSdk = exports.verifyMfaActionSdk = exports.setupMfaActionSdk = exports.updateProfileActionSdk = exports.logoutActionSdk = exports.registerActionSdk = exports.loginActionSdk = exports.getSession = exports.coreIamAuth = exports.coreIamMiddleware = exports.useAuth = exports.CoreIAMProvider = void 0;
4
+ exports.auth = auth;
4
5
  // src/nextjs/index.ts
6
+ const server_1 = require("next/server");
7
+ const core_1 = require("../core");
5
8
  var provider_1 = require("./provider");
6
9
  Object.defineProperty(exports, "CoreIAMProvider", { enumerable: true, get: function () { return provider_1.CoreIAMProvider; } });
7
10
  Object.defineProperty(exports, "useAuth", { enumerable: true, get: function () { return provider_1.useAuth; } });
11
+ var middleware_1 = require("./middleware");
12
+ Object.defineProperty(exports, "coreIamMiddleware", { enumerable: true, get: function () { return middleware_1.coreIamMiddleware; } });
13
+ Object.defineProperty(exports, "coreIamAuth", { enumerable: true, get: function () { return middleware_1.coreIamAuth; } });
14
+ // FIX: Export ALL Server Action functions here
8
15
  var actions_1 = require("./actions");
16
+ Object.defineProperty(exports, "getSession", { enumerable: true, get: function () { return actions_1.getSession; } });
9
17
  Object.defineProperty(exports, "loginActionSdk", { enumerable: true, get: function () { return actions_1.loginActionSdk; } });
10
18
  Object.defineProperty(exports, "registerActionSdk", { enumerable: true, get: function () { return actions_1.registerActionSdk; } });
11
19
  Object.defineProperty(exports, "logoutActionSdk", { enumerable: true, get: function () { return actions_1.logoutActionSdk; } });
12
- Object.defineProperty(exports, "getSession", { enumerable: true, get: function () { return actions_1.getSession; } });
13
- var middleware_1 = require("./middleware");
14
- Object.defineProperty(exports, "coreIamMiddleware", { enumerable: true, get: function () { return middleware_1.coreIamMiddleware; } });
15
- const server_1 = require("next/server");
16
- const core_1 = require("../core");
20
+ Object.defineProperty(exports, "updateProfileActionSdk", { enumerable: true, get: function () { return actions_1.updateProfileActionSdk; } });
21
+ Object.defineProperty(exports, "setupMfaActionSdk", { enumerable: true, get: function () { return actions_1.setupMfaActionSdk; } });
22
+ Object.defineProperty(exports, "verifyMfaActionSdk", { enumerable: true, get: function () { return actions_1.verifyMfaActionSdk; } });
23
+ Object.defineProperty(exports, "disableMfaActionSdk", { enumerable: true, get: function () { return actions_1.disableMfaActionSdk; } });
24
+ Object.defineProperty(exports, "requestOtpActionSdk", { enumerable: true, get: function () { return actions_1.requestOtpActionSdk; } });
25
+ Object.defineProperty(exports, "verifyOtpActionSdk", { enumerable: true, get: function () { return actions_1.verifyOtpActionSdk; } });
17
26
  class NextHandlers {
18
27
  iam;
19
28
  constructor(iam) {
@@ -46,3 +55,84 @@ class NextHandlers {
46
55
  }
47
56
  }
48
57
  exports.NextHandlers = NextHandlers;
58
+ class OAuthHandlers {
59
+ iam;
60
+ constructor(iam) {
61
+ this.iam = iam || new core_1.CoreIAM({ apiKey: process.env.COREIAM_API_KEY, baseUrl: process.env.COREIAM_API_URL });
62
+ }
63
+ // Handles the initial redirect to Google/GitHub
64
+ async handleInitiate(req, provider) {
65
+ const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/${provider}`;
66
+ const response = await this.iam.proxy(`/auth/oauth/initiate/${provider}?redirectUri=${encodeURIComponent(redirectUri)}&scopes=email,profile`, { method: 'GET' }, req.headers);
67
+ const data = await response.json();
68
+ if (data.url) {
69
+ return server_1.NextResponse.redirect(data.url);
70
+ }
71
+ return server_1.NextResponse.redirect(new URL('/auth/login', req.url));
72
+ }
73
+ // Handles the callback from Google/GitHub
74
+ async handleCallback(req, provider) {
75
+ const { searchParams } = new URL(req.url);
76
+ const code = searchParams.get('code');
77
+ const state = searchParams.get('state');
78
+ const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/${provider}`;
79
+ const targetUrl = `/auth/oauth/callback/${provider}?code=${code}&state=${state}&redirectUri=${redirectUri}`;
80
+ const response = await this.iam.proxy(targetUrl, { method: 'GET' }, req.headers);
81
+ const res = server_1.NextResponse.redirect(new URL('/', req.url));
82
+ const setCookies = response.headers.getSetCookie();
83
+ if (setCookies && setCookies.length > 0) {
84
+ setCookies.forEach(cookie => {
85
+ res.headers.append('set-cookie', cookie);
86
+ });
87
+ }
88
+ return res;
89
+ }
90
+ }
91
+ exports.OAuthHandlers = OAuthHandlers;
92
+ const MAX_CACHE_SIZE = 10000;
93
+ const apiKeyCache = new Map();
94
+ async function auth(request) {
95
+ const authHeader = request.headers.get('authorization');
96
+ if (!authHeader?.startsWith('Bearer '))
97
+ return null;
98
+ const token = authHeader.substring(7);
99
+ const gatewayUrl = process.env.COREIAM_API_URL;
100
+ if (!gatewayUrl)
101
+ return null;
102
+ // 1. Check Cache
103
+ const cached = apiKeyCache.get(token);
104
+ if (cached && cached.expires > Date.now()) {
105
+ return cached.valid ? cached.identity : null;
106
+ }
107
+ // FIX: Prevent memory leak by clearing cache if it gets too large
108
+ if (apiKeyCache.size >= MAX_CACHE_SIZE) {
109
+ apiKeyCache.clear(); // Simple crude eviction. In production SDK, use lru-cache.
110
+ }
111
+ // 2. Call CoreIAM to verify
112
+ try {
113
+ const res = await fetch(`${gatewayUrl}/auth/api-keys/verify`, {
114
+ method: 'POST',
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ 'X-API-Key': process.env.COREIAM_API_KEY
118
+ },
119
+ body: JSON.stringify({ token }),
120
+ });
121
+ const data = await res.json();
122
+ if (data.valid) {
123
+ const identity = {
124
+ sub: data.sub,
125
+ tenantId: data.tenantId,
126
+ };
127
+ apiKeyCache.set(token, { valid: true, identity, expires: Date.now() + (5 * 60 * 1000) });
128
+ return identity;
129
+ }
130
+ else {
131
+ apiKeyCache.set(token, { valid: false, expires: Date.now() + (5 * 60 * 1000) });
132
+ return null;
133
+ }
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
@@ -1,9 +1,16 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  /**
3
- * Handles CoreIAM token refresh and header rewriting.
4
- * Returns the NextResponse to return, and whether the user is authenticated.
3
+ * Helper function for custom middleware (proxy.ts).
4
+ * Returns the response and authentication state.
5
5
  */
6
- export declare function coreIamMiddleware(request: NextRequest): Promise<{
6
+ export declare function coreIamAuth(request: NextRequest): Promise<{
7
7
  response: NextResponse;
8
8
  isAuthenticated: boolean;
9
9
  }>;
10
+ /**
11
+ * 1-line middleware factory for developers who don't need custom routing.
12
+ */
13
+ export declare function coreIamMiddleware(options?: {
14
+ publicRoutes?: string[];
15
+ signInUrl?: string;
16
+ }): (request: NextRequest) => Promise<NextResponse<unknown>>;
@@ -1,11 +1,148 @@
1
1
  "use strict";
2
+ // // src/nextjs/middleware.ts
3
+ // import { NextRequest, NextResponse } from 'next/server';
4
+ // import { jwtVerify, createRemoteJWKSet } from 'jose';
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.coreIamAuth = coreIamAuth;
3
7
  exports.coreIamMiddleware = coreIamMiddleware;
8
+ // // FIX: Instantiate the JWKS client ONCE at the module level.
9
+ // // This allows jose's internal cache to persist across requests.
10
+ // const gatewayUrl = process.env.COREIAM_API_URL;
11
+ // const JWKS = gatewayUrl
12
+ // ? createRemoteJWKSet(new URL(`${gatewayUrl}/.well-known/jwks.json`))
13
+ // : null;
14
+ // let refreshInFlight: Promise<{ jwt: string; refreshToken: string } | null> | null = null;
15
+ // async function performRefresh(
16
+ // refreshTokenCookie: { value: string } | undefined,
17
+ // jwtCookie: { value: string } | undefined,
18
+ // gatewayUrl: string,
19
+ // ): Promise<{ jwt: string; refreshToken: string } | null> {
20
+ // try {
21
+ // const refreshRes = await fetch(`${gatewayUrl}/auth/refresh-token`, {
22
+ // method: 'POST',
23
+ // headers: {
24
+ // 'X-API-Key': process.env.COREIAM_API_KEY!,
25
+ // 'cookie': `refreshToken=${refreshTokenCookie?.value || ''}; jwt=${jwtCookie?.value || ''}`,
26
+ // },
27
+ // });
28
+ // if (!refreshRes.ok) return null;
29
+ // const setCookies = refreshRes.headers.getSetCookie();
30
+ // let newJwt = '';
31
+ // let newRefreshToken = '';
32
+ // setCookies.forEach(cookie => {
33
+ // if (cookie.startsWith('jwt=')) newJwt = cookie.split(';')[0].split('=')[1];
34
+ // if (cookie.startsWith('refreshToken=')) newRefreshToken = cookie.split(';')[0].split('=')[1];
35
+ // });
36
+ // return newJwt ? { jwt: newJwt, refreshToken: newRefreshToken } : null;
37
+ // } catch (error) {
38
+ // return null;
39
+ // }
40
+ // }
41
+ // export async function coreIamMiddleware(request: NextRequest): Promise<{
42
+ // response: NextResponse;
43
+ // isAuthenticated: boolean;
44
+ // }> {
45
+ // const jwtCookie = request.cookies.get("jwt");
46
+ // const refreshTokenCookie = request.cookies.get("refreshToken");
47
+ // let isAuthenticated = false;
48
+ // let needsRefresh = false;
49
+ // if (jwtCookie && JWKS) {
50
+ // try {
51
+ // // 1. SECURELY VERIFY JWT LOCALLY USING THE MODULE-LEVEL JWKS CLIENT
52
+ // const { payload } = await jwtVerify(jwtCookie.value, JWKS, {
53
+ // issuer: 'coreiam',
54
+ // algorithms: ['RS256']
55
+ // });
56
+ // // ATTACH IDENTITY TO REQUEST HEADERS FOR SERVER COMPONENTS
57
+ // const requestHeaders = new Headers(request.headers);
58
+ // requestHeaders.set('x-coreiam-identity', JSON.stringify({
59
+ // sub: payload.sub,
60
+ // email: payload.email,
61
+ // tenantId: payload.tenantId,
62
+ // roles: payload.roles || []
63
+ // }));
64
+ // const response = NextResponse.next({
65
+ // request: { headers: requestHeaders },
66
+ // });
67
+ // return { response, isAuthenticated: true };
68
+ // } catch (error: any) {
69
+ // // If token is expired, jose throws ERR_JWT_EXPIRED
70
+ // if (error.code === 'ERR_JWT_EXPIRED' && refreshTokenCookie) {
71
+ // needsRefresh = true;
72
+ // }
73
+ // }
74
+ // } else if (refreshTokenCookie) {
75
+ // needsRefresh = true;
76
+ // }
77
+ // if (needsRefresh && gatewayUrl) {
78
+ // if (!refreshInFlight) {
79
+ // refreshInFlight = performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl).finally(() => {
80
+ // refreshInFlight = null;
81
+ // });
82
+ // }
83
+ // const result = await refreshInFlight;
84
+ // if (result) {
85
+ // const requestHeaders = new Headers(request.headers);
86
+ // requestHeaders.set('cookie', `jwt=${result.jwt}; refreshToken=${result.refreshToken}`);
87
+ // requestHeaders.set('Authorization', `Bearer ${result.jwt}`);
88
+ // // ATTACH IDENTITY TO REQUEST HEADERS FOR SERVER COMPONENTS (using the new JWT)
89
+ // try {
90
+ // const payloadB64 = result.jwt.split('.')[1];
91
+ // const payloadStr = Buffer.from(payloadB64, 'base64').toString('utf8');
92
+ // const payload = JSON.parse(payloadStr);
93
+ // requestHeaders.set('x-coreiam-identity', JSON.stringify({
94
+ // sub: payload.sub,
95
+ // email: payload.email,
96
+ // tenantId: payload.tenantId,
97
+ // roles: payload.roles || []
98
+ // }));
99
+ // } catch {}
100
+ // const response = NextResponse.next({
101
+ // request: { headers: requestHeaders },
102
+ // });
103
+ // const isProd = process.env.NODE_ENV === 'production';
104
+ // response.cookies.set('jwt', result.jwt, {
105
+ // httpOnly: true,
106
+ // secure: isProd,
107
+ // sameSite: 'strict',
108
+ // maxAge: 24 * 60 * 60,
109
+ // });
110
+ // response.cookies.set('refreshToken', result.refreshToken, {
111
+ // httpOnly: true,
112
+ // secure: isProd,
113
+ // sameSite: 'strict',
114
+ // maxAge: 7 * 24 * 60 * 60,
115
+ // });
116
+ // return { response, isAuthenticated: true };
117
+ // }
118
+ // const response = NextResponse.next();
119
+ // response.cookies.delete('jwt');
120
+ // response.cookies.delete('refreshToken');
121
+ // return { response, isAuthenticated: false };
122
+ // }
123
+ // return { response: NextResponse.next(), isAuthenticated };
124
+ // }
4
125
  // src/nextjs/middleware.ts
5
126
  const server_1 = require("next/server");
6
- const jwt_decode_1 = require("jwt-decode");
127
+ //import { jwtVerify, createRemoteJWKSet } from 'jose';
128
+ const core_1 = require("../core");
129
+ const gatewayUrl = process.env.COREIAM_API_URL;
130
+ /**
131
+ const JWKS = gatewayUrl
132
+ ? createRemoteJWKSet(new URL(`${gatewayUrl}/.well-known/jwks.json`))
133
+ : null;
134
+ */
135
+ let iam = null;
136
+ function getIam() {
137
+ if (!gatewayUrl || !process.env.COREIAM_API_KEY)
138
+ return null;
139
+ iam ??= new core_1.CoreIAM({ apiKey: process.env.COREIAM_API_KEY, baseUrl: gatewayUrl });
140
+ return iam;
141
+ }
7
142
  let refreshInFlight = null;
8
- async function performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl) {
143
+ async function performRefresh(refreshTokenCookie, jwtCookie) {
144
+ if (!gatewayUrl)
145
+ return null;
9
146
  try {
10
147
  const refreshRes = await fetch(`${gatewayUrl}/auth/refresh-token`, {
11
148
  method: 'POST',
@@ -17,8 +154,7 @@ async function performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl) {
17
154
  if (!refreshRes.ok)
18
155
  return null;
19
156
  const setCookies = refreshRes.headers.getSetCookie();
20
- let newJwt = '';
21
- let newRefreshToken = '';
157
+ let newJwt = '', newRefreshToken = '';
22
158
  setCookies.forEach(cookie => {
23
159
  if (cookie.startsWith('jwt='))
24
160
  newJwt = cookie.split(';')[0].split('=')[1];
@@ -32,37 +168,213 @@ async function performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl) {
32
168
  }
33
169
  }
34
170
  /**
35
- * Handles CoreIAM token refresh and header rewriting.
36
- * Returns the NextResponse to return, and whether the user is authenticated.
171
+ * Helper function for custom middleware (proxy.ts).
172
+ * Returns the response and authentication state.
37
173
  */
38
- async function coreIamMiddleware(request) {
174
+ async function coreIamAuth(request) {
175
+ const { nextUrl, method } = request;
176
+ const pathname = nextUrl.pathname;
177
+ // ========================================
178
+ // 1. ZERO-CONFIG API PROXY
179
+ // ========================================
180
+ if (pathname.startsWith('/api/auth/')) {
181
+ // FIX: Short-circuit CORS Preflight requests!
182
+ if (method === 'OPTIONS') {
183
+ const res = new server_1.NextResponse(null, { status: 204 }); // 204 No Content
184
+ const origin = request.headers.get('origin');
185
+ if (origin) {
186
+ res.headers.set('Access-Control-Allow-Origin', origin);
187
+ res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
188
+ res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-CSRF-Token');
189
+ res.headers.set('Access-Control-Allow-Credentials', 'true');
190
+ }
191
+ return { response: res, isAuthenticated: false };
192
+ }
193
+ // const iam = new CoreIAM({ apiKey: process.env.COREIAM_API_KEY!, baseUrl: gatewayUrl! });
194
+ const iam = getIam();
195
+ if (!iam) {
196
+ return {
197
+ response: new server_1.NextResponse('CoreIAM is not configured (COREIAM_API_URL / COREIAM_API_KEY)', { status: 500 }),
198
+ isAuthenticated: false,
199
+ };
200
+ }
201
+ // FIX: Special handler for the Link Confirm POST! (Must be at the TOP, before generic body read)
202
+ if (pathname === '/api/auth/oauth/link/confirm') {
203
+ let formData = {};
204
+ const contentType = request.headers.get('content-type') || '';
205
+ if (contentType.includes('application/x-www-form-urlencoded')) {
206
+ const text = await request.text();
207
+ const params = new URLSearchParams(text);
208
+ formData = {
209
+ tempToken: params.get('tempToken'),
210
+ password: params.get('password')
211
+ };
212
+ }
213
+ else {
214
+ // Fallback to JSON (if called via fetch elsewhere)
215
+ formData = await request.json().catch(() => ({}));
216
+ }
217
+ const response = await iam.proxy('/auth/oauth/link/confirm', {
218
+ method: 'POST',
219
+ body: JSON.stringify(formData),
220
+ headers: { 'Content-Type': 'application/json' },
221
+ }, request.headers);
222
+ const data = await response.json().catch(() => ({}));
223
+ // If CoreIAM successfully verified the password and linked the account
224
+ if (response.ok && data.success) {
225
+ const appOrigin = request.nextUrl.origin; // e.g., http://localhost:3000
226
+ const redirectPath = data.redirectUri || '/';
227
+ const finalUrl = new URL(redirectPath, appOrigin).toString();
228
+ // FIX: Return a 200 OK with an HTML page that uses JavaScript to redirect.
229
+ // This breaks the cross-site redirect chain, forcing the browser to send the Lax cookies.
230
+ const html = `
231
+ <!DOCTYPE html>
232
+ <html>
233
+ <head>
234
+ <title>Linking Successful</title>
235
+ </head>
236
+ <body>
237
+ <p>Account linked successfully! Redirecting...</p>
238
+ <script>window.location.href = "${finalUrl}";</script>
239
+ </body>
240
+ </html>
241
+ `;
242
+ const res = new server_1.NextResponse(html, { status: 200, headers: { 'Content-Type': 'text/html' } });
243
+ // Set the cookies on the Next.js domain (localhost:3000)!
244
+ const isProd = process.env.NODE_ENV === 'production';
245
+ if (data.accessToken) {
246
+ res.cookies.set('jwt', data.accessToken, { httpOnly: true, secure: isProd, sameSite: 'lax', maxAge: 24 * 60 * 60, path: '/' });
247
+ }
248
+ if (data.clientRefreshToken) {
249
+ res.cookies.set('refreshToken', data.clientRefreshToken, { httpOnly: true, secure: isProd, sameSite: 'lax', maxAge: 7 * 24 * 60 * 60, path: '/' });
250
+ }
251
+ return { response: res, isAuthenticated: true };
252
+ }
253
+ // FIX: If it failed (wrong password), redirect back to the hosted page with an error param
254
+ const errorRedirect = new URL(`${gatewayUrl}/auth/hosted/confirm-link`);
255
+ errorRedirect.searchParams.set('token', formData.tempToken);
256
+ errorRedirect.searchParams.set('provider', data.provider || 'google');
257
+ errorRedirect.searchParams.set('email', data.email || '');
258
+ errorRedirect.searchParams.set('appOrigin', request.nextUrl.origin);
259
+ errorRedirect.searchParams.set('error', data.message || 'Invalid password. Link failed.');
260
+ return { response: server_1.NextResponse.redirect(errorRedirect, 303), isAuthenticated: false };
261
+ }
262
+ // Now it is safe to read the body for all other standard routes (Login, Register, etc.)
263
+ let targetPath = pathname.replace('/api/auth', '/auth');
264
+ const body = method !== 'GET' ? await request.json().catch(() => undefined) : undefined;
265
+ // FIX: Special routing for OAuth Initiate
266
+ if (pathname.startsWith('/api/auth/oauth/') && !pathname.includes('callback') && !pathname.includes('link')) {
267
+ const provider = pathname.split('/').pop();
268
+ const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/${provider}`;
269
+ targetPath = `/auth/oauth/initiate/${provider}?redirectUri=${encodeURIComponent(redirectUri)}&scopes=email,profile`;
270
+ }
271
+ // FIX: Special routing for OAuth Callback
272
+ if (pathname.startsWith('/api/auth/callback/')) {
273
+ const provider = pathname.split('/').pop();
274
+ const searchParams = new URL(request.url).searchParams;
275
+ const code = searchParams.get('code');
276
+ const state = searchParams.get('state');
277
+ const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/${provider}`;
278
+ targetPath = `/auth/oauth/callback/${provider}?code=${code}&state=${state}&redirectUri=${redirectUri}`;
279
+ }
280
+ const response = await iam.proxy(targetPath, {
281
+ method,
282
+ body: body ? JSON.stringify(body) : undefined,
283
+ headers: { 'Content-Type': 'application/json' },
284
+ }, request.headers);
285
+ // Handle OAuth Initiate Redirects
286
+ if (pathname.startsWith('/api/auth/oauth/') && !pathname.includes('callback') && !pathname.includes('link')) {
287
+ const data = await response.json().catch(() => ({}));
288
+ if (data.url)
289
+ return { response: server_1.NextResponse.redirect(data.url), isAuthenticated: false };
290
+ return { response: server_1.NextResponse.redirect(new URL('/auth/login', request.url)), isAuthenticated: false };
291
+ }
292
+ // Handle OAuth Callback Redirects
293
+ if (pathname.startsWith('/api/auth/callback/')) {
294
+ const data = await response.json().catch(() => ({}));
295
+ // FIX: Redirect to CoreIAM's Hosted Confirmation Page and pass appOrigin!
296
+ if (data.action === 'link_candidate') {
297
+ const appOrigin = request.nextUrl.origin; // e.g., http://localhost:3000
298
+ const redirectUrl = `${gatewayUrl}/auth/hosted/confirm-link?token=${encodeURIComponent(data.tempToken)}&provider=${encodeURIComponent(data.provider)}&email=${encodeURIComponent(data.email)}&appOrigin=${encodeURIComponent(appOrigin)}`;
299
+ return { response: server_1.NextResponse.redirect(redirectUrl), isAuthenticated: false };
300
+ }
301
+ // Normal OAuth login: redirect to dashboard
302
+ const res = server_1.NextResponse.redirect(new URL('/', request.url));
303
+ const setCookies = response.headers.getSetCookie();
304
+ setCookies.forEach(c => res.headers.append('set-cookie', c));
305
+ return { response: res, isAuthenticated: false };
306
+ }
307
+ // Standard JSON API Responses (Login, Logout, Register, etc.)
308
+ const data = await response.json().catch(() => ({}));
309
+ const res = server_1.NextResponse.json(data, { status: response.status });
310
+ const setCookies = response.headers.getSetCookie();
311
+ setCookies.forEach(c => res.headers.append('set-cookie', c));
312
+ return { response: res, isAuthenticated: false };
313
+ }
314
+ /**
315
+ // 2. LOCAL JWT VERIFICATION & REFRESH
316
+ const jwtCookie = request.cookies.get("jwt");
317
+ const refreshTokenCookie = request.cookies.get("refreshToken");
318
+
319
+ let isAuthenticated = false;
320
+ let needsRefresh = false;
321
+
322
+ if (jwtCookie && JWKS) {
323
+ try {
324
+ const { payload } = await jwtVerify(jwtCookie.value, JWKS, {
325
+ issuer: 'coreiam',
326
+ algorithms: ['RS256']
327
+ });
328
+
329
+ const requestHeaders = new Headers(request.headers);
330
+ requestHeaders.set('x-coreiam-identity', JSON.stringify({
331
+ sub: payload.sub,
332
+ email: payload.email,
333
+ tenantId: payload.tenantId,
334
+ roles: payload.roles || []
335
+ }));
336
+
337
+ return {
338
+ response: NextResponse.next({ request: { headers: requestHeaders } }),
339
+ isAuthenticated: true
340
+ };
341
+ } catch (error: any) {
342
+ if (error.code === 'ERR_JWT_EXPIRED' && refreshTokenCookie) {
343
+ needsRefresh = true;
344
+ }
345
+ }
346
+ } else if (refreshTokenCookie) {
347
+ needsRefresh = true;
348
+ }
349
+
350
+ */
351
+ // 2. LOCAL JWT VERIFICATION & REFRESH
352
+ // Now delegates to CoreIAM.verifySession — the same code path Express
353
+ // users get, so the two implementations can never drift.
39
354
  const jwtCookie = request.cookies.get("jwt");
40
355
  const refreshTokenCookie = request.cookies.get("refreshToken");
41
- const gatewayUrl = process.env.COREIAM_API_URL;
42
- let isAuthenticated = false;
356
+ const client = getIam();
43
357
  let needsRefresh = false;
44
- if (jwtCookie) {
45
- try {
46
- const decoded = (0, jwt_decode_1.jwtDecode)(jwtCookie.value);
47
- if (decoded.exp && decoded.exp * 1000 > Date.now()) {
48
- isAuthenticated = true;
49
- }
50
- else if (refreshTokenCookie) {
51
- needsRefresh = true;
52
- }
358
+ if (client) {
359
+ const result = await client.verifySession(request.headers);
360
+ if (result.identity) {
361
+ const requestHeaders = new Headers(request.headers);
362
+ requestHeaders.set('x-coreiam-identity', JSON.stringify(result.identity));
363
+ return {
364
+ response: server_1.NextResponse.next({ request: { headers: requestHeaders } }),
365
+ isAuthenticated: true
366
+ };
53
367
  }
54
- catch {
55
- if (refreshTokenCookie)
56
- needsRefresh = true;
368
+ if (refreshTokenCookie && (result.reason === 'expired' || result.reason === 'no_cookie')) {
369
+ needsRefresh = true;
57
370
  }
58
371
  }
59
372
  else if (refreshTokenCookie) {
60
373
  needsRefresh = true;
61
374
  }
62
- // 1. ATTEMPT SILENT REFRESH (single-flight)
63
- if (needsRefresh && gatewayUrl) {
375
+ if (needsRefresh) {
64
376
  if (!refreshInFlight) {
65
- refreshInFlight = performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl).finally(() => {
377
+ refreshInFlight = performRefresh(refreshTokenCookie, jwtCookie).finally(() => {
66
378
  refreshInFlight = null;
67
379
  });
68
380
  }
@@ -71,30 +383,48 @@ async function coreIamMiddleware(request) {
71
383
  const requestHeaders = new Headers(request.headers);
72
384
  requestHeaders.set('cookie', `jwt=${result.jwt}; refreshToken=${result.refreshToken}`);
73
385
  requestHeaders.set('Authorization', `Bearer ${result.jwt}`);
74
- const response = server_1.NextResponse.next({
75
- request: { headers: requestHeaders },
76
- });
386
+ try {
387
+ const payloadB64 = result.jwt.split('.')[1];
388
+ const payloadStr = Buffer.from(payloadB64, 'base64').toString('utf8');
389
+ const payload = JSON.parse(payloadStr);
390
+ requestHeaders.set('x-coreiam-identity', JSON.stringify({
391
+ sub: payload.sub,
392
+ email: payload.email,
393
+ tenantId: payload.tenantId,
394
+ roles: payload.roles || []
395
+ }));
396
+ }
397
+ catch { }
398
+ const response = server_1.NextResponse.next({ request: { headers: requestHeaders } });
77
399
  const isProd = process.env.NODE_ENV === 'production';
78
- response.cookies.set('jwt', result.jwt, {
79
- httpOnly: true,
80
- secure: isProd,
81
- sameSite: 'strict',
82
- maxAge: 24 * 60 * 60,
83
- });
84
- response.cookies.set('refreshToken', result.refreshToken, {
85
- httpOnly: true,
86
- secure: isProd,
87
- sameSite: 'strict',
88
- maxAge: 7 * 24 * 60 * 60,
89
- });
400
+ response.cookies.set('jwt', result.jwt, { httpOnly: true, secure: isProd, sameSite: 'strict', maxAge: 24 * 60 * 60 });
401
+ response.cookies.set('refreshToken', result.refreshToken, { httpOnly: true, secure: isProd, sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 });
90
402
  return { response, isAuthenticated: true };
91
403
  }
92
- // 2. IF REFRESH FAILED, CLEAR COOKIES
93
404
  const response = server_1.NextResponse.next();
94
405
  response.cookies.delete('jwt');
95
406
  response.cookies.delete('refreshToken');
96
407
  return { response, isAuthenticated: false };
97
408
  }
98
- // 3. NO REFRESH NEEDED (Token is valid or completely missing)
99
- return { response: server_1.NextResponse.next(), isAuthenticated };
409
+ return { response: server_1.NextResponse.next(), isAuthenticated: false };
410
+ }
411
+ /**
412
+ * 1-line middleware factory for developers who don't need custom routing.
413
+ */
414
+ function coreIamMiddleware(options = {}) {
415
+ const { publicRoutes = [], signInUrl = '/login' } = options;
416
+ return async (request) => {
417
+ const { response, isAuthenticated } = await coreIamAuth(request);
418
+ const pathname = request.nextUrl.pathname;
419
+ const isPublicRoute = publicRoutes.some(route => pathname.startsWith(route));
420
+ // If not authenticated and trying to access a protected route
421
+ if (!isAuthenticated && !isPublicRoute) {
422
+ const redirect = server_1.NextResponse.redirect(new URL(signInUrl, request.url));
423
+ // CRITICAL: Copy any refreshed cookies/headers from the SDK response onto the redirect
424
+ const setCookies = response.headers.getSetCookie();
425
+ setCookies.forEach(c => redirect.headers.append('set-cookie', c));
426
+ return redirect;
427
+ }
428
+ return response;
429
+ };
100
430
  }
@@ -1,39 +1,30 @@
1
1
  import React from 'react';
2
2
  import type { CoreIAMIdentity } from '../core';
3
- export interface CoreIAMRedirects {
4
- /** Where to send the user after a successful login, if no callbackUrl is passed to login(). */
5
- afterLogin?: string;
6
- /** Where to send the user after logout. */
7
- afterLogout?: string;
8
- }
9
- interface LoginResult {
10
- success?: boolean;
11
- error?: string;
12
- }
13
3
  interface AuthContextType {
14
- /**
15
- * Generic CoreIAM identity (sub/email/roles/tenantId) — NOT an app-shaped
16
- * user. This SDK has no knowledge of any consumer's local user model
17
- * (roles, profile fields, etc). Consumers that need an enriched user
18
- * object should build their own layer on top of this, keyed off `sub`.
19
- */
20
4
  identity: CoreIAMIdentity | null;
21
5
  isLoading: boolean;
22
6
  isAuthenticated: boolean;
23
7
  login: (values: {
24
8
  email: string;
25
9
  password: string;
26
- }, callbackUrl?: string) => Promise<LoginResult>;
10
+ }, callbackUrl?: string) => Promise<{
11
+ success?: boolean;
12
+ error?: string;
13
+ }>;
27
14
  logout: () => Promise<void>;
28
- register: (userData: Record<string, any>) => Promise<LoginResult>;
15
+ register: (userData: any) => Promise<{
16
+ success?: boolean;
17
+ error?: string;
18
+ }>;
19
+ verifyMfa: (tempToken: string, code: string) => Promise<{
20
+ success?: boolean;
21
+ error?: string;
22
+ }>;
29
23
  refetchIdentity: () => Promise<void>;
30
24
  }
31
- export declare function CoreIAMProvider({ children, initialIdentity, redirects, }: {
25
+ export declare function CoreIAMProvider({ children, initialIdentity }: {
32
26
  children: React.ReactNode;
33
- /** Pass the result of a server-side getSession() call to skip the client fetch on first paint. */
34
27
  initialIdentity?: CoreIAMIdentity | null;
35
- /** Where to redirect after login/logout. Omit for no redirect (caller handles navigation itself). */
36
- redirects?: CoreIAMRedirects;
37
28
  }): import("react/jsx-runtime").JSX.Element;
38
29
  export declare function useAuth(): AuthContextType;
39
30
  export {};