@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,7 +1,16 @@
1
+ import { type JWTPayload } from 'jose';
1
2
  export interface CoreIAMConfig {
2
3
  baseUrl?: string;
3
4
  apiKey?: string;
4
5
  tenantId?: string;
6
+ /** Expected token issuer. Default: 'coreiam'. */
7
+ issuer?: string;
8
+ /** Optional strict audience check — off by default, matches current gateway tokens. */
9
+ audience?: string;
10
+ /** Session cookie name. Default: 'jwt'. */
11
+ sessionCookieName?: string;
12
+ /** JWKS path on the gateway. Default: '/.well-known/jwks.json'. */
13
+ jwksPath?: string;
5
14
  }
6
15
  export interface CoreIAMIdentity {
7
16
  sub: string;
@@ -10,14 +19,83 @@ export interface CoreIAMIdentity {
10
19
  roles?: string[];
11
20
  [key: string]: any;
12
21
  }
22
+ /** Accepts a Headers instance, an Express req.headers object, or a raw Cookie string. */
23
+ export type SessionInput = Headers | Record<string, string | string[] | undefined> | string;
24
+ export type SessionFailureReason = 'no_cookie' | 'expired' | 'invalid_claims' | 'invalid_signature' | 'jwks_unavailable';
25
+ export interface SessionResult {
26
+ identity: CoreIAMIdentity | null;
27
+ reason?: SessionFailureReason;
28
+ }
29
+ export interface RefreshSessionResult {
30
+ identity: CoreIAMIdentity | null;
31
+ /** Raw Set-Cookie strings to apply on your response (rotated jwt/refreshToken). */
32
+ setCookies: string[] | null;
33
+ }
34
+ /** Maps gateway token claims → CoreIAMIdentity (same shape the Next.js middleware injects). */
35
+ export declare function toIdentity(payload: JWTPayload): CoreIAMIdentity;
36
+ /**
37
+ export class CoreIAM {
38
+ private apiKey: string = '';
39
+ private baseUrl: string = '';
40
+
41
+ constructor(config?: { apiKey?: string; baseUrl?: string }) {
42
+ this.apiKey = config?.apiKey || process.env.COREIAM_API_KEY || '';
43
+ // FIX: Removed hardcoded URL. Throw if missing.
44
+ this.baseUrl = config?.baseUrl || process.env.COREIAM_API_URL || '';
45
+
46
+ if (!this.apiKey) throw new Error('CoreIAM: Missing API Key (COREIAM_API_KEY)');
47
+ if (!this.baseUrl) throw new Error('CoreIAM: Missing Gateway URL (COREIAM_API_URL)');
48
+ }
49
+ */
13
50
  export declare class CoreIAM {
14
51
  private apiKey;
15
52
  private baseUrl;
16
- constructor(config?: {
17
- apiKey?: string;
18
- baseUrl?: string;
19
- });
20
- refreshTokens(headers: Headers): Promise<string[] | null>;
53
+ private issuer;
54
+ private audience?;
55
+ private sessionCookieName;
56
+ private jwksPath;
57
+ private jwks;
58
+ private refreshInFlight;
59
+ constructor(config?: CoreIAMConfig);
60
+ private getJwks;
61
+ /** Optional warm-up: call at boot so the first verification pays no JWKS latency. */
62
+ preloadJwks(): Promise<void>;
63
+ private readCookieHeader;
64
+ private readSessionCookie;
65
+ verifySession(input: SessionInput): Promise<SessionResult>;
66
+ getSession(input: SessionInput): Promise<CoreIAMIdentity | null>;
67
+ /**
68
+ * One-call silent refresh for non-Next servers: posts the refresh cookie
69
+ * to the gateway, verifies the rotated access token, and returns the new
70
+ * Set-Cookie strings so the caller can forward them on its response.
71
+ */
72
+ refreshSession(input: SessionInput): Promise<RefreshSessionResult>;
73
+ refreshTokens(input: SessionInput): Promise<string[] | null>;
74
+ private doRefresh;
75
+ /**
76
+ async refreshTokens(headers: Headers): Promise<string[] | null> {
77
+ try {
78
+ const response = await fetch(`${this.baseUrl}/auth/refresh-token`, {
79
+ method: 'POST',
80
+ headers: {
81
+ 'X-API-Key': this.apiKey,
82
+ 'cookie': headers.get('cookie') || '',
83
+ },
84
+ });
85
+
86
+ if (response.ok) {
87
+ const setCookies = response.headers.getSetCookie();
88
+ if (setCookies && setCookies.length > 0) {
89
+ return setCookies;
90
+ }
91
+ }
92
+ return null;
93
+ } catch (error) {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ */
21
99
  proxy(path: string, init: RequestInit, incomingHeaders: Headers): Promise<Response>;
22
100
  getCsrfToken(headers: Headers): Promise<Response>;
23
101
  login(body: any, headers: Headers): Promise<Response>;
@@ -1,41 +1,189 @@
1
1
  "use strict";
2
- // src/core/index.ts
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
4
3
  exports.CoreIAM = void 0;
4
+ exports.toIdentity = toIdentity;
5
+ // src/core/index.ts
6
+ const jose_1 = require("jose");
7
+ /** Maps gateway token claims → CoreIAMIdentity (same shape the Next.js middleware injects). */
8
+ function toIdentity(payload) {
9
+ return {
10
+ sub: payload.sub,
11
+ email: payload.email,
12
+ tenantId: payload.tenantId,
13
+ roles: Array.isArray(payload.roles) ? payload.roles : [],
14
+ };
15
+ }
16
+ /**
17
+ export class CoreIAM {
18
+ private apiKey: string = '';
19
+ private baseUrl: string = '';
20
+
21
+ constructor(config?: { apiKey?: string; baseUrl?: string }) {
22
+ this.apiKey = config?.apiKey || process.env.COREIAM_API_KEY || '';
23
+ // FIX: Removed hardcoded URL. Throw if missing.
24
+ this.baseUrl = config?.baseUrl || process.env.COREIAM_API_URL || '';
25
+
26
+ if (!this.apiKey) throw new Error('CoreIAM: Missing API Key (COREIAM_API_KEY)');
27
+ if (!this.baseUrl) throw new Error('CoreIAM: Missing Gateway URL (COREIAM_API_URL)');
28
+ }
29
+ */
5
30
  class CoreIAM {
6
31
  apiKey = '';
7
32
  baseUrl = '';
33
+ issuer = 'coreiam';
34
+ audience;
35
+ sessionCookieName = 'jwt';
36
+ jwksPath = '/.well-known/jwks.json';
37
+ jwks = null;
38
+ refreshInFlight = null;
8
39
  constructor(config) {
9
40
  this.apiKey = config?.apiKey || process.env.COREIAM_API_KEY || '';
10
- // FIX: Removed hardcoded URL. Throw if missing.
11
- this.baseUrl = config?.baseUrl || process.env.COREIAM_API_URL || '';
41
+ this.baseUrl = (config?.baseUrl || process.env.COREIAM_API_URL || '').replace(/\/+$/, '');
42
+ this.issuer = config?.issuer ?? 'coreiam';
43
+ this.audience = config?.audience;
44
+ this.sessionCookieName = config?.sessionCookieName ?? 'jwt';
45
+ this.jwksPath = config?.jwksPath ?? '/.well-known/jwks.json';
12
46
  if (!this.apiKey)
13
47
  throw new Error('CoreIAM: Missing API Key (COREIAM_API_KEY)');
14
48
  if (!this.baseUrl)
15
49
  throw new Error('CoreIAM: Missing Gateway URL (COREIAM_API_URL)');
16
50
  }
17
- // Exposed publicly so the Next.js layer can use it for getSession()
18
- async refreshTokens(headers) {
51
+ getJwks() {
52
+ // Lazy singleton: jose fetches once, caches, and handles key rotation.
53
+ this.jwks ??= (0, jose_1.createRemoteJWKSet)(new URL(`${this.baseUrl}${this.jwksPath}`), {
54
+ cooldownDuration: 30_000,
55
+ cacheMaxAge: 10 * 60_000,
56
+ });
57
+ return this.jwks;
58
+ }
59
+ /** Optional warm-up: call at boot so the first verification pays no JWKS latency. */
60
+ async preloadJwks() {
61
+ try {
62
+ // Deliberately look up a key that won't exist — jose fetches and
63
+ // caches the whole key set on the first lookup, which is the warm-up.
64
+ await this.getJwks()({ alg: 'RS256', kid: '__preload__' }, {});
65
+ }
66
+ catch {
67
+ /* expected: no matching key — the fetch is the point */
68
+ }
69
+ }
70
+ readCookieHeader(input) {
71
+ if (typeof input === 'string')
72
+ return input;
73
+ if (typeof input.get === 'function')
74
+ return input.get('cookie') || '';
75
+ return input.cookie || '';
76
+ }
77
+ readSessionCookie(input) {
78
+ const raw = this.readCookieHeader(input);
79
+ if (!raw)
80
+ return null;
81
+ // Anchored to a real cookie-name boundary (same pattern proxy() uses),
82
+ // so a cookie named "notjwt" or "xjwt" can never match.
83
+ const escaped = this.sessionCookieName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
84
+ const match = raw.match(new RegExp(`(?:^|;\\s*)${escaped}=([^;]+)`));
85
+ return match ? decodeURIComponent(match[1]) : null;
86
+ }
87
+ async verifySession(input) {
88
+ const token = this.readSessionCookie(input);
89
+ if (!token)
90
+ return { identity: null, reason: 'no_cookie' };
91
+ try {
92
+ const { payload } = await (0, jose_1.jwtVerify)(token, this.getJwks(), {
93
+ algorithms: ['RS256'], // pinned — blocks alg confusion
94
+ issuer: this.issuer,
95
+ ...(this.audience ? { audience: this.audience } : {}),
96
+ });
97
+ return { identity: toIdentity(payload) };
98
+ }
99
+ catch (error) {
100
+ if (error instanceof jose_1.errors.JWTExpired)
101
+ return { identity: null, reason: 'expired' };
102
+ if (error instanceof jose_1.errors.JWTClaimValidationFailed)
103
+ return { identity: null, reason: 'invalid_claims' };
104
+ // JOSE errors = the token itself is bad. Anything else (fetch failure
105
+ // while getting the JWKS) = we couldn't check — that is not a sign-out.
106
+ if (error instanceof jose_1.errors.JOSEError)
107
+ return { identity: null, reason: 'invalid_signature' };
108
+ return { identity: null, reason: 'jwks_unavailable' };
109
+ }
110
+ }
111
+ async getSession(input) {
112
+ return (await this.verifySession(input)).identity;
113
+ }
114
+ /**
115
+ * One-call silent refresh for non-Next servers: posts the refresh cookie
116
+ * to the gateway, verifies the rotated access token, and returns the new
117
+ * Set-Cookie strings so the caller can forward them on its response.
118
+ */
119
+ async refreshSession(input) {
120
+ const setCookies = await this.refreshTokens(input);
121
+ if (!setCookies)
122
+ return { identity: null, setCookies: null };
123
+ const rotated = setCookies
124
+ .find(c => c.startsWith(`${this.sessionCookieName}=`))
125
+ ?.split(';')[0]; // "jwt=<new value>" — a valid single-cookie header string
126
+ if (rotated) {
127
+ const { identity } = await this.verifySession(rotated);
128
+ if (identity)
129
+ return { identity, setCookies };
130
+ }
131
+ return { identity: null, setCookies };
132
+ }
133
+ async refreshTokens(input) {
134
+ // Single-flight: concurrent requests share one gateway refresh call
135
+ // (matters with rotating refresh tokens). Input widened from Headers
136
+ // to SessionInput — backwards compatible.
137
+ this.refreshInFlight ??= this.doRefresh(input).finally(() => {
138
+ this.refreshInFlight = null;
139
+ });
140
+ return this.refreshInFlight;
141
+ }
142
+ async doRefresh(input) {
19
143
  try {
20
144
  const response = await fetch(`${this.baseUrl}/auth/refresh-token`, {
21
145
  method: 'POST',
22
146
  headers: {
23
147
  'X-API-Key': this.apiKey,
24
- 'cookie': headers.get('cookie') || '',
148
+ 'cookie': this.readCookieHeader(input),
25
149
  },
26
150
  });
27
151
  if (response.ok) {
28
152
  const setCookies = response.headers.getSetCookie();
29
- if (setCookies && setCookies.length > 0) {
153
+ if (setCookies && setCookies.length > 0)
30
154
  return setCookies;
31
- }
32
155
  }
33
156
  return null;
34
157
  }
35
- catch (error) {
158
+ catch {
36
159
  return null;
37
160
  }
38
161
  }
162
+ // Exposed publicly so the Next.js layer can use it for getSession()
163
+ /**
164
+ async refreshTokens(headers: Headers): Promise<string[] | null> {
165
+ try {
166
+ const response = await fetch(`${this.baseUrl}/auth/refresh-token`, {
167
+ method: 'POST',
168
+ headers: {
169
+ 'X-API-Key': this.apiKey,
170
+ 'cookie': headers.get('cookie') || '',
171
+ },
172
+ });
173
+
174
+ if (response.ok) {
175
+ const setCookies = response.headers.getSetCookie();
176
+ if (setCookies && setCookies.length > 0) {
177
+ return setCookies;
178
+ }
179
+ }
180
+ return null;
181
+ } catch (error) {
182
+ return null;
183
+ }
184
+ }
185
+
186
+ */
39
187
  async proxy(path, init, incomingHeaders) {
40
188
  const url = `${this.baseUrl}${path}`;
41
189
  const headers = new Headers(init.headers);
@@ -0,0 +1,26 @@
1
+ import { Router, type RequestHandler } from 'express';
2
+ import { type CoreIAMConfig } from '../core';
3
+ export interface CoreIamExpressOptions extends CoreIAMConfig {
4
+ /**
5
+ * Public URL of the app as the user's browser reaches it (e.g. http://localhost:5173
6
+ * in Vite dev, or your production domain). /api/auth must route from this origin
7
+ * to this Express server (reverse proxy in prod, Vite dev proxy in dev).
8
+ */
9
+ appUrl: string;
10
+ /** Mount path of the auth router. Default: '/api/auth'. */
11
+ authPath?: string;
12
+ /** Redirect target after successful OAuth login. Default: '/'. */
13
+ successUrl?: string;
14
+ /** Redirect target when OAuth initiation fails. Default: '/login'. */
15
+ signInUrl?: string;
16
+ }
17
+ export declare function coreIamExpress(options: CoreIamExpressOptions): Router;
18
+ /**
19
+ * Route guard: verifies the session cookie locally and puts the identity on
20
+ * res.locals.identity. 401s API requests; redirects page requests.
21
+ */
22
+ export declare function requireAuth(config?: CoreIAMConfig & {
23
+ signInUrl?: string;
24
+ }): RequestHandler;
25
+ export { CoreIAM } from '../core';
26
+ export type { CoreIAMIdentity } from '../core';
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CoreIAM = void 0;
4
+ exports.coreIamExpress = coreIamExpress;
5
+ exports.requireAuth = requireAuth;
6
+ // src/express/index.ts
7
+ const express_1 = require("express");
8
+ const core_1 = require("../core");
9
+ const CSRF_PROTECTED = new Set([
10
+ '/auth/login', '/auth/register', '/auth/profile',
11
+ '/auth/mfa/setup', '/auth/mfa/verify', '/auth/mfa/disable',
12
+ '/auth/request-otp', '/auth/verify-otp',
13
+ ]);
14
+ /** Express 4 does not catch async rejections — route them to next(err). */
15
+ const wrap = (fn) => (req, res, next) => { fn(req, res, next).catch(next); };
16
+ function sessionCookie(name, value, maxAgeSec, isProd) {
17
+ return `${name}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAgeSec}${isProd ? '; Secure' : ''}`;
18
+ }
19
+ function coreIamExpress(options) {
20
+ const appUrl = options.appUrl.replace(/\/+$/, '');
21
+ const authPath = (options.authPath ?? '/api/auth').replace(/\/+$/, '');
22
+ const successUrl = options.successUrl ?? '/';
23
+ const signInUrl = options.signInUrl ?? '/login';
24
+ const gatewayUrl = (options.baseUrl || process.env.COREIAM_API_URL || '').replace(/\/+$/, '');
25
+ const isProd = process.env.NODE_ENV === 'production';
26
+ const iam = new core_1.CoreIAM(options);
27
+ const router = (0, express_1.Router)();
28
+ // Body parsing for JSON + the urlencoded link-confirm post.
29
+ // Skips automatically if the host app already parsed the body.
30
+ router.use((0, express_1.json)());
31
+ router.use((0, express_1.urlencoded)({ extended: false }));
32
+ const toHeaders = (req) => new Headers(req.headers);
33
+ // ---------------------------------------------------------------
34
+ // GET {authPath}/session — matches the React provider's default
35
+ // sessionUrl ('/api/auth/session'). Local JWKS verify + silent refresh.
36
+ // ---------------------------------------------------------------
37
+ router.get('/session', wrap(async (req, res) => {
38
+ const result = await iam.verifySession(req.headers);
39
+ if (result.identity)
40
+ return void res.json(result.identity);
41
+ if (result.reason === 'expired') {
42
+ const { identity, setCookies } = await iam.refreshSession(req.headers);
43
+ if (identity) {
44
+ setCookies?.forEach(c => res.append('Set-Cookie', c));
45
+ return void res.json(identity);
46
+ }
47
+ }
48
+ res.status(401).json({ error: 'unauthenticated' });
49
+ }));
50
+ // ---------------------------------------------------------------
51
+ // OAuth initiate: GET {authPath}/oauth/:provider
52
+ // ---------------------------------------------------------------
53
+ router.get('/oauth/:provider', wrap(async (req, res, next) => {
54
+ const provider = req.params.provider;
55
+ if (provider === 'link')
56
+ return next(); // not an initiate route
57
+ const redirectUri = `${appUrl}${authPath}/callback/${provider}`;
58
+ const target = `/auth/oauth/initiate/${provider}?redirectUri=${encodeURIComponent(redirectUri)}&scopes=email,profile`;
59
+ const response = await iam.proxy(target, { method: 'GET' }, toHeaders(req));
60
+ const data = await response.json().catch(() => ({}));
61
+ if (data.url)
62
+ return void res.redirect(302, data.url);
63
+ res.redirect(302, signInUrl);
64
+ }));
65
+ // ---------------------------------------------------------------
66
+ // OAuth callback: GET {authPath}/callback/:provider
67
+ // ---------------------------------------------------------------
68
+ router.get('/callback/:provider', wrap(async (req, res) => {
69
+ const provider = req.params.provider;
70
+ const code = req.query.code;
71
+ const state = req.query.state;
72
+ const redirectUri = `${appUrl}${authPath}/callback/${provider}`;
73
+ const target = `/auth/oauth/callback/${provider}?code=${code}&state=${state}&redirectUri=${encodeURIComponent(redirectUri)}`;
74
+ const response = await iam.proxy(target, { method: 'GET' }, toHeaders(req));
75
+ const data = await response.json().catch(() => ({}));
76
+ // Account linking: redirect to CoreIAM's hosted confirmation page
77
+ if (data.action === 'link_candidate') {
78
+ const redirect = new URL(`${gatewayUrl}/auth/hosted/confirm-link`);
79
+ redirect.searchParams.set('token', data.tempToken);
80
+ redirect.searchParams.set('provider', data.provider);
81
+ redirect.searchParams.set('email', data.email || '');
82
+ redirect.searchParams.set('appOrigin', appUrl);
83
+ return void res.redirect(303, redirect.toString());
84
+ }
85
+ // Normal OAuth login: forward the gateway's session cookies, then redirect.
86
+ // (Set-Cookie must be appended BEFORE res.redirect — Express sends immediately.)
87
+ response.headers.getSetCookie().forEach(c => res.append('Set-Cookie', c));
88
+ res.redirect(302, successUrl);
89
+ }));
90
+ // ---------------------------------------------------------------
91
+ // Account-link confirmation: POST {authPath}/oauth/link/confirm
92
+ // (the hosted page posts tempToken + password back here)
93
+ // ---------------------------------------------------------------
94
+ router.post('/oauth/link/confirm', wrap(async (req, res) => {
95
+ const formData = (req.body ?? {});
96
+ const response = await iam.proxy('/auth/oauth/link/confirm', {
97
+ method: 'POST',
98
+ body: JSON.stringify({ tempToken: formData.tempToken, password: formData.password }),
99
+ headers: { 'Content-Type': 'application/json' },
100
+ }, toHeaders(req));
101
+ const data = await response.json().catch(() => ({}));
102
+ if (response.ok && data.success) {
103
+ const finalUrl = new URL(data.redirectUri || '/', appUrl).toString();
104
+ // Same cookie attributes the Next.js handler uses.
105
+ if (data.accessToken)
106
+ res.append('Set-Cookie', sessionCookie('jwt', data.accessToken, 24 * 60 * 60, isProd));
107
+ if (data.clientRefreshToken)
108
+ res.append('Set-Cookie', sessionCookie('refreshToken', data.clientRefreshToken, 7 * 24 * 60 * 60, isProd));
109
+ // 200 + JS redirect breaks the cross-site redirect chain so the browser
110
+ // sends the freshly-set (SameSite=Lax) cookies on the next hop.
111
+ return void res.status(200).type('html').send(`<!DOCTYPE html>
112
+ <html>
113
+ <head><title>Linking Successful</title></head>
114
+ <body>
115
+ <p>Account linked successfully! Redirecting...</p>
116
+ <script>window.location.href = ${JSON.stringify(finalUrl)};</script>
117
+ </body>
118
+ </html>`);
119
+ }
120
+ // Failed (wrong password) -> back to the hosted page with the error
121
+ const errorRedirect = new URL(`${gatewayUrl}/auth/hosted/confirm-link`);
122
+ errorRedirect.searchParams.set('token', formData.tempToken);
123
+ errorRedirect.searchParams.set('provider', data.provider || 'google');
124
+ errorRedirect.searchParams.set('email', data.email || '');
125
+ errorRedirect.searchParams.set('appOrigin', appUrl);
126
+ errorRedirect.searchParams.set('error', data.message || 'Invalid password. Link failed.');
127
+ res.redirect(303, errorRedirect.toString());
128
+ }));
129
+ // ---------------------------------------------------------------
130
+ // Everything else: generic JSON passthrough (login, register, logout,
131
+ // csrf-token, MFA, OTP, profile...). CSRF-protected endpoints run the
132
+ // handshake first, same as the Next.js server actions.
133
+ // ---------------------------------------------------------------
134
+ router.use(wrap(async (req, res) => {
135
+ const target = `/auth${req.path}`; // /login -> /auth/login
136
+ const init = { method: req.method };
137
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
138
+ init.body = req.body ? JSON.stringify(req.body) : undefined;
139
+ init.headers = { 'Content-Type': 'application/json' };
140
+ }
141
+ const response = CSRF_PROTECTED.has(target)
142
+ ? await iam.proxyWithCsrfProtection(target, init, toHeaders(req))
143
+ : await iam.proxy(target, init, toHeaders(req));
144
+ const data = await response.json().catch(() => ({}));
145
+ res.status(response.status);
146
+ response.headers.getSetCookie().forEach(c => res.append('Set-Cookie', c));
147
+ res.json(data);
148
+ }));
149
+ return router;
150
+ }
151
+ /**
152
+ * Route guard: verifies the session cookie locally and puts the identity on
153
+ * res.locals.identity. 401s API requests; redirects page requests.
154
+ */
155
+ function requireAuth(config) {
156
+ const iam = new core_1.CoreIAM(config);
157
+ const { signInUrl = '/login' } = config ?? {};
158
+ return wrap(async (req, res, next) => {
159
+ const identity = await iam.getSession(req.headers);
160
+ if (!identity) {
161
+ if (req.path.startsWith('/api') || req.accepts(['html', 'json']) === 'json') {
162
+ return void res.status(401).json({ error: 'unauthenticated' });
163
+ }
164
+ return void res.redirect(302, signInUrl);
165
+ }
166
+ res.locals.identity = identity;
167
+ next();
168
+ });
169
+ }
170
+ var core_2 = require("../core");
171
+ Object.defineProperty(exports, "CoreIAM", { enumerable: true, get: function () { return core_2.CoreIAM; } });
@@ -189,16 +189,59 @@ async function syncCookiesToNext(response) {
189
189
  // cookies. If middleware already refreshed upstream, this succeeds; if
190
190
  // middleware's refresh also failed, returning null here is correct, not a
191
191
  // bug to work around.
192
+ // export async function getSession(): Promise<CoreIAMIdentity | null> {
193
+ // console.log(`[DEBUG_TRACE] getSession() real fetch @ ${new Date().toISOString()}`);
194
+ // const reqHeaders = await headers();
195
+ // const response = await getIam().proxy('/auth/profile', { method: 'GET' }, reqHeaders);
196
+ // if (!response.ok) return null;
197
+ // const coreIamIdentity = await response.json();
198
+ // if (!coreIamIdentity?.sub) return null;
199
+ // return coreIamIdentity as CoreIAMIdentity;
200
+ // }
192
201
  async function getSession() {
193
- console.log(`[DEBUG_TRACE] getSession() real fetch @ ${new Date().toISOString()}`);
194
202
  const reqHeaders = await (0, headers_1.headers)();
195
- const response = await getIam().proxy('/auth/profile', { method: 'GET' }, reqHeaders);
196
- if (!response.ok)
203
+ // The middleware verified the JWT and attached the payload to the request headers
204
+ const identityHeader = reqHeaders.get('x-coreiam-identity');
205
+ if (identityHeader) {
206
+ try {
207
+ return JSON.parse(identityHeader);
208
+ }
209
+ catch {
210
+ return null;
211
+ }
212
+ }
213
+ // Fallback: If middleware didn't run (e.g. API route), we can still read the JWT
214
+ // But we DO NOT verify it here, because verification requires the JWKS fetch which
215
+ // is too slow for Server Components. We just decode it to get the identity.
216
+ // The middleware is the true security gate.
217
+ const authHeader = reqHeaders.get('Authorization') || reqHeaders.get('authorization');
218
+ const cookieHeader = reqHeaders.get('cookie');
219
+ let token = undefined;
220
+ if (authHeader?.startsWith('Bearer ')) {
221
+ token = authHeader.substring(7);
222
+ }
223
+ else if (cookieHeader) {
224
+ const match = cookieHeader.match(/jwt=([^;]+)/);
225
+ if (match)
226
+ token = match[1];
227
+ }
228
+ if (!token)
197
229
  return null;
198
- const coreIamIdentity = await response.json();
199
- if (!coreIamIdentity?.sub)
230
+ try {
231
+ // Just decode the payload without verifying (middleware already verified it)
232
+ const payloadB64 = token.split('.')[1];
233
+ const payloadStr = Buffer.from(payloadB64, 'base64').toString('utf8');
234
+ const payload = JSON.parse(payloadStr);
235
+ return {
236
+ sub: payload.sub,
237
+ email: payload.email,
238
+ tenantId: payload.tenantId,
239
+ roles: payload.roles || []
240
+ };
241
+ }
242
+ catch {
200
243
  return null;
201
- return coreIamIdentity;
244
+ }
202
245
  }
203
246
  async function loginActionSdk(identifier, password) {
204
247
  const reqHeaders = await (0, headers_1.headers)();
@@ -1,10 +1,17 @@
1
- export { CoreIAMProvider, useAuth } from './provider';
2
- export { loginActionSdk, registerActionSdk, logoutActionSdk, getSession } from './actions';
3
- export { coreIamMiddleware } from './middleware';
4
1
  import { NextRequest, NextResponse } from 'next/server';
5
- import { CoreIAM } from '../core';
2
+ import { CoreIAM, CoreIAMIdentity } from '../core';
3
+ export { CoreIAMProvider, useAuth } from './provider';
4
+ export { coreIamMiddleware, coreIamAuth } from './middleware';
5
+ export { getSession, loginActionSdk, registerActionSdk, logoutActionSdk, updateProfileActionSdk, setupMfaActionSdk, verifyMfaActionSdk, disableMfaActionSdk, requestOtpActionSdk, verifyOtpActionSdk } from './actions';
6
6
  export declare class NextHandlers {
7
7
  private iam;
8
8
  constructor(iam?: CoreIAM);
9
9
  handleProxy(req: NextRequest, internalPath: string): Promise<NextResponse<any>>;
10
10
  }
11
+ export declare class OAuthHandlers {
12
+ private iam;
13
+ constructor(iam?: CoreIAM);
14
+ handleInitiate(req: NextRequest, provider: string): Promise<NextResponse<unknown>>;
15
+ handleCallback(req: NextRequest, provider: string): Promise<NextResponse<unknown>>;
16
+ }
17
+ export declare function auth(request: NextRequest): Promise<CoreIAMIdentity | null>;