@coreiam/auth 1.0.14 → 1.0.39
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/nextjs/actions.d.ts +1 -1
- package/dist/nextjs/actions.js +52 -14
- package/dist/nextjs/index.d.ts +11 -4
- package/dist/nextjs/index.js +96 -6
- package/dist/nextjs/middleware.d.ts +10 -3
- package/dist/nextjs/middleware.js +324 -39
- package/dist/nextjs/provider.d.ts +13 -22
- package/dist/nextjs/provider.js +103 -183
- package/dist/ui/buttons.d.ts +45 -0
- package/dist/ui/buttons.js +76 -0
- package/package.json +4 -2
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 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>;
|
package/dist/nextjs/actions.js
CHANGED
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
// // src/nextjs/actions.ts
|
|
95
95
|
// // REMOVE 'use server' from here.
|
|
96
96
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
97
|
-
exports.getSession =
|
|
97
|
+
exports.getSession = getSession;
|
|
98
98
|
exports.loginActionSdk = loginActionSdk;
|
|
99
99
|
exports.registerActionSdk = registerActionSdk;
|
|
100
100
|
exports.logoutActionSdk = logoutActionSdk;
|
|
@@ -104,7 +104,6 @@ exports.verifyMfaActionSdk = verifyMfaActionSdk;
|
|
|
104
104
|
exports.disableMfaActionSdk = disableMfaActionSdk;
|
|
105
105
|
exports.requestOtpActionSdk = requestOtpActionSdk;
|
|
106
106
|
exports.verifyOtpActionSdk = verifyOtpActionSdk;
|
|
107
|
-
const react_1 = require("react");
|
|
108
107
|
const headers_1 = require("next/headers");
|
|
109
108
|
const core_1 = require("../core");
|
|
110
109
|
// FIX: lazy singleton instead of `const iam = new CoreIAM();` at module
|
|
@@ -190,21 +189,60 @@ async function syncCookiesToNext(response) {
|
|
|
190
189
|
// cookies. If middleware already refreshed upstream, this succeeds; if
|
|
191
190
|
// middleware's refresh also failed, returning null here is correct, not a
|
|
192
191
|
// bug to work around.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
192
|
+
// export async function getSession(): Promise<CoreIAMIdentity | null> {
|
|
193
|
+
// console.log(`[DEBUG_TRACE] getSession() real fetch @ ${new Date().toISOString()}`);
|
|
194
|
+
// const reqHeaders = await headers();
|
|
195
|
+
// const response = await getIam().proxy('/auth/profile', { method: 'GET' }, reqHeaders);
|
|
196
|
+
// if (!response.ok) return null;
|
|
197
|
+
// const coreIamIdentity = await response.json();
|
|
198
|
+
// if (!coreIamIdentity?.sub) return null;
|
|
199
|
+
// return coreIamIdentity as CoreIAMIdentity;
|
|
200
|
+
// }
|
|
201
|
+
async function getSession() {
|
|
199
202
|
const reqHeaders = await (0, headers_1.headers)();
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
// The middleware verified the JWT and attached the payload to the request headers
|
|
204
|
+
const identityHeader = reqHeaders.get('x-coreiam-identity');
|
|
205
|
+
if (identityHeader) {
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(identityHeader);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Fallback: If middleware didn't run (e.g. API route), we can still read the JWT
|
|
214
|
+
// But we DO NOT verify it here, because verification requires the JWKS fetch which
|
|
215
|
+
// is too slow for Server Components. We just decode it to get the identity.
|
|
216
|
+
// The middleware is the true security gate.
|
|
217
|
+
const authHeader = reqHeaders.get('Authorization') || reqHeaders.get('authorization');
|
|
218
|
+
const cookieHeader = reqHeaders.get('cookie');
|
|
219
|
+
let token = undefined;
|
|
220
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
221
|
+
token = authHeader.substring(7);
|
|
222
|
+
}
|
|
223
|
+
else if (cookieHeader) {
|
|
224
|
+
const match = cookieHeader.match(/jwt=([^;]+)/);
|
|
225
|
+
if (match)
|
|
226
|
+
token = match[1];
|
|
227
|
+
}
|
|
228
|
+
if (!token)
|
|
202
229
|
return null;
|
|
203
|
-
|
|
204
|
-
|
|
230
|
+
try {
|
|
231
|
+
// Just decode the payload without verifying (middleware already verified it)
|
|
232
|
+
const payloadB64 = token.split('.')[1];
|
|
233
|
+
const payloadStr = Buffer.from(payloadB64, 'base64').toString('utf8');
|
|
234
|
+
const payload = JSON.parse(payloadStr);
|
|
235
|
+
return {
|
|
236
|
+
sub: payload.sub,
|
|
237
|
+
email: payload.email,
|
|
238
|
+
tenantId: payload.tenantId,
|
|
239
|
+
roles: payload.roles || []
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
205
243
|
return null;
|
|
206
|
-
|
|
207
|
-
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
208
246
|
async function loginActionSdk(identifier, password) {
|
|
209
247
|
const reqHeaders = await (0, headers_1.headers)();
|
|
210
248
|
try {
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
export { CoreIAMProvider, useAuth } from './provider';
|
|
2
|
-
export { loginActionSdk, registerActionSdk, logoutActionSdk, getSession } from './actions';
|
|
3
|
-
export { coreIamMiddleware } from './middleware';
|
|
4
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
5
|
-
import { CoreIAM } from '../core';
|
|
2
|
+
import { CoreIAM, CoreIAMIdentity } from '../core';
|
|
3
|
+
export { CoreIAMProvider, useAuth } from './provider';
|
|
4
|
+
export { coreIamMiddleware, coreIamAuth } from './middleware';
|
|
5
|
+
export { getSession, loginActionSdk, registerActionSdk, logoutActionSdk, updateProfileActionSdk, setupMfaActionSdk, verifyMfaActionSdk, disableMfaActionSdk, requestOtpActionSdk, verifyOtpActionSdk } from './actions';
|
|
6
6
|
export declare class NextHandlers {
|
|
7
7
|
private iam;
|
|
8
8
|
constructor(iam?: CoreIAM);
|
|
9
9
|
handleProxy(req: NextRequest, internalPath: string): Promise<NextResponse<any>>;
|
|
10
10
|
}
|
|
11
|
+
export declare class OAuthHandlers {
|
|
12
|
+
private iam;
|
|
13
|
+
constructor(iam?: CoreIAM);
|
|
14
|
+
handleInitiate(req: NextRequest, provider: string): Promise<NextResponse<unknown>>;
|
|
15
|
+
handleCallback(req: NextRequest, provider: string): Promise<NextResponse<unknown>>;
|
|
16
|
+
}
|
|
17
|
+
export declare function auth(request: NextRequest): Promise<CoreIAMIdentity | null>;
|
package/dist/nextjs/index.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NextHandlers = exports.
|
|
3
|
+
exports.OAuthHandlers = exports.NextHandlers = exports.verifyOtpActionSdk = exports.requestOtpActionSdk = exports.disableMfaActionSdk = exports.verifyMfaActionSdk = exports.setupMfaActionSdk = exports.updateProfileActionSdk = exports.logoutActionSdk = exports.registerActionSdk = exports.loginActionSdk = exports.getSession = exports.coreIamAuth = exports.coreIamMiddleware = exports.useAuth = exports.CoreIAMProvider = void 0;
|
|
4
|
+
exports.auth = auth;
|
|
4
5
|
// src/nextjs/index.ts
|
|
6
|
+
const server_1 = require("next/server");
|
|
7
|
+
const core_1 = require("../core");
|
|
5
8
|
var provider_1 = require("./provider");
|
|
6
9
|
Object.defineProperty(exports, "CoreIAMProvider", { enumerable: true, get: function () { return provider_1.CoreIAMProvider; } });
|
|
7
10
|
Object.defineProperty(exports, "useAuth", { enumerable: true, get: function () { return provider_1.useAuth; } });
|
|
11
|
+
var middleware_1 = require("./middleware");
|
|
12
|
+
Object.defineProperty(exports, "coreIamMiddleware", { enumerable: true, get: function () { return middleware_1.coreIamMiddleware; } });
|
|
13
|
+
Object.defineProperty(exports, "coreIamAuth", { enumerable: true, get: function () { return middleware_1.coreIamAuth; } });
|
|
14
|
+
// FIX: Export ALL Server Action functions here
|
|
8
15
|
var actions_1 = require("./actions");
|
|
16
|
+
Object.defineProperty(exports, "getSession", { enumerable: true, get: function () { return actions_1.getSession; } });
|
|
9
17
|
Object.defineProperty(exports, "loginActionSdk", { enumerable: true, get: function () { return actions_1.loginActionSdk; } });
|
|
10
18
|
Object.defineProperty(exports, "registerActionSdk", { enumerable: true, get: function () { return actions_1.registerActionSdk; } });
|
|
11
19
|
Object.defineProperty(exports, "logoutActionSdk", { enumerable: true, get: function () { return actions_1.logoutActionSdk; } });
|
|
12
|
-
Object.defineProperty(exports, "
|
|
13
|
-
|
|
14
|
-
Object.defineProperty(exports, "
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
Object.defineProperty(exports, "updateProfileActionSdk", { enumerable: true, get: function () { return actions_1.updateProfileActionSdk; } });
|
|
21
|
+
Object.defineProperty(exports, "setupMfaActionSdk", { enumerable: true, get: function () { return actions_1.setupMfaActionSdk; } });
|
|
22
|
+
Object.defineProperty(exports, "verifyMfaActionSdk", { enumerable: true, get: function () { return actions_1.verifyMfaActionSdk; } });
|
|
23
|
+
Object.defineProperty(exports, "disableMfaActionSdk", { enumerable: true, get: function () { return actions_1.disableMfaActionSdk; } });
|
|
24
|
+
Object.defineProperty(exports, "requestOtpActionSdk", { enumerable: true, get: function () { return actions_1.requestOtpActionSdk; } });
|
|
25
|
+
Object.defineProperty(exports, "verifyOtpActionSdk", { enumerable: true, get: function () { return actions_1.verifyOtpActionSdk; } });
|
|
17
26
|
class NextHandlers {
|
|
18
27
|
iam;
|
|
19
28
|
constructor(iam) {
|
|
@@ -46,3 +55,84 @@ class NextHandlers {
|
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
exports.NextHandlers = NextHandlers;
|
|
58
|
+
class OAuthHandlers {
|
|
59
|
+
iam;
|
|
60
|
+
constructor(iam) {
|
|
61
|
+
this.iam = iam || new core_1.CoreIAM({ apiKey: process.env.COREIAM_API_KEY, baseUrl: process.env.COREIAM_API_URL });
|
|
62
|
+
}
|
|
63
|
+
// Handles the initial redirect to Google/GitHub
|
|
64
|
+
async handleInitiate(req, provider) {
|
|
65
|
+
const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/${provider}`;
|
|
66
|
+
const response = await this.iam.proxy(`/auth/oauth/initiate/${provider}?redirectUri=${encodeURIComponent(redirectUri)}&scopes=email,profile`, { method: 'GET' }, req.headers);
|
|
67
|
+
const data = await response.json();
|
|
68
|
+
if (data.url) {
|
|
69
|
+
return server_1.NextResponse.redirect(data.url);
|
|
70
|
+
}
|
|
71
|
+
return server_1.NextResponse.redirect(new URL('/auth/login', req.url));
|
|
72
|
+
}
|
|
73
|
+
// Handles the callback from Google/GitHub
|
|
74
|
+
async handleCallback(req, provider) {
|
|
75
|
+
const { searchParams } = new URL(req.url);
|
|
76
|
+
const code = searchParams.get('code');
|
|
77
|
+
const state = searchParams.get('state');
|
|
78
|
+
const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/${provider}`;
|
|
79
|
+
const targetUrl = `/auth/oauth/callback/${provider}?code=${code}&state=${state}&redirectUri=${redirectUri}`;
|
|
80
|
+
const response = await this.iam.proxy(targetUrl, { method: 'GET' }, req.headers);
|
|
81
|
+
const res = server_1.NextResponse.redirect(new URL('/', req.url));
|
|
82
|
+
const setCookies = response.headers.getSetCookie();
|
|
83
|
+
if (setCookies && setCookies.length > 0) {
|
|
84
|
+
setCookies.forEach(cookie => {
|
|
85
|
+
res.headers.append('set-cookie', cookie);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return res;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.OAuthHandlers = OAuthHandlers;
|
|
92
|
+
const MAX_CACHE_SIZE = 10000;
|
|
93
|
+
const apiKeyCache = new Map();
|
|
94
|
+
async function auth(request) {
|
|
95
|
+
const authHeader = request.headers.get('authorization');
|
|
96
|
+
if (!authHeader?.startsWith('Bearer '))
|
|
97
|
+
return null;
|
|
98
|
+
const token = authHeader.substring(7);
|
|
99
|
+
const gatewayUrl = process.env.COREIAM_API_URL;
|
|
100
|
+
if (!gatewayUrl)
|
|
101
|
+
return null;
|
|
102
|
+
// 1. Check Cache
|
|
103
|
+
const cached = apiKeyCache.get(token);
|
|
104
|
+
if (cached && cached.expires > Date.now()) {
|
|
105
|
+
return cached.valid ? cached.identity : null;
|
|
106
|
+
}
|
|
107
|
+
// FIX: Prevent memory leak by clearing cache if it gets too large
|
|
108
|
+
if (apiKeyCache.size >= MAX_CACHE_SIZE) {
|
|
109
|
+
apiKeyCache.clear(); // Simple crude eviction. In production SDK, use lru-cache.
|
|
110
|
+
}
|
|
111
|
+
// 2. Call CoreIAM to verify
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch(`${gatewayUrl}/auth/api-keys/verify`, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: {
|
|
116
|
+
'Content-Type': 'application/json',
|
|
117
|
+
'X-API-Key': process.env.COREIAM_API_KEY
|
|
118
|
+
},
|
|
119
|
+
body: JSON.stringify({ token }),
|
|
120
|
+
});
|
|
121
|
+
const data = await res.json();
|
|
122
|
+
if (data.valid) {
|
|
123
|
+
const identity = {
|
|
124
|
+
sub: data.sub,
|
|
125
|
+
tenantId: data.tenantId,
|
|
126
|
+
};
|
|
127
|
+
apiKeyCache.set(token, { valid: true, identity, expires: Date.now() + (5 * 60 * 1000) });
|
|
128
|
+
return identity;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
apiKeyCache.set(token, { valid: false, expires: Date.now() + (5 * 60 * 1000) });
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Returns the
|
|
3
|
+
* Helper function for custom middleware (proxy.ts).
|
|
4
|
+
* Returns the response and authentication state.
|
|
5
5
|
*/
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function coreIamAuth(request: NextRequest): Promise<{
|
|
7
7
|
response: NextResponse;
|
|
8
8
|
isAuthenticated: boolean;
|
|
9
9
|
}>;
|
|
10
|
+
/**
|
|
11
|
+
* 1-line middleware factory for developers who don't need custom routing.
|
|
12
|
+
*/
|
|
13
|
+
export declare function coreIamMiddleware(options?: {
|
|
14
|
+
publicRoutes?: string[];
|
|
15
|
+
signInUrl?: string;
|
|
16
|
+
}): (request: NextRequest) => Promise<NextResponse<unknown>>;
|
|
@@ -1,11 +1,139 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// // src/nextjs/middleware.ts
|
|
3
|
+
// import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
+
// import { jwtVerify, createRemoteJWKSet } from 'jose';
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.coreIamAuth = coreIamAuth;
|
|
3
7
|
exports.coreIamMiddleware = coreIamMiddleware;
|
|
8
|
+
// // FIX: Instantiate the JWKS client ONCE at the module level.
|
|
9
|
+
// // This allows jose's internal cache to persist across requests.
|
|
10
|
+
// const gatewayUrl = process.env.COREIAM_API_URL;
|
|
11
|
+
// const JWKS = gatewayUrl
|
|
12
|
+
// ? createRemoteJWKSet(new URL(`${gatewayUrl}/.well-known/jwks.json`))
|
|
13
|
+
// : null;
|
|
14
|
+
// let refreshInFlight: Promise<{ jwt: string; refreshToken: string } | null> | null = null;
|
|
15
|
+
// async function performRefresh(
|
|
16
|
+
// refreshTokenCookie: { value: string } | undefined,
|
|
17
|
+
// jwtCookie: { value: string } | undefined,
|
|
18
|
+
// gatewayUrl: string,
|
|
19
|
+
// ): Promise<{ jwt: string; refreshToken: string } | null> {
|
|
20
|
+
// try {
|
|
21
|
+
// const refreshRes = await fetch(`${gatewayUrl}/auth/refresh-token`, {
|
|
22
|
+
// method: 'POST',
|
|
23
|
+
// headers: {
|
|
24
|
+
// 'X-API-Key': process.env.COREIAM_API_KEY!,
|
|
25
|
+
// 'cookie': `refreshToken=${refreshTokenCookie?.value || ''}; jwt=${jwtCookie?.value || ''}`,
|
|
26
|
+
// },
|
|
27
|
+
// });
|
|
28
|
+
// if (!refreshRes.ok) return null;
|
|
29
|
+
// const setCookies = refreshRes.headers.getSetCookie();
|
|
30
|
+
// let newJwt = '';
|
|
31
|
+
// let newRefreshToken = '';
|
|
32
|
+
// setCookies.forEach(cookie => {
|
|
33
|
+
// if (cookie.startsWith('jwt=')) newJwt = cookie.split(';')[0].split('=')[1];
|
|
34
|
+
// if (cookie.startsWith('refreshToken=')) newRefreshToken = cookie.split(';')[0].split('=')[1];
|
|
35
|
+
// });
|
|
36
|
+
// return newJwt ? { jwt: newJwt, refreshToken: newRefreshToken } : null;
|
|
37
|
+
// } catch (error) {
|
|
38
|
+
// return null;
|
|
39
|
+
// }
|
|
40
|
+
// }
|
|
41
|
+
// export async function coreIamMiddleware(request: NextRequest): Promise<{
|
|
42
|
+
// response: NextResponse;
|
|
43
|
+
// isAuthenticated: boolean;
|
|
44
|
+
// }> {
|
|
45
|
+
// const jwtCookie = request.cookies.get("jwt");
|
|
46
|
+
// const refreshTokenCookie = request.cookies.get("refreshToken");
|
|
47
|
+
// let isAuthenticated = false;
|
|
48
|
+
// let needsRefresh = false;
|
|
49
|
+
// if (jwtCookie && JWKS) {
|
|
50
|
+
// try {
|
|
51
|
+
// // 1. SECURELY VERIFY JWT LOCALLY USING THE MODULE-LEVEL JWKS CLIENT
|
|
52
|
+
// const { payload } = await jwtVerify(jwtCookie.value, JWKS, {
|
|
53
|
+
// issuer: 'coreiam',
|
|
54
|
+
// algorithms: ['RS256']
|
|
55
|
+
// });
|
|
56
|
+
// // ATTACH IDENTITY TO REQUEST HEADERS FOR SERVER COMPONENTS
|
|
57
|
+
// const requestHeaders = new Headers(request.headers);
|
|
58
|
+
// requestHeaders.set('x-coreiam-identity', JSON.stringify({
|
|
59
|
+
// sub: payload.sub,
|
|
60
|
+
// email: payload.email,
|
|
61
|
+
// tenantId: payload.tenantId,
|
|
62
|
+
// roles: payload.roles || []
|
|
63
|
+
// }));
|
|
64
|
+
// const response = NextResponse.next({
|
|
65
|
+
// request: { headers: requestHeaders },
|
|
66
|
+
// });
|
|
67
|
+
// return { response, isAuthenticated: true };
|
|
68
|
+
// } catch (error: any) {
|
|
69
|
+
// // If token is expired, jose throws ERR_JWT_EXPIRED
|
|
70
|
+
// if (error.code === 'ERR_JWT_EXPIRED' && refreshTokenCookie) {
|
|
71
|
+
// needsRefresh = true;
|
|
72
|
+
// }
|
|
73
|
+
// }
|
|
74
|
+
// } else if (refreshTokenCookie) {
|
|
75
|
+
// needsRefresh = true;
|
|
76
|
+
// }
|
|
77
|
+
// if (needsRefresh && gatewayUrl) {
|
|
78
|
+
// if (!refreshInFlight) {
|
|
79
|
+
// refreshInFlight = performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl).finally(() => {
|
|
80
|
+
// refreshInFlight = null;
|
|
81
|
+
// });
|
|
82
|
+
// }
|
|
83
|
+
// const result = await refreshInFlight;
|
|
84
|
+
// if (result) {
|
|
85
|
+
// const requestHeaders = new Headers(request.headers);
|
|
86
|
+
// requestHeaders.set('cookie', `jwt=${result.jwt}; refreshToken=${result.refreshToken}`);
|
|
87
|
+
// requestHeaders.set('Authorization', `Bearer ${result.jwt}`);
|
|
88
|
+
// // ATTACH IDENTITY TO REQUEST HEADERS FOR SERVER COMPONENTS (using the new JWT)
|
|
89
|
+
// try {
|
|
90
|
+
// const payloadB64 = result.jwt.split('.')[1];
|
|
91
|
+
// const payloadStr = Buffer.from(payloadB64, 'base64').toString('utf8');
|
|
92
|
+
// const payload = JSON.parse(payloadStr);
|
|
93
|
+
// requestHeaders.set('x-coreiam-identity', JSON.stringify({
|
|
94
|
+
// sub: payload.sub,
|
|
95
|
+
// email: payload.email,
|
|
96
|
+
// tenantId: payload.tenantId,
|
|
97
|
+
// roles: payload.roles || []
|
|
98
|
+
// }));
|
|
99
|
+
// } catch {}
|
|
100
|
+
// const response = NextResponse.next({
|
|
101
|
+
// request: { headers: requestHeaders },
|
|
102
|
+
// });
|
|
103
|
+
// const isProd = process.env.NODE_ENV === 'production';
|
|
104
|
+
// response.cookies.set('jwt', result.jwt, {
|
|
105
|
+
// httpOnly: true,
|
|
106
|
+
// secure: isProd,
|
|
107
|
+
// sameSite: 'strict',
|
|
108
|
+
// maxAge: 24 * 60 * 60,
|
|
109
|
+
// });
|
|
110
|
+
// response.cookies.set('refreshToken', result.refreshToken, {
|
|
111
|
+
// httpOnly: true,
|
|
112
|
+
// secure: isProd,
|
|
113
|
+
// sameSite: 'strict',
|
|
114
|
+
// maxAge: 7 * 24 * 60 * 60,
|
|
115
|
+
// });
|
|
116
|
+
// return { response, isAuthenticated: true };
|
|
117
|
+
// }
|
|
118
|
+
// const response = NextResponse.next();
|
|
119
|
+
// response.cookies.delete('jwt');
|
|
120
|
+
// response.cookies.delete('refreshToken');
|
|
121
|
+
// return { response, isAuthenticated: false };
|
|
122
|
+
// }
|
|
123
|
+
// return { response: NextResponse.next(), isAuthenticated };
|
|
124
|
+
// }
|
|
4
125
|
// src/nextjs/middleware.ts
|
|
5
126
|
const server_1 = require("next/server");
|
|
6
|
-
const
|
|
127
|
+
const jose_1 = require("jose");
|
|
128
|
+
const core_1 = require("../core");
|
|
129
|
+
const gatewayUrl = process.env.COREIAM_API_URL;
|
|
130
|
+
const JWKS = gatewayUrl
|
|
131
|
+
? (0, jose_1.createRemoteJWKSet)(new URL(`${gatewayUrl}/.well-known/jwks.json`))
|
|
132
|
+
: null;
|
|
7
133
|
let refreshInFlight = null;
|
|
8
|
-
async function performRefresh(refreshTokenCookie, jwtCookie
|
|
134
|
+
async function performRefresh(refreshTokenCookie, jwtCookie) {
|
|
135
|
+
if (!gatewayUrl)
|
|
136
|
+
return null;
|
|
9
137
|
try {
|
|
10
138
|
const refreshRes = await fetch(`${gatewayUrl}/auth/refresh-token`, {
|
|
11
139
|
method: 'POST',
|
|
@@ -17,8 +145,7 @@ async function performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl) {
|
|
|
17
145
|
if (!refreshRes.ok)
|
|
18
146
|
return null;
|
|
19
147
|
const setCookies = refreshRes.headers.getSetCookie();
|
|
20
|
-
let newJwt = '';
|
|
21
|
-
let newRefreshToken = '';
|
|
148
|
+
let newJwt = '', newRefreshToken = '';
|
|
22
149
|
setCookies.forEach(cookie => {
|
|
23
150
|
if (cookie.startsWith('jwt='))
|
|
24
151
|
newJwt = cookie.split(';')[0].split('=')[1];
|
|
@@ -32,37 +159,177 @@ async function performRefresh(refreshTokenCookie, jwtCookie, gatewayUrl) {
|
|
|
32
159
|
}
|
|
33
160
|
}
|
|
34
161
|
/**
|
|
35
|
-
*
|
|
36
|
-
* Returns the
|
|
162
|
+
* Helper function for custom middleware (proxy.ts).
|
|
163
|
+
* Returns the response and authentication state.
|
|
37
164
|
*/
|
|
38
|
-
async function
|
|
165
|
+
async function coreIamAuth(request) {
|
|
166
|
+
const { nextUrl, method } = request;
|
|
167
|
+
const pathname = nextUrl.pathname;
|
|
168
|
+
// ========================================
|
|
169
|
+
// 1. ZERO-CONFIG API PROXY
|
|
170
|
+
// ========================================
|
|
171
|
+
if (pathname.startsWith('/api/auth/')) {
|
|
172
|
+
// FIX: Short-circuit CORS Preflight requests!
|
|
173
|
+
if (method === 'OPTIONS') {
|
|
174
|
+
const res = new server_1.NextResponse(null, { status: 204 }); // 204 No Content
|
|
175
|
+
const origin = request.headers.get('origin');
|
|
176
|
+
if (origin) {
|
|
177
|
+
res.headers.set('Access-Control-Allow-Origin', origin);
|
|
178
|
+
res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
179
|
+
res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-CSRF-Token');
|
|
180
|
+
res.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
181
|
+
}
|
|
182
|
+
return { response: res, isAuthenticated: false };
|
|
183
|
+
}
|
|
184
|
+
const iam = new core_1.CoreIAM({ apiKey: process.env.COREIAM_API_KEY, baseUrl: gatewayUrl });
|
|
185
|
+
// FIX: Special handler for the Link Confirm POST! (Must be at the TOP, before generic body read)
|
|
186
|
+
if (pathname === '/api/auth/oauth/link/confirm') {
|
|
187
|
+
let formData = {};
|
|
188
|
+
const contentType = request.headers.get('content-type') || '';
|
|
189
|
+
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
190
|
+
const text = await request.text();
|
|
191
|
+
const params = new URLSearchParams(text);
|
|
192
|
+
formData = {
|
|
193
|
+
tempToken: params.get('tempToken'),
|
|
194
|
+
password: params.get('password')
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// Fallback to JSON (if called via fetch elsewhere)
|
|
199
|
+
formData = await request.json().catch(() => ({}));
|
|
200
|
+
}
|
|
201
|
+
const response = await iam.proxy('/auth/oauth/link/confirm', {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: JSON.stringify(formData),
|
|
204
|
+
headers: { 'Content-Type': 'application/json' },
|
|
205
|
+
}, request.headers);
|
|
206
|
+
const data = await response.json().catch(() => ({}));
|
|
207
|
+
// If CoreIAM successfully verified the password and linked the account
|
|
208
|
+
if (response.ok && data.success) {
|
|
209
|
+
const appOrigin = request.nextUrl.origin; // e.g., http://localhost:3000
|
|
210
|
+
const redirectPath = data.redirectUri || '/';
|
|
211
|
+
const finalUrl = new URL(redirectPath, appOrigin).toString();
|
|
212
|
+
// FIX: Return a 200 OK with an HTML page that uses JavaScript to redirect.
|
|
213
|
+
// This breaks the cross-site redirect chain, forcing the browser to send the Lax cookies.
|
|
214
|
+
const html = `
|
|
215
|
+
<!DOCTYPE html>
|
|
216
|
+
<html>
|
|
217
|
+
<head>
|
|
218
|
+
<title>Linking Successful</title>
|
|
219
|
+
</head>
|
|
220
|
+
<body>
|
|
221
|
+
<p>Account linked successfully! Redirecting...</p>
|
|
222
|
+
<script>window.location.href = "${finalUrl}";</script>
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
|
225
|
+
`;
|
|
226
|
+
const res = new server_1.NextResponse(html, { status: 200, headers: { 'Content-Type': 'text/html' } });
|
|
227
|
+
// Set the cookies on the Next.js domain (localhost:3000)!
|
|
228
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
229
|
+
if (data.accessToken) {
|
|
230
|
+
res.cookies.set('jwt', data.accessToken, { httpOnly: true, secure: isProd, sameSite: 'lax', maxAge: 24 * 60 * 60, path: '/' });
|
|
231
|
+
}
|
|
232
|
+
if (data.clientRefreshToken) {
|
|
233
|
+
res.cookies.set('refreshToken', data.clientRefreshToken, { httpOnly: true, secure: isProd, sameSite: 'lax', maxAge: 7 * 24 * 60 * 60, path: '/' });
|
|
234
|
+
}
|
|
235
|
+
return { response: res, isAuthenticated: true };
|
|
236
|
+
}
|
|
237
|
+
// FIX: If it failed (wrong password), redirect back to the hosted page with an error param
|
|
238
|
+
const errorRedirect = new URL(`${gatewayUrl}/auth/hosted/confirm-link`);
|
|
239
|
+
errorRedirect.searchParams.set('token', formData.tempToken);
|
|
240
|
+
errorRedirect.searchParams.set('provider', data.provider || 'google');
|
|
241
|
+
errorRedirect.searchParams.set('email', data.email || '');
|
|
242
|
+
errorRedirect.searchParams.set('appOrigin', request.nextUrl.origin);
|
|
243
|
+
errorRedirect.searchParams.set('error', data.message || 'Invalid password. Link failed.');
|
|
244
|
+
return { response: server_1.NextResponse.redirect(errorRedirect, 303), isAuthenticated: false };
|
|
245
|
+
}
|
|
246
|
+
// Now it is safe to read the body for all other standard routes (Login, Register, etc.)
|
|
247
|
+
let targetPath = pathname.replace('/api/auth', '/auth');
|
|
248
|
+
const body = method !== 'GET' ? await request.json().catch(() => undefined) : undefined;
|
|
249
|
+
// FIX: Special routing for OAuth Initiate
|
|
250
|
+
if (pathname.startsWith('/api/auth/oauth/') && !pathname.includes('callback') && !pathname.includes('link')) {
|
|
251
|
+
const provider = pathname.split('/').pop();
|
|
252
|
+
const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/${provider}`;
|
|
253
|
+
targetPath = `/auth/oauth/initiate/${provider}?redirectUri=${encodeURIComponent(redirectUri)}&scopes=email,profile`;
|
|
254
|
+
}
|
|
255
|
+
// FIX: Special routing for OAuth Callback
|
|
256
|
+
if (pathname.startsWith('/api/auth/callback/')) {
|
|
257
|
+
const provider = pathname.split('/').pop();
|
|
258
|
+
const searchParams = new URL(request.url).searchParams;
|
|
259
|
+
const code = searchParams.get('code');
|
|
260
|
+
const state = searchParams.get('state');
|
|
261
|
+
const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/${provider}`;
|
|
262
|
+
targetPath = `/auth/oauth/callback/${provider}?code=${code}&state=${state}&redirectUri=${redirectUri}`;
|
|
263
|
+
}
|
|
264
|
+
const response = await iam.proxy(targetPath, {
|
|
265
|
+
method,
|
|
266
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
267
|
+
headers: { 'Content-Type': 'application/json' },
|
|
268
|
+
}, request.headers);
|
|
269
|
+
// Handle OAuth Initiate Redirects
|
|
270
|
+
if (pathname.startsWith('/api/auth/oauth/') && !pathname.includes('callback') && !pathname.includes('link')) {
|
|
271
|
+
const data = await response.json().catch(() => ({}));
|
|
272
|
+
if (data.url)
|
|
273
|
+
return { response: server_1.NextResponse.redirect(data.url), isAuthenticated: false };
|
|
274
|
+
return { response: server_1.NextResponse.redirect(new URL('/auth/login', request.url)), isAuthenticated: false };
|
|
275
|
+
}
|
|
276
|
+
// Handle OAuth Callback Redirects
|
|
277
|
+
if (pathname.startsWith('/api/auth/callback/')) {
|
|
278
|
+
const data = await response.json().catch(() => ({}));
|
|
279
|
+
// FIX: Redirect to CoreIAM's Hosted Confirmation Page and pass appOrigin!
|
|
280
|
+
if (data.action === 'link_candidate') {
|
|
281
|
+
const appOrigin = request.nextUrl.origin; // e.g., http://localhost:3000
|
|
282
|
+
const redirectUrl = `${gatewayUrl}/auth/hosted/confirm-link?token=${encodeURIComponent(data.tempToken)}&provider=${encodeURIComponent(data.provider)}&email=${encodeURIComponent(data.email)}&appOrigin=${encodeURIComponent(appOrigin)}`;
|
|
283
|
+
return { response: server_1.NextResponse.redirect(redirectUrl), isAuthenticated: false };
|
|
284
|
+
}
|
|
285
|
+
// Normal OAuth login: redirect to dashboard
|
|
286
|
+
const res = server_1.NextResponse.redirect(new URL('/', request.url));
|
|
287
|
+
const setCookies = response.headers.getSetCookie();
|
|
288
|
+
setCookies.forEach(c => res.headers.append('set-cookie', c));
|
|
289
|
+
return { response: res, isAuthenticated: false };
|
|
290
|
+
}
|
|
291
|
+
// Standard JSON API Responses (Login, Logout, Register, etc.)
|
|
292
|
+
const data = await response.json().catch(() => ({}));
|
|
293
|
+
const res = server_1.NextResponse.json(data, { status: response.status });
|
|
294
|
+
const setCookies = response.headers.getSetCookie();
|
|
295
|
+
setCookies.forEach(c => res.headers.append('set-cookie', c));
|
|
296
|
+
return { response: res, isAuthenticated: false };
|
|
297
|
+
}
|
|
298
|
+
// 2. LOCAL JWT VERIFICATION & REFRESH
|
|
39
299
|
const jwtCookie = request.cookies.get("jwt");
|
|
40
300
|
const refreshTokenCookie = request.cookies.get("refreshToken");
|
|
41
|
-
const gatewayUrl = process.env.COREIAM_API_URL;
|
|
42
301
|
let isAuthenticated = false;
|
|
43
302
|
let needsRefresh = false;
|
|
44
|
-
if (jwtCookie) {
|
|
303
|
+
if (jwtCookie && JWKS) {
|
|
45
304
|
try {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
305
|
+
const { payload } = await (0, jose_1.jwtVerify)(jwtCookie.value, JWKS, {
|
|
306
|
+
issuer: 'coreiam',
|
|
307
|
+
algorithms: ['RS256']
|
|
308
|
+
});
|
|
309
|
+
const requestHeaders = new Headers(request.headers);
|
|
310
|
+
requestHeaders.set('x-coreiam-identity', JSON.stringify({
|
|
311
|
+
sub: payload.sub,
|
|
312
|
+
email: payload.email,
|
|
313
|
+
tenantId: payload.tenantId,
|
|
314
|
+
roles: payload.roles || []
|
|
315
|
+
}));
|
|
316
|
+
return {
|
|
317
|
+
response: server_1.NextResponse.next({ request: { headers: requestHeaders } }),
|
|
318
|
+
isAuthenticated: true
|
|
319
|
+
};
|
|
53
320
|
}
|
|
54
|
-
catch {
|
|
55
|
-
if (refreshTokenCookie)
|
|
321
|
+
catch (error) {
|
|
322
|
+
if (error.code === 'ERR_JWT_EXPIRED' && refreshTokenCookie) {
|
|
56
323
|
needsRefresh = true;
|
|
324
|
+
}
|
|
57
325
|
}
|
|
58
326
|
}
|
|
59
327
|
else if (refreshTokenCookie) {
|
|
60
328
|
needsRefresh = true;
|
|
61
329
|
}
|
|
62
|
-
|
|
63
|
-
if (needsRefresh && gatewayUrl) {
|
|
330
|
+
if (needsRefresh) {
|
|
64
331
|
if (!refreshInFlight) {
|
|
65
|
-
refreshInFlight = performRefresh(refreshTokenCookie, jwtCookie
|
|
332
|
+
refreshInFlight = performRefresh(refreshTokenCookie, jwtCookie).finally(() => {
|
|
66
333
|
refreshInFlight = null;
|
|
67
334
|
});
|
|
68
335
|
}
|
|
@@ -71,30 +338,48 @@ async function coreIamMiddleware(request) {
|
|
|
71
338
|
const requestHeaders = new Headers(request.headers);
|
|
72
339
|
requestHeaders.set('cookie', `jwt=${result.jwt}; refreshToken=${result.refreshToken}`);
|
|
73
340
|
requestHeaders.set('Authorization', `Bearer ${result.jwt}`);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
341
|
+
try {
|
|
342
|
+
const payloadB64 = result.jwt.split('.')[1];
|
|
343
|
+
const payloadStr = Buffer.from(payloadB64, 'base64').toString('utf8');
|
|
344
|
+
const payload = JSON.parse(payloadStr);
|
|
345
|
+
requestHeaders.set('x-coreiam-identity', JSON.stringify({
|
|
346
|
+
sub: payload.sub,
|
|
347
|
+
email: payload.email,
|
|
348
|
+
tenantId: payload.tenantId,
|
|
349
|
+
roles: payload.roles || []
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
catch { }
|
|
353
|
+
const response = server_1.NextResponse.next({ request: { headers: requestHeaders } });
|
|
77
354
|
const isProd = process.env.NODE_ENV === 'production';
|
|
78
|
-
response.cookies.set('jwt', result.jwt, {
|
|
79
|
-
|
|
80
|
-
secure: isProd,
|
|
81
|
-
sameSite: 'strict',
|
|
82
|
-
maxAge: 24 * 60 * 60,
|
|
83
|
-
});
|
|
84
|
-
response.cookies.set('refreshToken', result.refreshToken, {
|
|
85
|
-
httpOnly: true,
|
|
86
|
-
secure: isProd,
|
|
87
|
-
sameSite: 'strict',
|
|
88
|
-
maxAge: 7 * 24 * 60 * 60,
|
|
89
|
-
});
|
|
355
|
+
response.cookies.set('jwt', result.jwt, { httpOnly: true, secure: isProd, sameSite: 'strict', maxAge: 24 * 60 * 60 });
|
|
356
|
+
response.cookies.set('refreshToken', result.refreshToken, { httpOnly: true, secure: isProd, sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 });
|
|
90
357
|
return { response, isAuthenticated: true };
|
|
91
358
|
}
|
|
92
|
-
// 2. IF REFRESH FAILED, CLEAR COOKIES
|
|
93
359
|
const response = server_1.NextResponse.next();
|
|
94
360
|
response.cookies.delete('jwt');
|
|
95
361
|
response.cookies.delete('refreshToken');
|
|
96
362
|
return { response, isAuthenticated: false };
|
|
97
363
|
}
|
|
98
|
-
|
|
99
|
-
|
|
364
|
+
return { response: server_1.NextResponse.next(), isAuthenticated: false };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* 1-line middleware factory for developers who don't need custom routing.
|
|
368
|
+
*/
|
|
369
|
+
function coreIamMiddleware(options = {}) {
|
|
370
|
+
const { publicRoutes = [], signInUrl = '/login' } = options;
|
|
371
|
+
return async (request) => {
|
|
372
|
+
const { response, isAuthenticated } = await coreIamAuth(request);
|
|
373
|
+
const pathname = request.nextUrl.pathname;
|
|
374
|
+
const isPublicRoute = publicRoutes.some(route => pathname.startsWith(route));
|
|
375
|
+
// If not authenticated and trying to access a protected route
|
|
376
|
+
if (!isAuthenticated && !isPublicRoute) {
|
|
377
|
+
const redirect = server_1.NextResponse.redirect(new URL(signInUrl, request.url));
|
|
378
|
+
// CRITICAL: Copy any refreshed cookies/headers from the SDK response onto the redirect
|
|
379
|
+
const setCookies = response.headers.getSetCookie();
|
|
380
|
+
setCookies.forEach(c => redirect.headers.append('set-cookie', c));
|
|
381
|
+
return redirect;
|
|
382
|
+
}
|
|
383
|
+
return response;
|
|
384
|
+
};
|
|
100
385
|
}
|
|
@@ -1,39 +1,30 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { CoreIAMIdentity } from '../core';
|
|
3
|
-
export interface CoreIAMRedirects {
|
|
4
|
-
/** Where to send the user after a successful login, if no callbackUrl is passed to login(). */
|
|
5
|
-
afterLogin?: string;
|
|
6
|
-
/** Where to send the user after logout. */
|
|
7
|
-
afterLogout?: string;
|
|
8
|
-
}
|
|
9
|
-
interface LoginResult {
|
|
10
|
-
success?: boolean;
|
|
11
|
-
error?: string;
|
|
12
|
-
}
|
|
13
3
|
interface AuthContextType {
|
|
14
|
-
/**
|
|
15
|
-
* Generic CoreIAM identity (sub/email/roles/tenantId) — NOT an app-shaped
|
|
16
|
-
* user. This SDK has no knowledge of any consumer's local user model
|
|
17
|
-
* (roles, profile fields, etc). Consumers that need an enriched user
|
|
18
|
-
* object should build their own layer on top of this, keyed off `sub`.
|
|
19
|
-
*/
|
|
20
4
|
identity: CoreIAMIdentity | null;
|
|
21
5
|
isLoading: boolean;
|
|
22
6
|
isAuthenticated: boolean;
|
|
23
7
|
login: (values: {
|
|
24
8
|
email: string;
|
|
25
9
|
password: string;
|
|
26
|
-
}, callbackUrl?: string) => Promise<
|
|
10
|
+
}, callbackUrl?: string) => Promise<{
|
|
11
|
+
success?: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
}>;
|
|
27
14
|
logout: () => Promise<void>;
|
|
28
|
-
register: (userData:
|
|
15
|
+
register: (userData: any) => Promise<{
|
|
16
|
+
success?: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}>;
|
|
19
|
+
verifyMfa: (tempToken: string, code: string) => Promise<{
|
|
20
|
+
success?: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
}>;
|
|
29
23
|
refetchIdentity: () => Promise<void>;
|
|
30
24
|
}
|
|
31
|
-
export declare function CoreIAMProvider({ children, initialIdentity
|
|
25
|
+
export declare function CoreIAMProvider({ children, initialIdentity }: {
|
|
32
26
|
children: React.ReactNode;
|
|
33
|
-
/** Pass the result of a server-side getSession() call to skip the client fetch on first paint. */
|
|
34
27
|
initialIdentity?: CoreIAMIdentity | null;
|
|
35
|
-
/** Where to redirect after login/logout. Omit for no redirect (caller handles navigation itself). */
|
|
36
|
-
redirects?: CoreIAMRedirects;
|
|
37
28
|
}): import("react/jsx-runtime").JSX.Element;
|
|
38
29
|
export declare function useAuth(): AuthContextType;
|
|
39
30
|
export {};
|
package/dist/nextjs/provider.js
CHANGED
|
@@ -1,182 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// // src/nextjs/provider.tsx
|
|
3
3
|
// 'use client';
|
|
4
|
-
// import React, { createContext, useContext, useEffect, useState
|
|
5
|
-
// import {
|
|
4
|
+
// import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
5
|
+
// import { getSession } from './actions';
|
|
6
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
7
|
// interface AuthContextType {
|
|
17
8
|
// identity: CoreIAMIdentity | null;
|
|
18
9
|
// isLoading: boolean;
|
|
19
10
|
// 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
11
|
// refetchIdentity: () => Promise<void>;
|
|
24
12
|
// }
|
|
25
13
|
// const AuthContext = createContext<AuthContextType>({
|
|
26
14
|
// identity: null,
|
|
27
15
|
// isLoading: true,
|
|
28
16
|
// isAuthenticated: false,
|
|
29
|
-
// login: async () => ({ error: 'CoreIAMProvider is not mounted' }),
|
|
30
|
-
// logout: async () => {},
|
|
31
|
-
// register: async () => ({ error: 'CoreIAMProvider is not mounted' }),
|
|
32
17
|
// refetchIdentity: async () => {},
|
|
33
18
|
// });
|
|
34
|
-
// export function CoreIAMProvider({
|
|
35
|
-
// children,
|
|
36
|
-
// initialIdentity
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// children: React.ReactNode;
|
|
19
|
+
// export function CoreIAMProvider({
|
|
20
|
+
// children,
|
|
21
|
+
// initialIdentity
|
|
22
|
+
// }: {
|
|
23
|
+
// children: React.ReactNode;
|
|
40
24
|
// initialIdentity?: CoreIAMIdentity | null;
|
|
41
|
-
// redirects?: CoreIAMRedirects;
|
|
42
25
|
// }) {
|
|
43
26
|
// const [identity, setIdentity] = useState<CoreIAMIdentity | null>(initialIdentity ?? null);
|
|
44
27
|
// const [isLoading, setIsLoading] = useState(initialIdentity === undefined);
|
|
45
|
-
// const
|
|
46
|
-
// const refetchIdentity = useCallback(async () => {
|
|
28
|
+
// const refetchIdentity = async () => {
|
|
47
29
|
// try {
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// setIdentity(res.ok ? await res.json() : null);
|
|
30
|
+
// const result = await getSession();
|
|
31
|
+
// setIdentity(result);
|
|
51
32
|
// } catch {
|
|
52
33
|
// setIdentity(null);
|
|
53
34
|
// } finally {
|
|
54
35
|
// setIsLoading(false);
|
|
55
36
|
// }
|
|
56
|
-
// }
|
|
37
|
+
// };
|
|
57
38
|
// useEffect(() => {
|
|
58
39
|
// if (initialIdentity === undefined) {
|
|
59
40
|
// refetchIdentity();
|
|
60
41
|
// } else {
|
|
61
42
|
// setIsLoading(false);
|
|
62
43
|
// }
|
|
63
|
-
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
64
44
|
// }, []);
|
|
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
45
|
// return (
|
|
177
|
-
// <AuthContext.Provider
|
|
178
|
-
// value={{ identity, isLoading, isAuthenticated: !!identity, login, logout, register, refetchIdentity }}
|
|
179
|
-
// >
|
|
46
|
+
// <AuthContext.Provider value={{ identity, isLoading, isAuthenticated: !!identity, refetchIdentity }}>
|
|
180
47
|
// {children}
|
|
181
48
|
// </AuthContext.Provider>
|
|
182
49
|
// );
|
|
@@ -184,6 +51,7 @@
|
|
|
184
51
|
// export function useAuth() {
|
|
185
52
|
// return useContext(AuthContext);
|
|
186
53
|
// }
|
|
54
|
+
// src/nextjs/provider.tsx
|
|
187
55
|
'use client';
|
|
188
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
189
57
|
exports.CoreIAMProvider = CoreIAMProvider;
|
|
@@ -196,20 +64,17 @@ const AuthContext = (0, react_1.createContext)({
|
|
|
196
64
|
identity: null,
|
|
197
65
|
isLoading: true,
|
|
198
66
|
isAuthenticated: false,
|
|
199
|
-
login: async () => ({ error: '
|
|
67
|
+
login: async () => ({ error: 'Not mounted' }),
|
|
200
68
|
logout: async () => { },
|
|
201
|
-
register: async () => ({ error: '
|
|
69
|
+
register: async () => ({ error: 'Not mounted' }),
|
|
70
|
+
verifyMfa: async () => ({ error: 'Not mounted' }),
|
|
202
71
|
refetchIdentity: async () => { },
|
|
203
72
|
});
|
|
204
|
-
function CoreIAMProvider({ children, initialIdentity
|
|
73
|
+
function CoreIAMProvider({ children, initialIdentity }) {
|
|
205
74
|
const [identity, setIdentity] = (0, react_1.useState)(initialIdentity ?? null);
|
|
206
75
|
const [isLoading, setIsLoading] = (0, react_1.useState)(initialIdentity === undefined);
|
|
207
76
|
const router = (0, navigation_1.useRouter)();
|
|
208
77
|
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()}`);
|
|
213
78
|
try {
|
|
214
79
|
const result = await (0, actions_1.getSession)();
|
|
215
80
|
setIdentity(result);
|
|
@@ -222,37 +87,71 @@ function CoreIAMProvider({ children, initialIdentity, redirects = {}, }) {
|
|
|
222
87
|
}
|
|
223
88
|
};
|
|
224
89
|
(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()}`);
|
|
235
90
|
if (initialIdentity === undefined) {
|
|
236
91
|
refetchIdentity();
|
|
237
92
|
}
|
|
238
93
|
else {
|
|
239
94
|
setIsLoading(false);
|
|
240
95
|
}
|
|
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.
|
|
243
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
244
96
|
}, []);
|
|
97
|
+
const getCsrfToken = async () => {
|
|
98
|
+
const res = await fetch('/api/auth/csrf-token', {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
credentials: 'include'
|
|
101
|
+
});
|
|
102
|
+
if (!res.ok)
|
|
103
|
+
throw new Error('Failed to get CSRF token');
|
|
104
|
+
const { csrfToken } = await res.json();
|
|
105
|
+
return csrfToken;
|
|
106
|
+
};
|
|
107
|
+
const verifyMfa = async (tempToken, code) => {
|
|
108
|
+
setIsLoading(true);
|
|
109
|
+
try {
|
|
110
|
+
const res = await fetch('/api/auth/mfa/login', {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
113
|
+
credentials: 'include',
|
|
114
|
+
body: JSON.stringify({ tempToken, code }),
|
|
115
|
+
});
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
return { error: data.message || 'Invalid 2FA code.' };
|
|
119
|
+
}
|
|
120
|
+
// Success! Hard refresh to let the server re-render with new cookies
|
|
121
|
+
window.location.href = '/';
|
|
122
|
+
return { success: true };
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return { error: 'Something went wrong' };
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
setIsLoading(false);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
245
131
|
const login = async (values, callbackUrl) => {
|
|
246
132
|
setIsLoading(true);
|
|
247
133
|
try {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
134
|
+
// 1. Fetch CSRF token
|
|
135
|
+
const csrfToken = await getCsrfToken();
|
|
136
|
+
// 2. Call login API route
|
|
137
|
+
const res = await fetch('/api/auth/login', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
'x-csrf-token': csrfToken,
|
|
142
|
+
},
|
|
143
|
+
credentials: 'include',
|
|
144
|
+
body: JSON.stringify({ identifier: values.email, password: values.password }),
|
|
145
|
+
});
|
|
146
|
+
const data = await res.json();
|
|
147
|
+
if (!res.ok)
|
|
148
|
+
return { error: data.message || 'Invalid credentials' };
|
|
149
|
+
if (data.twoFactorRequired) {
|
|
150
|
+
// Return the twoFactor flag and the tempToken to the form
|
|
151
|
+
return { twoFactor: true, tempToken: data.tempToken };
|
|
251
152
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (target)
|
|
255
|
-
router.push(target);
|
|
153
|
+
// Hard refresh to let server re-render with new cookies
|
|
154
|
+
window.location.href = callbackUrl || '/';
|
|
256
155
|
return { success: true };
|
|
257
156
|
}
|
|
258
157
|
catch {
|
|
@@ -263,31 +162,52 @@ function CoreIAMProvider({ children, initialIdentity, redirects = {}, }) {
|
|
|
263
162
|
}
|
|
264
163
|
};
|
|
265
164
|
const logout = async () => {
|
|
165
|
+
setIsLoading(true);
|
|
266
166
|
try {
|
|
267
|
-
await (
|
|
167
|
+
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
|
|
168
|
+
setIdentity(null);
|
|
169
|
+
window.location.href = '/auth/login';
|
|
268
170
|
}
|
|
269
171
|
finally {
|
|
270
|
-
|
|
271
|
-
if (redirects.afterLogout)
|
|
272
|
-
router.push(redirects.afterLogout);
|
|
172
|
+
setIsLoading(false);
|
|
273
173
|
}
|
|
274
174
|
};
|
|
275
175
|
const register = async (userData) => {
|
|
276
176
|
setIsLoading(true);
|
|
277
177
|
try {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
178
|
+
// 1. Fetch CSRF token
|
|
179
|
+
const csrfToken = await getCsrfToken();
|
|
180
|
+
// 2. Call register API route
|
|
181
|
+
const res = await fetch('/api/auth/register', {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
headers: {
|
|
184
|
+
'Content-Type': 'application/json',
|
|
185
|
+
'x-csrf-token': csrfToken,
|
|
186
|
+
},
|
|
187
|
+
credentials: 'include',
|
|
188
|
+
body: JSON.stringify(userData),
|
|
189
|
+
});
|
|
190
|
+
const data = await res.json();
|
|
191
|
+
// 3. FIX: Check for the "OAuth Required" signal
|
|
192
|
+
if (!res.ok) {
|
|
193
|
+
if (data.code === 'OAUTH_ACCOUNT_EXISTS') {
|
|
194
|
+
// Automatically redirect to the OAuth provider!
|
|
195
|
+
window.location.href = `/api/auth/oauth/${data.provider}`;
|
|
196
|
+
return { success: true };
|
|
197
|
+
}
|
|
198
|
+
return { error: data.message || 'Registration failed' };
|
|
281
199
|
}
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
200
|
+
// Auto-login after standard registration
|
|
201
|
+
return login({ email: userData.email, password: userData.password });
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return { error: 'Something went wrong' };
|
|
285
205
|
}
|
|
286
206
|
finally {
|
|
287
207
|
setIsLoading(false);
|
|
288
208
|
}
|
|
289
209
|
};
|
|
290
|
-
return ((0, jsx_runtime_1.jsx)(AuthContext.Provider, { value: { identity, isLoading, isAuthenticated: !!identity, login, logout, register, refetchIdentity }, children: children }));
|
|
210
|
+
return ((0, jsx_runtime_1.jsx)(AuthContext.Provider, { value: { identity, isLoading, isAuthenticated: !!identity, login, logout, register, verifyMfa, refetchIdentity }, children: children }));
|
|
291
211
|
}
|
|
292
212
|
function useAuth() {
|
|
293
213
|
return (0, react_1.useContext)(AuthContext);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SignInButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
3
|
+
/** If true, renders child directly via Radix Slot instead of a <button> */
|
|
4
|
+
asChild?: boolean;
|
|
5
|
+
/** Destination route on click */
|
|
6
|
+
redirectTo?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Renders a sign-in button or merges click behavior onto a child element (asChild).
|
|
10
|
+
*/
|
|
11
|
+
export declare function SignInButton({ asChild, redirectTo, onClick, children, ...props }: SignInButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export interface SignUpButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
13
|
+
asChild?: boolean;
|
|
14
|
+
redirectTo?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Renders a sign-up button or merges click behavior onto a child element (asChild).
|
|
18
|
+
*/
|
|
19
|
+
export declare function SignUpButton({ asChild, redirectTo, onClick, children, ...props }: SignUpButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export interface OAuthButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
21
|
+
asChild?: boolean;
|
|
22
|
+
provider: 'google' | 'github' | 'microsoft' | 'apple' | string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Renders an OAuth provider button or merges click behavior onto a child element (asChild).
|
|
26
|
+
*/
|
|
27
|
+
export declare function OAuthButton({ asChild, provider, onClick, children, ...props }: OAuthButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export interface SignOutButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
29
|
+
asChild?: boolean;
|
|
30
|
+
/** Destination route after signing out */
|
|
31
|
+
redirectTo?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Renders a sign-out button or merges click behavior onto a child element (asChild).
|
|
35
|
+
*/
|
|
36
|
+
export declare function SignOutButton({ asChild, redirectTo, onClick, children, ...props }: SignOutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
export interface UserButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
38
|
+
asChild?: boolean;
|
|
39
|
+
/** Destination route for the user's account/profile page */
|
|
40
|
+
redirectTo?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Renders a user profile button or merges click behavior onto a child element (asChild).
|
|
44
|
+
*/
|
|
45
|
+
export declare function UserButton({ asChild, redirectTo, onClick, children, ...props }: UserButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.SignInButton = SignInButton;
|
|
5
|
+
exports.SignUpButton = SignUpButton;
|
|
6
|
+
exports.OAuthButton = OAuthButton;
|
|
7
|
+
exports.SignOutButton = SignOutButton;
|
|
8
|
+
exports.UserButton = UserButton;
|
|
9
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
10
|
+
const react_slot_1 = require("@radix-ui/react-slot");
|
|
11
|
+
/**
|
|
12
|
+
* Renders a sign-in button or merges click behavior onto a child element (asChild).
|
|
13
|
+
*/
|
|
14
|
+
function SignInButton({ asChild = false, redirectTo = '/auth/login', onClick, children, ...props }) {
|
|
15
|
+
const Component = asChild ? react_slot_1.Slot : 'button';
|
|
16
|
+
const handleClick = (e) => {
|
|
17
|
+
onClick?.(e); // Run original onClick prop if passed
|
|
18
|
+
if (!e.defaultPrevented) {
|
|
19
|
+
window.location.href = redirectTo;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
return ((0, jsx_runtime_1.jsx)(Component, { ...props, onClick: handleClick, children: children ?? 'Sign In' }));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Renders a sign-up button or merges click behavior onto a child element (asChild).
|
|
26
|
+
*/
|
|
27
|
+
function SignUpButton({ asChild = false, redirectTo = '/auth/register', onClick, children, ...props }) {
|
|
28
|
+
const Component = asChild ? react_slot_1.Slot : 'button';
|
|
29
|
+
const handleClick = (e) => {
|
|
30
|
+
onClick?.(e);
|
|
31
|
+
if (!e.defaultPrevented) {
|
|
32
|
+
window.location.href = redirectTo;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return ((0, jsx_runtime_1.jsx)(Component, { ...props, onClick: handleClick, children: children ?? 'Sign Up' }));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Renders an OAuth provider button or merges click behavior onto a child element (asChild).
|
|
39
|
+
*/
|
|
40
|
+
function OAuthButton({ asChild = false, provider, onClick, children, ...props }) {
|
|
41
|
+
const Component = asChild ? react_slot_1.Slot : 'button';
|
|
42
|
+
const handleClick = (e) => {
|
|
43
|
+
onClick?.(e);
|
|
44
|
+
if (!e.defaultPrevented) {
|
|
45
|
+
window.location.href = `/api/auth/oauth/${provider}`;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return ((0, jsx_runtime_1.jsx)(Component, { ...props, onClick: handleClick, children: children ?? `Continue with ${provider}` }));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Renders a sign-out button or merges click behavior onto a child element (asChild).
|
|
52
|
+
*/
|
|
53
|
+
function SignOutButton({ asChild = false, redirectTo = '/', onClick, children, ...props }) {
|
|
54
|
+
const Component = asChild ? react_slot_1.Slot : 'button';
|
|
55
|
+
const handleClick = (e) => {
|
|
56
|
+
onClick?.(e);
|
|
57
|
+
if (!e.defaultPrevented) {
|
|
58
|
+
// Direct to your framework's sign-out endpoint, passing the redirect URL
|
|
59
|
+
window.location.href = `/api/auth/signout?callbackUrl=${encodeURIComponent(redirectTo)}`;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return ((0, jsx_runtime_1.jsx)(Component, { ...props, onClick: handleClick, children: children ?? 'Sign Out' }));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Renders a user profile button or merges click behavior onto a child element (asChild).
|
|
66
|
+
*/
|
|
67
|
+
function UserButton({ asChild = false, redirectTo = '/account', onClick, children, ...props }) {
|
|
68
|
+
const Component = asChild ? react_slot_1.Slot : 'button';
|
|
69
|
+
const handleClick = (e) => {
|
|
70
|
+
onClick?.(e);
|
|
71
|
+
if (!e.defaultPrevented) {
|
|
72
|
+
window.location.href = redirectTo;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return ((0, jsx_runtime_1.jsx)(Component, { ...props, onClick: handleClick, children: children ?? 'My Account' }));
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreiam/auth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.39",
|
|
4
4
|
"main": "./dist/core/index.js",
|
|
5
5
|
"types": "./dist/core/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -57,6 +57,8 @@
|
|
|
57
57
|
"typescript": "^5.0.0"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
+
"@radix-ui/react-slot": "^1.3.3",
|
|
61
|
+
"jose": "^6.2.7",
|
|
60
62
|
"jwt-decode": "^4.0.0"
|
|
61
63
|
}
|
|
62
|
-
}
|
|
64
|
+
}
|