@coreiam/auth 1.0.12 → 1.0.14
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/core/index.d.ts +16 -0
- package/dist/core/index.js +94 -21
- package/dist/nextjs/actions.d.ts +6 -0
- package/dist/nextjs/actions.js +202 -13
- package/dist/nextjs/provider.d.ts +10 -1
- package/dist/nextjs/provider.js +215 -90
- package/package.json +1 -1
package/dist/core/index.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/core/index.js
CHANGED
|
@@ -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(
|
|
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
|
-
},
|
|
156
|
+
}, incomingHeaders);
|
|
102
157
|
}
|
|
103
158
|
async registerWithCsrfProtection(userData, incomingHeaders) {
|
|
104
|
-
|
|
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
|
-
},
|
|
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;
|
package/dist/nextjs/actions.d.ts
CHANGED
|
@@ -8,3 +8,9 @@ export declare const 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>;
|
package/dist/nextjs/actions.js
CHANGED
|
@@ -1,14 +1,124 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// // src/nextjs/actions.ts
|
|
3
|
+
// // REMOVE 'use server' from here.
|
|
4
|
+
// import { cookies, headers } from 'next/headers';
|
|
5
|
+
// import { CoreIAM, CoreIAMIdentity } from '../core';
|
|
6
|
+
// export interface AuthResult {
|
|
7
|
+
// ok: boolean;
|
|
8
|
+
// status: number;
|
|
9
|
+
// data: any;
|
|
10
|
+
// }
|
|
11
|
+
// let _iam: CoreIAM | null = null;
|
|
12
|
+
// function getIam(): CoreIAM {
|
|
13
|
+
// if (!_iam) _iam = new CoreIAM();
|
|
14
|
+
// return _iam;
|
|
15
|
+
// }
|
|
16
|
+
// async function syncCookiesToNext(response: Response) {
|
|
17
|
+
// const cookieStore = await cookies();
|
|
18
|
+
// const setCookies = response.headers.getSetCookie();
|
|
19
|
+
// for (const cookieStr of setCookies) {
|
|
20
|
+
// const parts = cookieStr.split(';');
|
|
21
|
+
// const [nameValue, ...attributes] = parts;
|
|
22
|
+
// const [name, ...valueParts] = nameValue.split('=');
|
|
23
|
+
// const cookieOptions: any = {};
|
|
24
|
+
// let isDeleting = false;
|
|
25
|
+
// attributes.forEach(attr => {
|
|
26
|
+
// const [attrName, attrValue] = attr.trim().split('=');
|
|
27
|
+
// const lowerName = attrName?.toLowerCase();
|
|
28
|
+
// if (lowerName === 'httponly') cookieOptions.httpOnly = true;
|
|
29
|
+
// if (lowerName === 'secure') cookieOptions.secure = true;
|
|
30
|
+
// if (lowerName === 'path') cookieOptions.path = attrValue || '/';
|
|
31
|
+
// if (lowerName === 'max-age') {
|
|
32
|
+
// const maxAge = parseInt(attrValue, 10);
|
|
33
|
+
// if (maxAge <= 0) isDeleting = true;
|
|
34
|
+
// else cookieOptions.maxAge = maxAge;
|
|
35
|
+
// }
|
|
36
|
+
// if (lowerName === 'expires') {
|
|
37
|
+
// const expiryDate = new Date(attrValue);
|
|
38
|
+
// if (expiryDate.getTime() < Date.now()) isDeleting = true;
|
|
39
|
+
// else cookieOptions.expires = expiryDate;
|
|
40
|
+
// }
|
|
41
|
+
// if (lowerName === 'samesite') cookieOptions.sameSite = attrValue?.toLowerCase() || 'lax';
|
|
42
|
+
// });
|
|
43
|
+
// const cookieName = name.trim();
|
|
44
|
+
// const cookieValue = valueParts.join('=').trim();
|
|
45
|
+
// if (isDeleting || cookieValue === '') {
|
|
46
|
+
// cookieStore.delete(cookieName);
|
|
47
|
+
// } else {
|
|
48
|
+
// cookieStore.set(cookieName, cookieValue, cookieOptions);
|
|
49
|
+
// }
|
|
50
|
+
// }
|
|
51
|
+
// }
|
|
52
|
+
// // This is called by Server Components (e.g., layout.tsx)
|
|
53
|
+
// export async function getSession(): Promise<CoreIAMIdentity | null> {
|
|
54
|
+
// const reqHeaders = await headers();
|
|
55
|
+
// const response = await getIam().proxy('/auth/profile', { method: 'GET' }, reqHeaders);
|
|
56
|
+
// if (!response.ok) return null;
|
|
57
|
+
// const coreIamIdentity = await response.json();
|
|
58
|
+
// if (!coreIamIdentity?.sub) return null;
|
|
59
|
+
// return coreIamIdentity as CoreIAMIdentity;
|
|
60
|
+
// }
|
|
61
|
+
// // These are no longer called by the SDK's provider.tsx,
|
|
62
|
+
// // but can still be used by Qalam's Server Actions if needed.
|
|
63
|
+
// export async function loginActionSdk(identifier: string, password: string): Promise<AuthResult> {
|
|
64
|
+
// const reqHeaders = await headers();
|
|
65
|
+
// try {
|
|
66
|
+
// const response = await getIam().loginWithCsrfProtection(identifier, password, reqHeaders);
|
|
67
|
+
// await syncCookiesToNext(response);
|
|
68
|
+
// return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
69
|
+
// } catch (error) {
|
|
70
|
+
// return { ok: false, status: 500, data: { error: error instanceof Error ? error.message : 'Login failed' } };
|
|
71
|
+
// }
|
|
72
|
+
// }
|
|
73
|
+
// export async function registerActionSdk(userData: any): Promise<AuthResult> {
|
|
74
|
+
// const reqHeaders = await headers();
|
|
75
|
+
// try {
|
|
76
|
+
// const response = await getIam().registerWithCsrfProtection(userData, reqHeaders);
|
|
77
|
+
// await syncCookiesToNext(response);
|
|
78
|
+
// return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
79
|
+
// } catch (error) {
|
|
80
|
+
// return { ok: false, status: 500, data: { error: error instanceof Error ? error.message : 'Registration failed' } };
|
|
81
|
+
// }
|
|
82
|
+
// }
|
|
83
|
+
// export async function logoutActionSdk(): Promise<AuthResult> {
|
|
84
|
+
// const reqHeaders = await headers();
|
|
85
|
+
// try {
|
|
86
|
+
// const response = await getIam().logout(reqHeaders);
|
|
87
|
+
// await syncCookiesToNext(response);
|
|
88
|
+
// return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
89
|
+
// } catch (error) {
|
|
90
|
+
// return { ok: false, status: 500, data: { error: 'Logout failed' } };
|
|
91
|
+
// }
|
|
92
|
+
// }
|
|
93
|
+
'use server';
|
|
94
|
+
// // src/nextjs/actions.ts
|
|
95
|
+
// // REMOVE 'use server' from here.
|
|
2
96
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
97
|
exports.getSession = void 0;
|
|
4
98
|
exports.loginActionSdk = loginActionSdk;
|
|
5
99
|
exports.registerActionSdk = registerActionSdk;
|
|
6
100
|
exports.logoutActionSdk = logoutActionSdk;
|
|
7
|
-
|
|
8
|
-
|
|
101
|
+
exports.updateProfileActionSdk = updateProfileActionSdk;
|
|
102
|
+
exports.setupMfaActionSdk = setupMfaActionSdk;
|
|
103
|
+
exports.verifyMfaActionSdk = verifyMfaActionSdk;
|
|
104
|
+
exports.disableMfaActionSdk = disableMfaActionSdk;
|
|
105
|
+
exports.requestOtpActionSdk = requestOtpActionSdk;
|
|
106
|
+
exports.verifyOtpActionSdk = verifyOtpActionSdk;
|
|
9
107
|
const react_1 = require("react");
|
|
10
108
|
const headers_1 = require("next/headers");
|
|
11
109
|
const core_1 = require("../core");
|
|
110
|
+
// FIX: lazy singleton instead of `const iam = new CoreIAM();` at module
|
|
111
|
+
// scope. The constructor throws if COREIAM_API_KEY/COREIAM_API_URL are
|
|
112
|
+
// missing — fine on the server, where those env vars exist — but a
|
|
113
|
+
// module-level throw happens the instant the module is evaluated, before
|
|
114
|
+
// any function in it is even called. If this module's 'use server'
|
|
115
|
+
// boundary is ever not recognized correctly by the bundler (see the note
|
|
116
|
+
// on the browser error below), the real module gets included in the client
|
|
117
|
+
// bundle instead of being swapped for action-reference stubs, and that
|
|
118
|
+
// throw fires in the browser instead, where those env vars are never
|
|
119
|
+
// defined. Making this lazy doesn't fix a lost 'use server' boundary by
|
|
120
|
+
// itself, but it does mean import alone can never crash — only an actual
|
|
121
|
+
// invocation can, which is strictly better either way.
|
|
12
122
|
let _iam = null;
|
|
13
123
|
function getIam() {
|
|
14
124
|
if (!_iam)
|
|
@@ -60,16 +170,32 @@ async function syncCookiesToNext(response) {
|
|
|
60
170
|
}
|
|
61
171
|
}
|
|
62
172
|
}
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
173
|
+
// FIX: wrapped in React's cache(). This is the generic fix for "why are we
|
|
174
|
+
// calling /profile so much" — any number of calls to getSession() within the
|
|
175
|
+
// same request/render (direct calls, or nested inside a consumer's own
|
|
176
|
+
// currentUser()-style wrapper) now collapse into a single network round trip
|
|
177
|
+
// to CoreIAM automatically, for every current and future call site.
|
|
178
|
+
//
|
|
179
|
+
// Deliberately NOT adding a refresh-retry fallback here (an earlier draft in
|
|
180
|
+
// this thread did). Next.js only allows cookies().set()/cookies().delete()
|
|
181
|
+
// inside a Server Action or Route Handler — never during a Server Component
|
|
182
|
+
// render — and getSession() is called from both (e.g. from a plain
|
|
183
|
+
// RootLayout render, and from consumers' currentUser()-style wrappers used
|
|
184
|
+
// throughout Server Components). A refresh path that can only sometimes
|
|
185
|
+
// persist its new cookies isn't safe: on the render-context call it would
|
|
186
|
+
// still redeem the refresh token server-side (rotating/burning it) but never
|
|
187
|
+
// deliver the new one to the browser, stranding the client on a now-dead
|
|
188
|
+
// refresh token. Refresh belongs solely in middleware.ts, which is the one
|
|
189
|
+
// place in the request lifecycle actually allowed to mutate response
|
|
190
|
+
// cookies. If middleware already refreshed upstream, this succeeds; if
|
|
191
|
+
// middleware's refresh also failed, returning null here is correct, not a
|
|
192
|
+
// bug to work around.
|
|
72
193
|
exports.getSession = (0, react_1.cache)(async () => {
|
|
194
|
+
// DEBUG_TRACE: temporary — remove once the duplicate-call question is
|
|
195
|
+
// settled. This fires once per real (uncached) call, server-side, so it
|
|
196
|
+
// shows up in the Next.js terminal, not the browser console. Grep
|
|
197
|
+
// "DEBUG_TRACE" to find every line this touch added.
|
|
198
|
+
console.log(`[DEBUG_TRACE] getSession() real fetch @ ${new Date().toISOString()}`);
|
|
73
199
|
const reqHeaders = await (0, headers_1.headers)();
|
|
74
200
|
const response = await getIam().proxy('/auth/profile', { method: 'GET' }, reqHeaders);
|
|
75
201
|
if (!response.ok)
|
|
@@ -79,8 +205,6 @@ exports.getSession = (0, react_1.cache)(async () => {
|
|
|
79
205
|
return null;
|
|
80
206
|
return coreIamIdentity;
|
|
81
207
|
});
|
|
82
|
-
// These are no longer called by the SDK's provider.tsx,
|
|
83
|
-
// but can still be used by Qalam's Server Actions if needed.
|
|
84
208
|
async function loginActionSdk(identifier, password) {
|
|
85
209
|
const reqHeaders = await (0, headers_1.headers)();
|
|
86
210
|
try {
|
|
@@ -114,3 +238,68 @@ async function logoutActionSdk() {
|
|
|
114
238
|
return { ok: false, status: 500, data: { error: 'Logout failed' } };
|
|
115
239
|
}
|
|
116
240
|
}
|
|
241
|
+
// --- Newly added, prompted by an audit of CSRF coverage across every
|
|
242
|
+
// CsrfGuard-decorated CoreIAM endpoint (not just login/register). Each of
|
|
243
|
+
// these now handles its own CSRF handshake via core/index.ts's
|
|
244
|
+
// proxyWithCsrfProtection, the same way loginActionSdk/registerActionSdk
|
|
245
|
+
// already did — nothing calling these needs to fetch a CSRF token itself.
|
|
246
|
+
async function updateProfileActionSdk(updateData) {
|
|
247
|
+
const reqHeaders = await (0, headers_1.headers)();
|
|
248
|
+
try {
|
|
249
|
+
const response = await getIam().updateProfile(updateData, reqHeaders);
|
|
250
|
+
return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
return { ok: false, status: 500, data: { error: 'Profile update failed' } };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function setupMfaActionSdk() {
|
|
257
|
+
const reqHeaders = await (0, headers_1.headers)();
|
|
258
|
+
try {
|
|
259
|
+
const response = await getIam().setupMfa(reqHeaders);
|
|
260
|
+
return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
return { ok: false, status: 500, data: { error: 'MFA setup failed' } };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async function verifyMfaActionSdk(token) {
|
|
267
|
+
const reqHeaders = await (0, headers_1.headers)();
|
|
268
|
+
try {
|
|
269
|
+
const response = await getIam().verifyMfa(token, reqHeaders);
|
|
270
|
+
return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
return { ok: false, status: 500, data: { error: 'MFA verification failed' } };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async function disableMfaActionSdk() {
|
|
277
|
+
const reqHeaders = await (0, headers_1.headers)();
|
|
278
|
+
try {
|
|
279
|
+
const response = await getIam().disableMfa(reqHeaders);
|
|
280
|
+
return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
return { ok: false, status: 500, data: { error: 'Disabling MFA failed' } };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function requestOtpActionSdk(body) {
|
|
287
|
+
const reqHeaders = await (0, headers_1.headers)();
|
|
288
|
+
try {
|
|
289
|
+
const response = await getIam().requestOtp(body, reqHeaders);
|
|
290
|
+
return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
return { ok: false, status: 500, data: { error: 'OTP request failed' } };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function verifyOtpActionSdk(body) {
|
|
297
|
+
const reqHeaders = await (0, headers_1.headers)();
|
|
298
|
+
try {
|
|
299
|
+
const response = await getIam().verifyOtp(body, reqHeaders);
|
|
300
|
+
return { ok: response.ok, status: response.status, data: await response.json().catch(() => ({})) };
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
return { ok: false, status: 500, data: { error: 'OTP verification failed' } };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -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;
|
package/dist/nextjs/provider.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
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
|
|
139
|
-
if (!
|
|
140
|
-
return { error:
|
|
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
|
-
//
|
|
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
|
}
|