@coreiam/auth 1.0.13 → 1.0.15

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.
@@ -23,6 +23,22 @@ export declare class CoreIAM {
23
23
  login(body: any, headers: Headers): Promise<Response>;
24
24
  register(body: any, headers: Headers): Promise<Response>;
25
25
  logout(headers: Headers): Promise<Response>;
26
+ /**
27
+ * FIX: shared CSRF handshake, factored out of what used to be two
28
+ * near-identical copies in loginWithCsrfProtection/registerWithCsrfProtection.
29
+ * Fetches a CSRF token, attaches it as a header, merges the csrfToken
30
+ * cookie the backend just set into the outgoing cookie header, then
31
+ * proxies the real request through with both in place. Every
32
+ * CsrfGuard-decorated CoreIAM endpoint should go through this, not just
33
+ * login/register — see the new methods below.
34
+ */
35
+ proxyWithCsrfProtection(path: string, init: RequestInit, incomingHeaders: Headers): Promise<Response>;
26
36
  loginWithCsrfProtection(identifier: string, password: string, incomingHeaders: Headers): Promise<Response>;
27
37
  registerWithCsrfProtection(userData: any, incomingHeaders: Headers): Promise<Response>;
38
+ updateProfile(updateData: any, incomingHeaders: Headers): Promise<Response>;
39
+ setupMfa(incomingHeaders: Headers): Promise<Response>;
40
+ verifyMfa(token: string, incomingHeaders: Headers): Promise<Response>;
41
+ disableMfa(incomingHeaders: Headers): Promise<Response>;
42
+ requestOtp(body: any, incomingHeaders: Headers): Promise<Response>;
43
+ verifyOtp(body: any, incomingHeaders: Headers): Promise<Response>;
28
44
  }
@@ -79,7 +79,59 @@ class CoreIAM {
79
79
  async logout(headers) {
80
80
  return this.proxy('/auth/logout', { method: 'POST' }, headers);
81
81
  }
82
- async loginWithCsrfProtection(identifier, password, incomingHeaders) {
82
+ // async loginWithCsrfProtection(identifier: string, password: string, incomingHeaders: Headers) {
83
+ // const csrfResponse = await this.getCsrfToken(incomingHeaders);
84
+ // const csrfData = await csrfResponse.json();
85
+ // const csrfToken = csrfData.csrfToken || csrfData._csrf;
86
+ // if (!csrfToken) throw new Error('Failed to obtain CSRF token');
87
+ // const authHeaders = new Headers(incomingHeaders);
88
+ // authHeaders.set('x-csrf-token', csrfToken);
89
+ // const csrfCookies = csrfResponse.headers.getSetCookie();
90
+ // const existingCookieStr = incomingHeaders.get('cookie') || '';
91
+ // const newCookiePairs = csrfCookies.map(c => c.split(';')[0]);
92
+ // if (newCookiePairs.length > 0) {
93
+ // const combinedCookies = [existingCookieStr, ...newCookiePairs].filter(Boolean).join('; ');
94
+ // authHeaders.set('cookie', combinedCookies);
95
+ // }
96
+ // return this.proxy('/auth/login', {
97
+ // method: 'POST',
98
+ // headers: { 'Content-Type': 'application/json' },
99
+ // body: JSON.stringify({ identifier, password }),
100
+ // }, authHeaders);
101
+ // }
102
+ // async registerWithCsrfProtection(userData: any, incomingHeaders: Headers) {
103
+ // const csrfResponse = await this.getCsrfToken(incomingHeaders);
104
+ // const csrfData = await csrfResponse.json();
105
+ // const csrfToken = csrfData.csrfToken || csrfData._csrf;
106
+ // if (!csrfToken) throw new Error('Failed to obtain CSRF token');
107
+ // const authHeaders = new Headers(incomingHeaders);
108
+ // authHeaders.set('x-csrf-token', csrfToken);
109
+ // const setCookieHeader = csrfResponse.headers.get('set-cookie');
110
+ // if (setCookieHeader) {
111
+ // const existingCookie = incomingHeaders.get('cookie') || '';
112
+ // const updatedCookie = setCookieHeader.split(',').map(cookie => cookie.trim())
113
+ // .map(cookie => cookie.split(';')[0])
114
+ // .join('; ');
115
+ // const combinedCookies = [existingCookie, updatedCookie].filter(Boolean).join('; ');
116
+ // authHeaders.set('cookie', combinedCookies);
117
+ // }
118
+ // return this.proxy('/auth/register', {
119
+ // method: 'POST',
120
+ // headers: { 'Content-Type': 'application/json' },
121
+ // body: JSON.stringify(userData),
122
+ // }, authHeaders);
123
+ // }
124
+ // }
125
+ /**
126
+ * FIX: shared CSRF handshake, factored out of what used to be two
127
+ * near-identical copies in loginWithCsrfProtection/registerWithCsrfProtection.
128
+ * Fetches a CSRF token, attaches it as a header, merges the csrfToken
129
+ * cookie the backend just set into the outgoing cookie header, then
130
+ * proxies the real request through with both in place. Every
131
+ * CsrfGuard-decorated CoreIAM endpoint should go through this, not just
132
+ * login/register — see the new methods below.
133
+ */
134
+ async proxyWithCsrfProtection(path, init, incomingHeaders) {
83
135
  const csrfResponse = await this.getCsrfToken(incomingHeaders);
84
136
  const csrfData = await csrfResponse.json();
85
137
  const csrfToken = csrfData.csrfToken || csrfData._csrf;
@@ -94,34 +146,55 @@ class CoreIAM {
94
146
  const combinedCookies = [existingCookieStr, ...newCookiePairs].filter(Boolean).join('; ');
95
147
  authHeaders.set('cookie', combinedCookies);
96
148
  }
97
- return this.proxy('/auth/login', {
149
+ return this.proxy(path, init, authHeaders);
150
+ }
151
+ async loginWithCsrfProtection(identifier, password, incomingHeaders) {
152
+ return this.proxyWithCsrfProtection('/auth/login', {
98
153
  method: 'POST',
99
154
  headers: { 'Content-Type': 'application/json' },
100
155
  body: JSON.stringify({ identifier, password }),
101
- }, authHeaders);
156
+ }, incomingHeaders);
102
157
  }
103
158
  async registerWithCsrfProtection(userData, incomingHeaders) {
104
- const csrfResponse = await this.getCsrfToken(incomingHeaders);
105
- const csrfData = await csrfResponse.json();
106
- const csrfToken = csrfData.csrfToken || csrfData._csrf;
107
- if (!csrfToken)
108
- throw new Error('Failed to obtain CSRF token');
109
- const authHeaders = new Headers(incomingHeaders);
110
- authHeaders.set('x-csrf-token', csrfToken);
111
- const setCookieHeader = csrfResponse.headers.get('set-cookie');
112
- if (setCookieHeader) {
113
- const existingCookie = incomingHeaders.get('cookie') || '';
114
- const updatedCookie = setCookieHeader.split(',').map(cookie => cookie.trim())
115
- .map(cookie => cookie.split(';')[0])
116
- .join('; ');
117
- const combinedCookies = [existingCookie, updatedCookie].filter(Boolean).join('; ');
118
- authHeaders.set('cookie', combinedCookies);
119
- }
120
- return this.proxy('/auth/register', {
159
+ return this.proxyWithCsrfProtection('/auth/register', {
121
160
  method: 'POST',
122
161
  headers: { 'Content-Type': 'application/json' },
123
162
  body: JSON.stringify(userData),
124
- }, authHeaders);
163
+ }, incomingHeaders);
164
+ }
165
+ async updateProfile(updateData, incomingHeaders) {
166
+ return this.proxyWithCsrfProtection('/auth/profile', {
167
+ method: 'PUT',
168
+ headers: { 'Content-Type': 'application/json' },
169
+ body: JSON.stringify(updateData),
170
+ }, incomingHeaders);
171
+ }
172
+ async setupMfa(incomingHeaders) {
173
+ return this.proxyWithCsrfProtection('/auth/mfa/setup', { method: 'POST' }, incomingHeaders);
174
+ }
175
+ async verifyMfa(token, incomingHeaders) {
176
+ return this.proxyWithCsrfProtection('/auth/mfa/verify', {
177
+ method: 'POST',
178
+ headers: { 'Content-Type': 'application/json' },
179
+ body: JSON.stringify({ token }),
180
+ }, incomingHeaders);
181
+ }
182
+ async disableMfa(incomingHeaders) {
183
+ return this.proxyWithCsrfProtection('/auth/mfa/disable', { method: 'DELETE' }, incomingHeaders);
184
+ }
185
+ async requestOtp(body, incomingHeaders) {
186
+ return this.proxyWithCsrfProtection('/auth/request-otp', {
187
+ method: 'POST',
188
+ headers: { 'Content-Type': 'application/json' },
189
+ body: JSON.stringify(body),
190
+ }, incomingHeaders);
191
+ }
192
+ async verifyOtp(body, incomingHeaders) {
193
+ return this.proxyWithCsrfProtection('/auth/verify-otp', {
194
+ method: 'POST',
195
+ headers: { 'Content-Type': 'application/json' },
196
+ body: JSON.stringify(body),
197
+ }, incomingHeaders);
125
198
  }
126
199
  }
127
200
  exports.CoreIAM = CoreIAM;
@@ -4,7 +4,13 @@ export interface AuthResult {
4
4
  status: number;
5
5
  data: any;
6
6
  }
7
- export declare const getSession: () => Promise<CoreIAMIdentity | null>;
7
+ export declare function getSession(): Promise<CoreIAMIdentity | null>;
8
8
  export declare function loginActionSdk(identifier: string, password: string): Promise<AuthResult>;
9
9
  export declare function registerActionSdk(userData: any): Promise<AuthResult>;
10
10
  export declare function logoutActionSdk(): Promise<AuthResult>;
11
+ export declare function updateProfileActionSdk(updateData: Record<string, any>): Promise<AuthResult>;
12
+ export declare function setupMfaActionSdk(): Promise<AuthResult>;
13
+ export declare function verifyMfaActionSdk(token: string): Promise<AuthResult>;
14
+ export declare function disableMfaActionSdk(): Promise<AuthResult>;
15
+ export declare function requestOtpActionSdk(body: Record<string, any>): Promise<AuthResult>;
16
+ export declare function verifyOtpActionSdk(body: Record<string, any>): Promise<AuthResult>;
@@ -1,11 +1,6 @@
1
1
  "use strict";
2
2
  // // src/nextjs/actions.ts
3
3
  // // REMOVE 'use server' from here.
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.getSession = void 0;
6
- exports.loginActionSdk = loginActionSdk;
7
- exports.registerActionSdk = registerActionSdk;
8
- exports.logoutActionSdk = logoutActionSdk;
9
4
  // import { cookies, headers } from 'next/headers';
10
5
  // import { CoreIAM, CoreIAMIdentity } from '../core';
11
6
  // export interface AuthResult {
@@ -95,11 +90,34 @@ exports.logoutActionSdk = logoutActionSdk;
95
90
  // return { ok: false, status: 500, data: { error: 'Logout failed' } };
96
91
  // }
97
92
  // }
98
- // src/nextjs/actions.ts
99
- // NO 'use server' HERE. This is a module of server-safe functions.
100
- const react_1 = require("react");
93
+ 'use server';
94
+ // // src/nextjs/actions.ts
95
+ // // REMOVE 'use server' from here.
96
+ Object.defineProperty(exports, "__esModule", { value: true });
97
+ exports.getSession = getSession;
98
+ exports.loginActionSdk = loginActionSdk;
99
+ exports.registerActionSdk = registerActionSdk;
100
+ exports.logoutActionSdk = logoutActionSdk;
101
+ exports.updateProfileActionSdk = updateProfileActionSdk;
102
+ exports.setupMfaActionSdk = setupMfaActionSdk;
103
+ exports.verifyMfaActionSdk = verifyMfaActionSdk;
104
+ exports.disableMfaActionSdk = disableMfaActionSdk;
105
+ exports.requestOtpActionSdk = requestOtpActionSdk;
106
+ exports.verifyOtpActionSdk = verifyOtpActionSdk;
101
107
  const headers_1 = require("next/headers");
102
108
  const core_1 = require("../core");
109
+ // FIX: lazy singleton instead of `const iam = new CoreIAM();` at module
110
+ // scope. The constructor throws if COREIAM_API_KEY/COREIAM_API_URL are
111
+ // missing — fine on the server, where those env vars exist — but a
112
+ // module-level throw happens the instant the module is evaluated, before
113
+ // any function in it is even called. If this module's 'use server'
114
+ // boundary is ever not recognized correctly by the bundler (see the note
115
+ // on the browser error below), the real module gets included in the client
116
+ // bundle instead of being swapped for action-reference stubs, and that
117
+ // throw fires in the browser instead, where those env vars are never
118
+ // defined. Making this lazy doesn't fix a lost 'use server' boundary by
119
+ // itself, but it does mean import alone can never crash — only an actual
120
+ // invocation can, which is strictly better either way.
103
121
  let _iam = null;
104
122
  function getIam() {
105
123
  if (!_iam)
@@ -151,7 +169,28 @@ async function syncCookiesToNext(response) {
151
169
  }
152
170
  }
153
171
  }
154
- exports.getSession = (0, react_1.cache)(async () => {
172
+ // FIX: wrapped in React's cache(). This is the generic fix for "why are we
173
+ // calling /profile so much" — any number of calls to getSession() within the
174
+ // same request/render (direct calls, or nested inside a consumer's own
175
+ // currentUser()-style wrapper) now collapse into a single network round trip
176
+ // to CoreIAM automatically, for every current and future call site.
177
+ //
178
+ // Deliberately NOT adding a refresh-retry fallback here (an earlier draft in
179
+ // this thread did). Next.js only allows cookies().set()/cookies().delete()
180
+ // inside a Server Action or Route Handler — never during a Server Component
181
+ // render — and getSession() is called from both (e.g. from a plain
182
+ // RootLayout render, and from consumers' currentUser()-style wrappers used
183
+ // throughout Server Components). A refresh path that can only sometimes
184
+ // persist its new cookies isn't safe: on the render-context call it would
185
+ // still redeem the refresh token server-side (rotating/burning it) but never
186
+ // deliver the new one to the browser, stranding the client on a now-dead
187
+ // refresh token. Refresh belongs solely in middleware.ts, which is the one
188
+ // place in the request lifecycle actually allowed to mutate response
189
+ // cookies. If middleware already refreshed upstream, this succeeds; if
190
+ // middleware's refresh also failed, returning null here is correct, not a
191
+ // bug to work around.
192
+ async function getSession() {
193
+ console.log(`[DEBUG_TRACE] getSession() real fetch @ ${new Date().toISOString()}`);
155
194
  const reqHeaders = await (0, headers_1.headers)();
156
195
  const response = await getIam().proxy('/auth/profile', { method: 'GET' }, reqHeaders);
157
196
  if (!response.ok)
@@ -160,7 +199,7 @@ exports.getSession = (0, react_1.cache)(async () => {
160
199
  if (!coreIamIdentity?.sub)
161
200
  return null;
162
201
  return coreIamIdentity;
163
- });
202
+ }
164
203
  async function loginActionSdk(identifier, password) {
165
204
  const reqHeaders = await (0, headers_1.headers)();
166
205
  try {
@@ -194,3 +233,68 @@ async function logoutActionSdk() {
194
233
  return { ok: false, status: 500, data: { error: 'Logout failed' } };
195
234
  }
196
235
  }
236
+ // --- Newly added, prompted by an audit of CSRF coverage across every
237
+ // CsrfGuard-decorated CoreIAM endpoint (not just login/register). Each of
238
+ // these now handles its own CSRF handshake via core/index.ts's
239
+ // proxyWithCsrfProtection, the same way loginActionSdk/registerActionSdk
240
+ // already did — nothing calling these needs to fetch a CSRF token itself.
241
+ async function updateProfileActionSdk(updateData) {
242
+ const reqHeaders = await (0, headers_1.headers)();
243
+ try {
244
+ const response = await getIam().updateProfile(updateData, reqHeaders);
245
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
246
+ }
247
+ catch (error) {
248
+ return { ok: false, status: 500, data: { error: 'Profile update failed' } };
249
+ }
250
+ }
251
+ async function setupMfaActionSdk() {
252
+ const reqHeaders = await (0, headers_1.headers)();
253
+ try {
254
+ const response = await getIam().setupMfa(reqHeaders);
255
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
256
+ }
257
+ catch (error) {
258
+ return { ok: false, status: 500, data: { error: 'MFA setup failed' } };
259
+ }
260
+ }
261
+ async function verifyMfaActionSdk(token) {
262
+ const reqHeaders = await (0, headers_1.headers)();
263
+ try {
264
+ const response = await getIam().verifyMfa(token, reqHeaders);
265
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
266
+ }
267
+ catch (error) {
268
+ return { ok: false, status: 500, data: { error: 'MFA verification failed' } };
269
+ }
270
+ }
271
+ async function disableMfaActionSdk() {
272
+ const reqHeaders = await (0, headers_1.headers)();
273
+ try {
274
+ const response = await getIam().disableMfa(reqHeaders);
275
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
276
+ }
277
+ catch (error) {
278
+ return { ok: false, status: 500, data: { error: 'Disabling MFA failed' } };
279
+ }
280
+ }
281
+ async function requestOtpActionSdk(body) {
282
+ const reqHeaders = await (0, headers_1.headers)();
283
+ try {
284
+ const response = await getIam().requestOtp(body, reqHeaders);
285
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
286
+ }
287
+ catch (error) {
288
+ return { ok: false, status: 500, data: { error: 'OTP request failed' } };
289
+ }
290
+ }
291
+ async function verifyOtpActionSdk(body) {
292
+ const reqHeaders = await (0, headers_1.headers)();
293
+ try {
294
+ const response = await getIam().verifyOtp(body, reqHeaders);
295
+ return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
296
+ }
297
+ catch (error) {
298
+ return { ok: false, status: 500, data: { error: 'OTP verification failed' } };
299
+ }
300
+ }
@@ -1,15 +1,22 @@
1
1
  import React from 'react';
2
2
  import type { CoreIAMIdentity } from '../core';
3
3
  export interface CoreIAMRedirects {
4
+ /** Where to send the user after a successful login, if no callbackUrl is passed to login(). */
4
5
  afterLogin?: string;
6
+ /** Where to send the user after logout. */
5
7
  afterLogout?: string;
6
8
  }
7
9
  interface LoginResult {
8
10
  success?: boolean;
9
11
  error?: string;
10
- twoFactor?: boolean;
11
12
  }
12
13
  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
+ */
13
20
  identity: CoreIAMIdentity | null;
14
21
  isLoading: boolean;
15
22
  isAuthenticated: boolean;
@@ -23,7 +30,9 @@ interface AuthContextType {
23
30
  }
24
31
  export declare function CoreIAMProvider({ children, initialIdentity, redirects, }: {
25
32
  children: React.ReactNode;
33
+ /** Pass the result of a server-side getSession() call to skip the client fetch on first paint. */
26
34
  initialIdentity?: CoreIAMIdentity | null;
35
+ /** Where to redirect after login/logout. Omit for no redirect (caller handles navigation itself). */
27
36
  redirects?: CoreIAMRedirects;
28
37
  }): import("react/jsx-runtime").JSX.Element;
29
38
  export declare function useAuth(): AuthContextType;
@@ -1,5 +1,189 @@
1
1
  "use strict";
2
- // src/nextjs/provider.tsx
2
+ // // src/nextjs/provider.tsx
3
+ // 'use client';
4
+ // import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
5
+ // import { useRouter } from 'next/navigation';
6
+ // import type { CoreIAMIdentity } from '../core';
7
+ // export interface CoreIAMRedirects {
8
+ // afterLogin?: string;
9
+ // afterLogout?: string;
10
+ // }
11
+ // interface LoginResult {
12
+ // success?: boolean;
13
+ // error?: string;
14
+ // twoFactor?: boolean;
15
+ // }
16
+ // interface AuthContextType {
17
+ // identity: CoreIAMIdentity | null;
18
+ // isLoading: boolean;
19
+ // isAuthenticated: boolean;
20
+ // login: (values: { email: string; password: string }, callbackUrl?: string) => Promise<LoginResult>;
21
+ // logout: () => Promise<void>;
22
+ // register: (userData: Record<string, any>) => Promise<LoginResult>;
23
+ // refetchIdentity: () => Promise<void>;
24
+ // }
25
+ // const AuthContext = createContext<AuthContextType>({
26
+ // identity: null,
27
+ // isLoading: true,
28
+ // isAuthenticated: false,
29
+ // login: async () => ({ error: 'CoreIAMProvider is not mounted' }),
30
+ // logout: async () => {},
31
+ // register: async () => ({ error: 'CoreIAMProvider is not mounted' }),
32
+ // refetchIdentity: async () => {},
33
+ // });
34
+ // export function CoreIAMProvider({
35
+ // children,
36
+ // initialIdentity,
37
+ // redirects = {},
38
+ // }: {
39
+ // children: React.ReactNode;
40
+ // initialIdentity?: CoreIAMIdentity | null;
41
+ // redirects?: CoreIAMRedirects;
42
+ // }) {
43
+ // const [identity, setIdentity] = useState<CoreIAMIdentity | null>(initialIdentity ?? null);
44
+ // const [isLoading, setIsLoading] = useState(initialIdentity === undefined);
45
+ // const router = useRouter();
46
+ // const refetchIdentity = useCallback(async () => {
47
+ // try {
48
+ // // Call the Next.js API route, which securely calls CoreIAM
49
+ // const res = await fetch('/api/auth/session', { credentials: 'include' });
50
+ // setIdentity(res.ok ? await res.json() : null);
51
+ // } catch {
52
+ // setIdentity(null);
53
+ // } finally {
54
+ // setIsLoading(false);
55
+ // }
56
+ // }, []);
57
+ // useEffect(() => {
58
+ // if (initialIdentity === undefined) {
59
+ // refetchIdentity();
60
+ // } else {
61
+ // setIsLoading(false);
62
+ // }
63
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ // }, []);
65
+ // // const login = async (values: { email: string; password: string }, callbackUrl?: string): Promise<LoginResult> => {
66
+ // // setIsLoading(true);
67
+ // // try {
68
+ // // // Fetch CSRF token first
69
+ // // const csrfRes = await fetch('/api/auth/csrf', { credentials: 'include' });
70
+ // // if (!csrfRes.ok) return { error: 'Failed to get CSRF token' };
71
+ // // const { csrfToken } = await csrfRes.json();
72
+ // // // Call login API route
73
+ // // const loginRes = await fetch('/api/auth/login', {
74
+ // // method: 'POST',
75
+ // // headers: {
76
+ // // 'Content-Type': 'application/json',
77
+ // // 'x-csrf-token': csrfToken,
78
+ // // },
79
+ // // credentials: 'include',
80
+ // // body: JSON.stringify(values),
81
+ // // });
82
+ // // const data = await loginRes.json();
83
+ // // if (!loginRes.ok) {
84
+ // // return { error: data.message || data.error || 'Invalid credentials' };
85
+ // // }
86
+ // // if (data.twoFactor) {
87
+ // // return { twoFactor: true };
88
+ // // }
89
+ // // await refetchIdentity();
90
+ // // const target = callbackUrl || redirects.afterLogin;
91
+ // // if (target) router.push(target);
92
+ // // return { success: true };
93
+ // // } catch {
94
+ // // return { error: 'Something went wrong' };
95
+ // // } finally {
96
+ // // setIsLoading(false);
97
+ // // }
98
+ // // };
99
+ // const login = async (values: { email: string; password: string }, callbackUrl?: string): Promise<LoginResult> => {
100
+ // setIsLoading(true);
101
+ // try {
102
+ // // Fetch CSRF token first
103
+ // const csrfRes = await fetch('/api/auth/csrf', { credentials: 'include' });
104
+ // if (!csrfRes.ok) return { error: 'Failed to get CSRF token' };
105
+ // const { csrfToken } = await csrfRes.json();
106
+ // // FIX: Map 'email' to 'identifier' for the CoreIAM backend
107
+ // const loginPayload = {
108
+ // identifier: values.email,
109
+ // password: values.password,
110
+ // };
111
+ // // Call login API route
112
+ // const loginRes = await fetch('/api/auth/login', {
113
+ // method: 'POST',
114
+ // headers: {
115
+ // 'Content-Type': 'application/json',
116
+ // 'x-csrf-token': csrfToken,
117
+ // },
118
+ // credentials: 'include',
119
+ // body: JSON.stringify(loginPayload), // Send the mapped payload
120
+ // });
121
+ // const data = await loginRes.json();
122
+ // if (!loginRes.ok) {
123
+ // return { error: data.message || data.error || 'Invalid credentials' };
124
+ // }
125
+ // if (data.twoFactor) {
126
+ // return { twoFactor: true };
127
+ // }
128
+ // await refetchIdentity();
129
+ // const target = callbackUrl || redirects.afterLogin;
130
+ // if (target) router.push(target);
131
+ // return { success: true };
132
+ // } catch {
133
+ // return { error: 'Something went wrong' };
134
+ // } finally {
135
+ // setIsLoading(false);
136
+ // }
137
+ // };
138
+ // const logout = async () => {
139
+ // try {
140
+ // await fetch('/api/auth/logout', {
141
+ // method: 'POST',
142
+ // credentials: 'include',
143
+ // });
144
+ // } finally {
145
+ // setIdentity(null);
146
+ // if (redirects.afterLogout) router.push(redirects.afterLogout);
147
+ // }
148
+ // };
149
+ // const register = async (userData: Record<string, any>): Promise<LoginResult> => {
150
+ // setIsLoading(true);
151
+ // try {
152
+ // const csrfRes = await fetch('/api/auth/csrf', { credentials: 'include' });
153
+ // if (!csrfRes.ok) return { error: 'Failed to get CSRF token' };
154
+ // const { csrfToken } = await csrfRes.json();
155
+ // const registerRes = await fetch('/api/auth/register', {
156
+ // method: 'POST',
157
+ // headers: {
158
+ // 'Content-Type': 'application/json',
159
+ // 'x-csrf-token': csrfToken,
160
+ // },
161
+ // credentials: 'include',
162
+ // body: JSON.stringify(userData),
163
+ // });
164
+ // if (!registerRes.ok) {
165
+ // const error = await registerRes.json();
166
+ // return { error: error.message || error.error || 'Registration failed' };
167
+ // }
168
+ // // Auto-login after registration
169
+ // return await login({ email: userData.email, password: userData.password });
170
+ // } catch {
171
+ // return { error: 'Something went wrong' };
172
+ // } finally {
173
+ // setIsLoading(false);
174
+ // }
175
+ // };
176
+ // return (
177
+ // <AuthContext.Provider
178
+ // value={{ identity, isLoading, isAuthenticated: !!identity, login, logout, register, refetchIdentity }}
179
+ // >
180
+ // {children}
181
+ // </AuthContext.Provider>
182
+ // );
183
+ // }
184
+ // export function useAuth() {
185
+ // return useContext(AuthContext);
186
+ // }
3
187
  'use client';
4
188
  Object.defineProperty(exports, "__esModule", { value: true });
5
189
  exports.CoreIAMProvider = CoreIAMProvider;
@@ -7,6 +191,7 @@ exports.useAuth = useAuth;
7
191
  const jsx_runtime_1 = require("react/jsx-runtime");
8
192
  const react_1 = require("react");
9
193
  const navigation_1 = require("next/navigation");
194
+ const actions_1 = require("./actions");
10
195
  const AuthContext = (0, react_1.createContext)({
11
196
  identity: null,
12
197
  isLoading: true,
@@ -20,11 +205,14 @@ function CoreIAMProvider({ children, initialIdentity, redirects = {}, }) {
20
205
  const [identity, setIdentity] = (0, react_1.useState)(initialIdentity ?? null);
21
206
  const [isLoading, setIsLoading] = (0, react_1.useState)(initialIdentity === undefined);
22
207
  const router = (0, navigation_1.useRouter)();
23
- const refetchIdentity = (0, react_1.useCallback)(async () => {
208
+ const refetchIdentity = async () => {
209
+ // DEBUG_TRACE: browser console — tags who called this (mount effect vs.
210
+ // an explicit login/logout/register call). Temporary, remove once
211
+ // settled.
212
+ console.log(`[DEBUG_TRACE][CoreIAMProvider] refetchIdentity() called @ ${new Date().toISOString()}`);
24
213
  try {
25
- // Call the Next.js API route, which securely calls CoreIAM
26
- const res = await fetch('/api/auth/session', { credentials: 'include' });
27
- setIdentity(res.ok ? await res.json() : null);
214
+ const result = await (0, actions_1.getSession)();
215
+ setIdentity(result);
28
216
  }
29
217
  catch {
30
218
  setIdentity(null);
@@ -32,79 +220,34 @@ function CoreIAMProvider({ children, initialIdentity, redirects = {}, }) {
32
220
  finally {
33
221
  setIsLoading(false);
34
222
  }
35
- }, []);
223
+ };
36
224
  (0, react_1.useEffect)(() => {
225
+ // FIX: was `initialIdentity === null`, which can't tell "SSR ran and
226
+ // confirmed you're logged out" (a correct, final null) apart from "no
227
+ // SSR value was ever provided" (a real undefined). Since layout.tsx
228
+ // always calls getSession() and passes its result — even when that
229
+ // result legitimately is null — every logged-out page view was
230
+ // treated as "no SSR value, fetch again," firing a second, wholly
231
+ // redundant /auth/profile call for something SSR had already
232
+ // determined. undefined (the prop genuinely omitted) is the only case
233
+ // that should trigger this fallback fetch.
234
+ console.log(`[DEBUG_TRACE][CoreIAMProvider] mount effect ran, initialIdentity=${initialIdentity === undefined ? 'undefined' : JSON.stringify(initialIdentity)} @ ${new Date().toISOString()}`);
37
235
  if (initialIdentity === undefined) {
38
236
  refetchIdentity();
39
237
  }
40
238
  else {
41
239
  setIsLoading(false);
42
240
  }
241
+ // Intentionally only on mount — initialIdentity is a one-time SSR seed,
242
+ // not something we want to re-run this effect for if it changes identity.
43
243
  // eslint-disable-next-line react-hooks/exhaustive-deps
44
244
  }, []);
45
- // const login = async (values: { email: string; password: string }, callbackUrl?: string): Promise<LoginResult> => {
46
- // setIsLoading(true);
47
- // try {
48
- // // Fetch CSRF token first
49
- // const csrfRes = await fetch('/api/auth/csrf', { credentials: 'include' });
50
- // if (!csrfRes.ok) return { error: 'Failed to get CSRF token' };
51
- // const { csrfToken } = await csrfRes.json();
52
- // // Call login API route
53
- // const loginRes = await fetch('/api/auth/login', {
54
- // method: 'POST',
55
- // headers: {
56
- // 'Content-Type': 'application/json',
57
- // 'x-csrf-token': csrfToken,
58
- // },
59
- // credentials: 'include',
60
- // body: JSON.stringify(values),
61
- // });
62
- // const data = await loginRes.json();
63
- // if (!loginRes.ok) {
64
- // return { error: data.message || data.error || 'Invalid credentials' };
65
- // }
66
- // if (data.twoFactor) {
67
- // return { twoFactor: true };
68
- // }
69
- // await refetchIdentity();
70
- // const target = callbackUrl || redirects.afterLogin;
71
- // if (target) router.push(target);
72
- // return { success: true };
73
- // } catch {
74
- // return { error: 'Something went wrong' };
75
- // } finally {
76
- // setIsLoading(false);
77
- // }
78
- // };
79
245
  const login = async (values, callbackUrl) => {
80
246
  setIsLoading(true);
81
247
  try {
82
- // Fetch CSRF token first
83
- const csrfRes = await fetch('/api/auth/csrf', { credentials: 'include' });
84
- if (!csrfRes.ok)
85
- return { error: 'Failed to get CSRF token' };
86
- const { csrfToken } = await csrfRes.json();
87
- // FIX: Map 'email' to 'identifier' for the CoreIAM backend
88
- const loginPayload = {
89
- identifier: values.email,
90
- password: values.password,
91
- };
92
- // Call login API route
93
- const loginRes = await fetch('/api/auth/login', {
94
- method: 'POST',
95
- headers: {
96
- 'Content-Type': 'application/json',
97
- 'x-csrf-token': csrfToken,
98
- },
99
- credentials: 'include',
100
- body: JSON.stringify(loginPayload), // Send the mapped payload
101
- });
102
- const data = await loginRes.json();
103
- if (!loginRes.ok) {
104
- return { error: data.message || data.error || 'Invalid credentials' };
105
- }
106
- if (data.twoFactor) {
107
- return { twoFactor: true };
248
+ const result = await (0, actions_1.loginActionSdk)(values.email, values.password);
249
+ if (!result.ok) {
250
+ return { error: result.data?.message || result.data?.error || 'Invalid credentials' };
108
251
  }
109
252
  await refetchIdentity();
110
253
  const target = callbackUrl || redirects.afterLogin;
@@ -121,10 +264,7 @@ function CoreIAMProvider({ children, initialIdentity, redirects = {}, }) {
121
264
  };
122
265
  const logout = async () => {
123
266
  try {
124
- await fetch('/api/auth/logout', {
125
- method: 'POST',
126
- credentials: 'include',
127
- });
267
+ await (0, actions_1.logoutActionSdk)();
128
268
  }
129
269
  finally {
130
270
  setIdentity(null);
@@ -135,29 +275,14 @@ function CoreIAMProvider({ children, initialIdentity, redirects = {}, }) {
135
275
  const register = async (userData) => {
136
276
  setIsLoading(true);
137
277
  try {
138
- const csrfRes = await fetch('/api/auth/csrf', { credentials: 'include' });
139
- if (!csrfRes.ok)
140
- return { error: 'Failed to get CSRF token' };
141
- const { csrfToken } = await csrfRes.json();
142
- const registerRes = await fetch('/api/auth/register', {
143
- method: 'POST',
144
- headers: {
145
- 'Content-Type': 'application/json',
146
- 'x-csrf-token': csrfToken,
147
- },
148
- credentials: 'include',
149
- body: JSON.stringify(userData),
150
- });
151
- if (!registerRes.ok) {
152
- const error = await registerRes.json();
153
- return { error: error.message || error.error || 'Registration failed' };
278
+ const result = await (0, actions_1.registerActionSdk)(userData);
279
+ if (!result.ok) {
280
+ return { error: result.data?.message || result.data?.error || 'Registration failed' };
154
281
  }
155
- // Auto-login after registration
282
+ // Registration on the backend doesn't auto-issue a session — log in
283
+ // immediately after, same as the flow this replaces did.
156
284
  return await login({ email: userData.email, password: userData.password });
157
285
  }
158
- catch {
159
- return { error: 'Something went wrong' };
160
- }
161
286
  finally {
162
287
  setIsLoading(false);
163
288
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreiam/auth",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "main": "./dist/core/index.js",
5
5
  "types": "./dist/core/index.d.ts",
6
6
  "exports": {