@coreiam/auth 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.js +10 -4
- package/dist/nextjs/actions.d.ts +1 -1
- package/dist/nextjs/actions.js +24 -4
- package/dist/nextjs/index.d.ts +1 -1
- package/dist/nextjs/index.js +4 -1
- package/dist/nextjs/middleware.d.ts +8 -1
- package/dist/nextjs/middleware.js +108 -81
- package/dist/nextjs/provider.d.ts +31 -2
- package/dist/nextjs/provider.js +79 -10
- package/package.json +1 -1
package/dist/core/index.js
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
// src/core/index.ts
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.CoreIAM = void 0;
|
|
5
|
-
const DEFAULT_GATEWAY_URL = "https://api.e-qalam.com";
|
|
6
5
|
class CoreIAM {
|
|
7
6
|
apiKey = '';
|
|
8
7
|
baseUrl = '';
|
|
9
8
|
constructor(config) {
|
|
10
9
|
this.apiKey = config?.apiKey || process.env.COREIAM_API_KEY || '';
|
|
11
|
-
|
|
10
|
+
// FIX: Removed hardcoded URL. Throw if missing.
|
|
11
|
+
this.baseUrl = config?.baseUrl || process.env.COREIAM_API_URL || '';
|
|
12
12
|
if (!this.apiKey)
|
|
13
|
-
throw new Error('CoreIAM: Missing API Key');
|
|
13
|
+
throw new Error('CoreIAM: Missing API Key (COREIAM_API_KEY)');
|
|
14
|
+
if (!this.baseUrl)
|
|
15
|
+
throw new Error('CoreIAM: Missing Gateway URL (COREIAM_API_URL)');
|
|
14
16
|
}
|
|
15
17
|
// Exposed publicly so the Next.js layer can use it for getSession()
|
|
16
18
|
async refreshTokens(headers) {
|
|
@@ -49,7 +51,11 @@ class CoreIAM {
|
|
|
49
51
|
const cookie = incomingHeaders.get('cookie');
|
|
50
52
|
if (cookie) {
|
|
51
53
|
headers.set('cookie', cookie);
|
|
52
|
-
|
|
54
|
+
// FIX: anchored to a real cookie-name boundary (start of string, or right
|
|
55
|
+
// after "; "), so this can't match "jwt=" as a substring of some other
|
|
56
|
+
// cookie's name or value. Not currently exploitable (no other cookie in
|
|
57
|
+
// this system contains "jwt="), but cheap to close off.
|
|
58
|
+
const jwtMatch = cookie.match(/(?:^|;\s*)jwt=([^;]+)/);
|
|
53
59
|
if (jwtMatch) {
|
|
54
60
|
headers.set('Authorization', `Bearer ${jwtMatch[1]}`);
|
|
55
61
|
}
|
package/dist/nextjs/actions.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface AuthResult {
|
|
|
4
4
|
status: number;
|
|
5
5
|
data: any;
|
|
6
6
|
}
|
|
7
|
-
export declare
|
|
7
|
+
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>;
|
package/dist/nextjs/actions.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// src/nextjs/actions.ts
|
|
3
2
|
'use server';
|
|
4
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.getSession =
|
|
4
|
+
exports.getSession = void 0;
|
|
6
5
|
exports.loginActionSdk = loginActionSdk;
|
|
7
6
|
exports.registerActionSdk = registerActionSdk;
|
|
8
7
|
exports.logoutActionSdk = logoutActionSdk;
|
|
8
|
+
const react_1 = require("react");
|
|
9
9
|
const headers_1 = require("next/headers");
|
|
10
10
|
const core_1 = require("../core");
|
|
11
11
|
const iam = new core_1.CoreIAM();
|
|
@@ -54,7 +54,27 @@ async function syncCookiesToNext(response) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
// FIX: wrapped in React's cache(). This is the generic fix for "why are we
|
|
58
|
+
// calling /profile so much" — any number of calls to getSession() within the
|
|
59
|
+
// same request/render (direct calls, or nested inside a consumer's own
|
|
60
|
+
// currentUser()-style wrapper) now collapse into a single network round trip
|
|
61
|
+
// to CoreIAM automatically, for every current and future call site.
|
|
62
|
+
//
|
|
63
|
+
// Deliberately NOT adding a refresh-retry fallback here (an earlier draft in
|
|
64
|
+
// this thread did). Next.js only allows cookies().set()/cookies().delete()
|
|
65
|
+
// inside a Server Action or Route Handler — never during a Server Component
|
|
66
|
+
// render — and getSession() is called from both (e.g. from a plain
|
|
67
|
+
// RootLayout render, and from consumers' currentUser()-style wrappers used
|
|
68
|
+
// throughout Server Components). A refresh path that can only sometimes
|
|
69
|
+
// persist its new cookies isn't safe: on the render-context call it would
|
|
70
|
+
// still redeem the refresh token server-side (rotating/burning it) but never
|
|
71
|
+
// deliver the new one to the browser, stranding the client on a now-dead
|
|
72
|
+
// refresh token. Refresh belongs solely in middleware.ts, which is the one
|
|
73
|
+
// place in the request lifecycle actually allowed to mutate response
|
|
74
|
+
// cookies. If middleware already refreshed upstream, this succeeds; if
|
|
75
|
+
// middleware's refresh also failed, returning null here is correct, not a
|
|
76
|
+
// bug to work around.
|
|
77
|
+
exports.getSession = (0, react_1.cache)(async () => {
|
|
58
78
|
const reqHeaders = await (0, headers_1.headers)();
|
|
59
79
|
const response = await iam.proxy('/auth/profile', { method: 'GET' }, reqHeaders);
|
|
60
80
|
if (!response.ok)
|
|
@@ -63,7 +83,7 @@ async function getSession() {
|
|
|
63
83
|
if (!coreIamIdentity?.sub)
|
|
64
84
|
return null;
|
|
65
85
|
return coreIamIdentity;
|
|
66
|
-
}
|
|
86
|
+
});
|
|
67
87
|
async function loginActionSdk(identifier, password) {
|
|
68
88
|
const reqHeaders = await (0, headers_1.headers)();
|
|
69
89
|
try {
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
5
5
|
import { CoreIAM } from '../core';
|
|
6
6
|
export declare class NextHandlers {
|
|
7
7
|
private iam;
|
|
8
|
-
constructor(iam
|
|
8
|
+
constructor(iam?: CoreIAM);
|
|
9
9
|
handleProxy(req: NextRequest, internalPath: string): Promise<NextResponse<any>>;
|
|
10
10
|
}
|
package/dist/nextjs/index.js
CHANGED
|
@@ -10,13 +10,16 @@ Object.defineProperty(exports, "loginActionSdk", { enumerable: true, get: functi
|
|
|
10
10
|
Object.defineProperty(exports, "registerActionSdk", { enumerable: true, get: function () { return actions_1.registerActionSdk; } });
|
|
11
11
|
Object.defineProperty(exports, "logoutActionSdk", { enumerable: true, get: function () { return actions_1.logoutActionSdk; } });
|
|
12
12
|
Object.defineProperty(exports, "getSession", { enumerable: true, get: function () { return actions_1.getSession; } });
|
|
13
|
+
// FIX: Export the new handleCoreIamAuth function instead of the old middleware
|
|
13
14
|
var middleware_1 = require("./middleware");
|
|
14
15
|
Object.defineProperty(exports, "coreIamMiddleware", { enumerable: true, get: function () { return middleware_1.coreIamMiddleware; } });
|
|
15
16
|
const server_1 = require("next/server");
|
|
17
|
+
const core_1 = require("../core");
|
|
16
18
|
class NextHandlers {
|
|
17
19
|
iam;
|
|
18
20
|
constructor(iam) {
|
|
19
|
-
|
|
21
|
+
// If no iam client is provided, instantiate one using the env variables
|
|
22
|
+
this.iam = iam || new core_1.CoreIAM({ apiKey: process.env.COREIAM_API_KEY, baseUrl: process.env.COREIAM_API_URL });
|
|
20
23
|
}
|
|
21
24
|
async handleProxy(req, internalPath) {
|
|
22
25
|
const method = internalPath === '/csrf' ? 'POST' : req.method;
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Handles CoreIAM token refresh and header rewriting.
|
|
4
|
+
* Returns the NextResponse to return, and whether the user is authenticated.
|
|
5
|
+
*/
|
|
6
|
+
export declare function coreIamMiddleware(request: NextRequest): Promise<{
|
|
7
|
+
response: NextResponse;
|
|
8
|
+
isAuthenticated: boolean;
|
|
9
|
+
}>;
|
|
@@ -4,91 +4,118 @@ exports.coreIamMiddleware = coreIamMiddleware;
|
|
|
4
4
|
// src/nextjs/middleware.ts
|
|
5
5
|
const server_1 = require("next/server");
|
|
6
6
|
const jwt_decode_1 = require("jwt-decode");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
// Module-level: persists across requests as long as this runs in a single,
|
|
8
|
+
// long-running Node process (true for a VPS deploy). This lets concurrent
|
|
9
|
+
// middleware invocations on THIS process share one in-flight refresh
|
|
10
|
+
// instead of each independently redeeming the same (single-use) refresh
|
|
11
|
+
// token and knocking each other out with 401 Invalid refresh token.
|
|
12
|
+
//
|
|
13
|
+
// This lock is per-process by nature — no amount of cleverness here makes
|
|
14
|
+
// it coordinate across multiple Node processes/instances, since it's just
|
|
15
|
+
// in-memory state. That's fine: it's an optimization (avoids the latency
|
|
16
|
+
// and CoreIAM load of firing off N calls that are mostly doomed to fail
|
|
17
|
+
// anyway), not the thing that makes rotation actually correct under
|
|
18
|
+
// concurrency. That guarantee now lives where all callers ultimately
|
|
19
|
+
// converge regardless of how many Qalam or CoreIAM processes exist: a
|
|
20
|
+
// conditional (WHERE isRevoked: false) update inside a transaction in
|
|
21
|
+
// auth.service.ts's refreshToken(), enforced by Postgres itself. If this
|
|
22
|
+
// lock is ever bypassed or scaled beyond one process, refreshes just get a
|
|
23
|
+
// little less efficient (a few requests eagerly hit CoreIAM instead of
|
|
24
|
+
// waiting on a local promise) — they don't get less correct.
|
|
25
|
+
let refreshInFlight = null;
|
|
26
|
+
async function performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl) {
|
|
27
|
+
try {
|
|
28
|
+
const refreshRes = await fetch(`${gatewayUrl}/auth/refresh-token`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'X-API-Key': process.env.COREIAM_API_KEY,
|
|
32
|
+
'cookie': `refreshToken=${refreshTokenCookie?.value || ''}; jwt=${jwtCookie?.value || ''}`,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (!refreshRes.ok)
|
|
36
|
+
return null;
|
|
37
|
+
const setCookies = refreshRes.headers.getSetCookie();
|
|
38
|
+
let newJwt = '';
|
|
39
|
+
let newRefreshToken = '';
|
|
40
|
+
setCookies.forEach(cookie => {
|
|
41
|
+
if (cookie.startsWith('jwt='))
|
|
42
|
+
newJwt = cookie.split(';')[0].split('=')[1];
|
|
43
|
+
if (cookie.startsWith('refreshToken='))
|
|
44
|
+
newRefreshToken = cookie.split(';')[0].split('=')[1];
|
|
45
|
+
});
|
|
46
|
+
return newJwt ? { jwt: newJwt, refreshToken: newRefreshToken } : null;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Handles CoreIAM token refresh and header rewriting.
|
|
54
|
+
* Returns the NextResponse to return, and whether the user is authenticated.
|
|
55
|
+
*/
|
|
56
|
+
async function coreIamMiddleware(request) {
|
|
57
|
+
const jwtCookie = request.cookies.get("jwt");
|
|
58
|
+
const refreshTokenCookie = request.cookies.get("refreshToken");
|
|
59
|
+
const gatewayUrl = process.env.COREIAM_API_URL;
|
|
60
|
+
let isAuthenticated = false;
|
|
61
|
+
let needsRefresh = false;
|
|
62
|
+
if (jwtCookie) {
|
|
63
|
+
try {
|
|
64
|
+
const decoded = (0, jwt_decode_1.jwtDecode)(jwtCookie.value);
|
|
65
|
+
if (decoded.exp && decoded.exp * 1000 > Date.now()) {
|
|
66
|
+
isAuthenticated = true;
|
|
26
67
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
needsRefresh = true;
|
|
68
|
+
else if (refreshTokenCookie) {
|
|
69
|
+
needsRefresh = true;
|
|
30
70
|
}
|
|
31
71
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Allow API routes to pass through (we handle their 401s normally)
|
|
36
|
-
if (pathname.startsWith('/api')) {
|
|
37
|
-
return server_1.NextResponse.next();
|
|
38
|
-
}
|
|
39
|
-
// 1. ATTEMPT SILENT REFRESH
|
|
40
|
-
if (needsRefresh) {
|
|
41
|
-
try {
|
|
42
|
-
const refreshRes = await fetch(`${DEFAULT_GATEWAY_URL}/auth/refresh-token`, {
|
|
43
|
-
method: 'POST',
|
|
44
|
-
headers: {
|
|
45
|
-
'X-API-Key': process.env.COREIAM_API_KEY,
|
|
46
|
-
'cookie': `refreshToken=${refreshTokenCookie?.value || ''}; jwt=${jwtCookie?.value || ''}`,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
if (refreshRes.ok) {
|
|
50
|
-
const setCookies = refreshRes.headers.getSetCookie();
|
|
51
|
-
if (setCookies && setCookies.length > 0) {
|
|
52
|
-
let newJwt = '';
|
|
53
|
-
let newRefreshToken = '';
|
|
54
|
-
setCookies.forEach(cookie => {
|
|
55
|
-
if (cookie.startsWith('jwt='))
|
|
56
|
-
newJwt = cookie.split(';')[0].split('=')[1];
|
|
57
|
-
if (cookie.startsWith('refreshToken='))
|
|
58
|
-
newRefreshToken = cookie.split(';')[0].split('=')[1];
|
|
59
|
-
});
|
|
60
|
-
// CRITICAL: Rewrite request headers so Server Components see the new JWT!
|
|
61
|
-
const requestHeaders = new Headers(request.headers);
|
|
62
|
-
requestHeaders.set('cookie', `jwt=${newJwt}; refreshToken=${newRefreshToken}`);
|
|
63
|
-
requestHeaders.set('Authorization', `Bearer ${newJwt}`);
|
|
64
|
-
const response = server_1.NextResponse.next({
|
|
65
|
-
request: { headers: requestHeaders },
|
|
66
|
-
});
|
|
67
|
-
// Set the new cookies on the browser
|
|
68
|
-
setCookies.forEach(cookie => {
|
|
69
|
-
response.headers.append('set-cookie', cookie);
|
|
70
|
-
});
|
|
71
|
-
return response; // Exit early, token is fresh!
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
// Refresh failed, fall through to clear cookies
|
|
77
|
-
}
|
|
78
|
-
// 2. IF REFRESH FAILED, CLEAR COOKIES TO PREVENT INFINITE LOOP
|
|
79
|
-
const res = server_1.NextResponse.redirect(new URL('/auth/login', nextUrl));
|
|
80
|
-
res.cookies.delete('jwt');
|
|
81
|
-
res.cookies.delete('refreshToken');
|
|
82
|
-
return res;
|
|
72
|
+
catch {
|
|
73
|
+
if (refreshTokenCookie)
|
|
74
|
+
needsRefresh = true;
|
|
83
75
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
}
|
|
77
|
+
else if (refreshTokenCookie) {
|
|
78
|
+
needsRefresh = true;
|
|
79
|
+
}
|
|
80
|
+
// 1. ATTEMPT SILENT REFRESH (single-flight)
|
|
81
|
+
if (needsRefresh && gatewayUrl) {
|
|
82
|
+
if (!refreshInFlight) {
|
|
83
|
+
refreshInFlight = performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl).finally(() => {
|
|
84
|
+
refreshInFlight = null;
|
|
85
|
+
});
|
|
88
86
|
}
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
const result = await refreshInFlight;
|
|
88
|
+
if (result) {
|
|
89
|
+
const requestHeaders = new Headers(request.headers);
|
|
90
|
+
requestHeaders.set('cookie', `jwt=${result.jwt}; refreshToken=${result.refreshToken}`);
|
|
91
|
+
requestHeaders.set('Authorization', `Bearer ${result.jwt}`);
|
|
92
|
+
const response = server_1.NextResponse.next({
|
|
93
|
+
request: { headers: requestHeaders },
|
|
94
|
+
});
|
|
95
|
+
// FIX: flags now match cookie.util.ts exactly (httpOnly, sameSite:
|
|
96
|
+
// 'strict', secure in production only, correct maxAge per cookie),
|
|
97
|
+
// instead of the placeholder flags used before that file was reviewed.
|
|
98
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
99
|
+
response.cookies.set('jwt', result.jwt, {
|
|
100
|
+
httpOnly: true,
|
|
101
|
+
secure: isProd,
|
|
102
|
+
sameSite: 'strict',
|
|
103
|
+
maxAge: 24 * 60 * 60, // seconds — matches the 24h maxAge in cookie.util.ts
|
|
104
|
+
});
|
|
105
|
+
response.cookies.set('refreshToken', result.refreshToken, {
|
|
106
|
+
httpOnly: true,
|
|
107
|
+
secure: isProd,
|
|
108
|
+
sameSite: 'strict',
|
|
109
|
+
maxAge: 7 * 24 * 60 * 60, // seconds — matches the 7d maxAge in cookie.util.ts
|
|
110
|
+
});
|
|
111
|
+
return { response, isAuthenticated: true };
|
|
91
112
|
}
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
// 2. IF REFRESH FAILED, CLEAR COOKIES
|
|
114
|
+
const response = server_1.NextResponse.next();
|
|
115
|
+
response.cookies.delete('jwt');
|
|
116
|
+
response.cookies.delete('refreshToken');
|
|
117
|
+
return { response, isAuthenticated: false };
|
|
118
|
+
}
|
|
119
|
+
// 3. NO REFRESH NEEDED (Token is valid or completely missing)
|
|
120
|
+
return { response: server_1.NextResponse.next(), isAuthenticated };
|
|
94
121
|
}
|
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { CoreIAMIdentity } from '../core';
|
|
3
|
+
export interface CoreIAMRedirects {
|
|
4
|
+
/** Where to send the user after a successful login, if no callbackUrl is passed to login(). */
|
|
5
|
+
afterLogin?: string;
|
|
6
|
+
/** Where to send the user after logout. */
|
|
7
|
+
afterLogout?: string;
|
|
8
|
+
}
|
|
9
|
+
interface LoginResult {
|
|
10
|
+
success?: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
2
13
|
interface AuthContextType {
|
|
3
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Generic CoreIAM identity (sub/email/roles/tenantId) — NOT an app-shaped
|
|
16
|
+
* user. This SDK has no knowledge of any consumer's local user model
|
|
17
|
+
* (roles, profile fields, etc). Consumers that need an enriched user
|
|
18
|
+
* object should build their own layer on top of this, keyed off `sub`.
|
|
19
|
+
*/
|
|
20
|
+
identity: CoreIAMIdentity | null;
|
|
4
21
|
isLoading: boolean;
|
|
22
|
+
isAuthenticated: boolean;
|
|
23
|
+
login: (values: {
|
|
24
|
+
email: string;
|
|
25
|
+
password: string;
|
|
26
|
+
}, callbackUrl?: string) => Promise<LoginResult>;
|
|
27
|
+
logout: () => Promise<void>;
|
|
28
|
+
register: (userData: Record<string, any>) => Promise<LoginResult>;
|
|
29
|
+
refetchIdentity: () => Promise<void>;
|
|
5
30
|
}
|
|
6
|
-
export declare function CoreIAMProvider({ children }: {
|
|
31
|
+
export declare function CoreIAMProvider({ children, initialIdentity, redirects, }: {
|
|
7
32
|
children: React.ReactNode;
|
|
33
|
+
/** Pass the result of a server-side getSession() call to skip the client fetch on first paint. */
|
|
34
|
+
initialIdentity?: CoreIAMIdentity | null;
|
|
35
|
+
/** Where to redirect after login/logout. Omit for no redirect (caller handles navigation itself). */
|
|
36
|
+
redirects?: CoreIAMRedirects;
|
|
8
37
|
}): import("react/jsx-runtime").JSX.Element;
|
|
9
38
|
export declare function useAuth(): AuthContextType;
|
|
10
39
|
export {};
|
package/dist/nextjs/provider.js
CHANGED
|
@@ -1,25 +1,94 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// src/nextjs/provider.tsx
|
|
3
2
|
'use client';
|
|
4
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
4
|
exports.CoreIAMProvider = CoreIAMProvider;
|
|
6
5
|
exports.useAuth = useAuth;
|
|
7
6
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
7
|
const react_1 = require("react");
|
|
8
|
+
const navigation_1 = require("next/navigation");
|
|
9
|
+
const actions_1 = require("./actions");
|
|
9
10
|
const AuthContext = (0, react_1.createContext)({
|
|
10
|
-
|
|
11
|
+
identity: null,
|
|
11
12
|
isLoading: true,
|
|
13
|
+
isAuthenticated: false,
|
|
14
|
+
login: async () => ({ error: 'CoreIAMProvider is not mounted' }),
|
|
15
|
+
logout: async () => { },
|
|
16
|
+
register: async () => ({ error: 'CoreIAMProvider is not mounted' }),
|
|
17
|
+
refetchIdentity: async () => { },
|
|
12
18
|
});
|
|
13
|
-
function CoreIAMProvider({ children }) {
|
|
14
|
-
const [
|
|
15
|
-
const [isLoading, setIsLoading] = (0, react_1.useState)(
|
|
19
|
+
function CoreIAMProvider({ children, initialIdentity = null, redirects = {}, }) {
|
|
20
|
+
const [identity, setIdentity] = (0, react_1.useState)(initialIdentity);
|
|
21
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(initialIdentity === null);
|
|
22
|
+
const router = (0, navigation_1.useRouter)();
|
|
23
|
+
const refetchIdentity = async () => {
|
|
24
|
+
try {
|
|
25
|
+
const result = await (0, actions_1.getSession)();
|
|
26
|
+
setIdentity(result);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
setIdentity(null);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
setIsLoading(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
16
35
|
(0, react_1.useEffect)(() => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
if (initialIdentity === null) {
|
|
37
|
+
refetchIdentity();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
setIsLoading(false);
|
|
41
|
+
}
|
|
42
|
+
// Intentionally only on mount — initialIdentity is a one-time SSR seed,
|
|
43
|
+
// not something we want to re-run this effect for if it changes identity.
|
|
44
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
21
45
|
}, []);
|
|
22
|
-
|
|
46
|
+
const login = async (values, callbackUrl) => {
|
|
47
|
+
setIsLoading(true);
|
|
48
|
+
try {
|
|
49
|
+
const result = await (0, actions_1.loginActionSdk)(values.email, values.password);
|
|
50
|
+
if (!result.ok) {
|
|
51
|
+
return { error: result.data?.message || result.data?.error || 'Invalid credentials' };
|
|
52
|
+
}
|
|
53
|
+
await refetchIdentity();
|
|
54
|
+
const target = callbackUrl || redirects.afterLogin;
|
|
55
|
+
if (target)
|
|
56
|
+
router.push(target);
|
|
57
|
+
return { success: true };
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { error: 'Something went wrong' };
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
setIsLoading(false);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const logout = async () => {
|
|
67
|
+
try {
|
|
68
|
+
await (0, actions_1.logoutActionSdk)();
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
setIdentity(null);
|
|
72
|
+
if (redirects.afterLogout)
|
|
73
|
+
router.push(redirects.afterLogout);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const register = async (userData) => {
|
|
77
|
+
setIsLoading(true);
|
|
78
|
+
try {
|
|
79
|
+
const result = await (0, actions_1.registerActionSdk)(userData);
|
|
80
|
+
if (!result.ok) {
|
|
81
|
+
return { error: result.data?.message || result.data?.error || 'Registration failed' };
|
|
82
|
+
}
|
|
83
|
+
// Registration on the backend doesn't auto-issue a session — log in
|
|
84
|
+
// immediately after, same as the flow this replaces did.
|
|
85
|
+
return await login({ email: userData.email, password: userData.password });
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
setIsLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return ((0, jsx_runtime_1.jsx)(AuthContext.Provider, { value: { identity, isLoading, isAuthenticated: !!identity, login, logout, register, refetchIdentity }, children: children }));
|
|
23
92
|
}
|
|
24
93
|
function useAuth() {
|
|
25
94
|
return (0, react_1.useContext)(AuthContext);
|