@coreiam/auth 1.0.2 → 1.0.4
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.js +5 -42
- package/dist/nextjs/index.d.ts +1 -0
- package/dist/nextjs/index.js +3 -1
- package/dist/nextjs/middleware.d.ts +2 -0
- package/dist/nextjs/middleware.js +94 -0
- package/package.json +23 -6
package/dist/nextjs/actions.js
CHANGED
|
@@ -9,8 +9,6 @@ exports.logoutActionSdk = logoutActionSdk;
|
|
|
9
9
|
const headers_1 = require("next/headers");
|
|
10
10
|
const core_1 = require("../core");
|
|
11
11
|
const iam = new core_1.CoreIAM();
|
|
12
|
-
// GLOBAL LOCK: Prevents concurrent refresh requests
|
|
13
|
-
let refreshLock = null;
|
|
14
12
|
async function syncCookiesToNext(response) {
|
|
15
13
|
const cookieStore = await (0, headers_1.cookies)();
|
|
16
14
|
const setCookies = response.headers.getSetCookie();
|
|
@@ -31,21 +29,17 @@ async function syncCookiesToNext(response) {
|
|
|
31
29
|
cookieOptions.path = attrValue || '/';
|
|
32
30
|
if (lowerName === 'max-age') {
|
|
33
31
|
const maxAge = parseInt(attrValue, 10);
|
|
34
|
-
if (maxAge <= 0)
|
|
32
|
+
if (maxAge <= 0)
|
|
35
33
|
isDeleting = true;
|
|
36
|
-
|
|
37
|
-
else {
|
|
34
|
+
else
|
|
38
35
|
cookieOptions.maxAge = maxAge;
|
|
39
|
-
}
|
|
40
36
|
}
|
|
41
37
|
if (lowerName === 'expires') {
|
|
42
38
|
const expiryDate = new Date(attrValue);
|
|
43
|
-
if (expiryDate.getTime() < Date.now())
|
|
39
|
+
if (expiryDate.getTime() < Date.now())
|
|
44
40
|
isDeleting = true;
|
|
45
|
-
|
|
46
|
-
else {
|
|
41
|
+
else
|
|
47
42
|
cookieOptions.expires = expiryDate;
|
|
48
|
-
}
|
|
49
43
|
}
|
|
50
44
|
if (lowerName === 'samesite')
|
|
51
45
|
cookieOptions.sameSite = attrValue?.toLowerCase() || 'lax';
|
|
@@ -62,38 +56,7 @@ async function syncCookiesToNext(response) {
|
|
|
62
56
|
}
|
|
63
57
|
async function getSession() {
|
|
64
58
|
const reqHeaders = await (0, headers_1.headers)();
|
|
65
|
-
|
|
66
|
-
if (response.status === 401) {
|
|
67
|
-
// 1. Use the lock! If another component is already refreshing, wait for it.
|
|
68
|
-
if (!refreshLock) {
|
|
69
|
-
refreshLock = iam.proxy('/auth/refresh-token', { method: 'POST' }, reqHeaders)
|
|
70
|
-
.then(res => res.ok ? res.headers.getSetCookie() : null)
|
|
71
|
-
.finally(() => { refreshLock = null; }); // Clear lock when done
|
|
72
|
-
}
|
|
73
|
-
const setCookies = await refreshLock;
|
|
74
|
-
if (setCookies && setCookies.length > 0) {
|
|
75
|
-
// 2. Sync the new cookies to the browser
|
|
76
|
-
const fakeRes = new Response(null, { headers: { 'set-cookie': setCookies.join(', ') } });
|
|
77
|
-
await syncCookiesToNext(fakeRes);
|
|
78
|
-
// 3. Build new headers with the refreshed cookies and retry
|
|
79
|
-
const cookieStore = await (0, headers_1.cookies)();
|
|
80
|
-
const newJwt = cookieStore.get('jwt')?.value;
|
|
81
|
-
const newRefresh = cookieStore.get('refreshToken')?.value;
|
|
82
|
-
const retryHeaders = new Headers(reqHeaders);
|
|
83
|
-
if (newJwt && newRefresh) {
|
|
84
|
-
retryHeaders.set('cookie', `jwt=${newJwt}; refreshToken=${newRefresh}`);
|
|
85
|
-
retryHeaders.set('Authorization', `Bearer ${newJwt}`);
|
|
86
|
-
}
|
|
87
|
-
response = await iam.proxy('/auth/profile', { method: 'GET' }, retryHeaders);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
// 4. CRITICAL: If refresh failed, CLEAR THE COOKIES to break the infinite loop!
|
|
91
|
-
const cookieStore = await (0, headers_1.cookies)();
|
|
92
|
-
cookieStore.delete('jwt');
|
|
93
|
-
cookieStore.delete('refreshToken');
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
59
|
+
const response = await iam.proxy('/auth/profile', { method: 'GET' }, reqHeaders);
|
|
97
60
|
if (!response.ok)
|
|
98
61
|
return null;
|
|
99
62
|
const coreIamIdentity = await response.json();
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { CoreIAMProvider, useAuth } from './provider';
|
|
2
2
|
export { loginActionSdk, registerActionSdk, logoutActionSdk, getSession } from './actions';
|
|
3
|
+
export { coreIamMiddleware } from './middleware';
|
|
3
4
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
5
|
import { CoreIAM } from '../core';
|
|
5
6
|
export declare class NextHandlers {
|
package/dist/nextjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NextHandlers = exports.getSession = exports.logoutActionSdk = exports.registerActionSdk = exports.loginActionSdk = exports.useAuth = exports.CoreIAMProvider = void 0;
|
|
3
|
+
exports.NextHandlers = exports.coreIamMiddleware = exports.getSession = exports.logoutActionSdk = exports.registerActionSdk = exports.loginActionSdk = exports.useAuth = exports.CoreIAMProvider = void 0;
|
|
4
4
|
// src/nextjs/index.ts
|
|
5
5
|
var provider_1 = require("./provider");
|
|
6
6
|
Object.defineProperty(exports, "CoreIAMProvider", { enumerable: true, get: function () { return provider_1.CoreIAMProvider; } });
|
|
@@ -10,6 +10,8 @@ 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
|
+
var middleware_1 = require("./middleware");
|
|
14
|
+
Object.defineProperty(exports, "coreIamMiddleware", { enumerable: true, get: function () { return middleware_1.coreIamMiddleware; } });
|
|
13
15
|
const server_1 = require("next/server");
|
|
14
16
|
class NextHandlers {
|
|
15
17
|
iam;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.coreIamMiddleware = coreIamMiddleware;
|
|
4
|
+
// src/nextjs/middleware.ts
|
|
5
|
+
const server_1 = require("next/server");
|
|
6
|
+
const jwt_decode_1 = require("jwt-decode");
|
|
7
|
+
const DEFAULT_GATEWAY_URL = "https://api.e-qalam.com";
|
|
8
|
+
function coreIamMiddleware() {
|
|
9
|
+
return async function middleware(request) {
|
|
10
|
+
const { nextUrl } = request;
|
|
11
|
+
const pathname = nextUrl.pathname;
|
|
12
|
+
const jwtCookie = request.cookies.get("jwt");
|
|
13
|
+
const refreshTokenCookie = request.cookies.get("refreshToken");
|
|
14
|
+
let isLoggedIn = false;
|
|
15
|
+
let needsRefresh = false;
|
|
16
|
+
if (jwtCookie) {
|
|
17
|
+
try {
|
|
18
|
+
const decoded = (0, jwt_decode_1.jwtDecode)(jwtCookie.value);
|
|
19
|
+
if (decoded.exp && decoded.exp * 1000 > Date.now()) {
|
|
20
|
+
isLoggedIn = true;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
if (refreshTokenCookie)
|
|
24
|
+
needsRefresh = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
if (refreshTokenCookie)
|
|
29
|
+
needsRefresh = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (refreshTokenCookie) {
|
|
33
|
+
needsRefresh = true;
|
|
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;
|
|
83
|
+
}
|
|
84
|
+
// 3. STANDARD ROUTE PROTECTION
|
|
85
|
+
const isAuthRoute = pathname.startsWith('/auth/login') || pathname.startsWith('/auth/register');
|
|
86
|
+
if (isAuthRoute && isLoggedIn) {
|
|
87
|
+
return server_1.NextResponse.redirect(new URL('/', nextUrl));
|
|
88
|
+
}
|
|
89
|
+
if (!isLoggedIn && !isAuthRoute) {
|
|
90
|
+
return server_1.NextResponse.redirect(new URL('/auth/login', nextUrl));
|
|
91
|
+
}
|
|
92
|
+
return server_1.NextResponse.next();
|
|
93
|
+
};
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreiam/auth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"main": "./dist/core/index.js",
|
|
5
5
|
"types": "./dist/core/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -20,26 +20,43 @@
|
|
|
20
20
|
"require": "./dist/react/index.js"
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
|
-
"files": [
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
24
26
|
"scripts": {
|
|
25
27
|
"build": "tsc"
|
|
26
28
|
},
|
|
27
|
-
"keywords": [
|
|
29
|
+
"keywords": [
|
|
30
|
+
"coreiam",
|
|
31
|
+
"auth",
|
|
32
|
+
"saas",
|
|
33
|
+
"multi-tenant",
|
|
34
|
+
"nextjs"
|
|
35
|
+
],
|
|
28
36
|
"author": "coreIAM",
|
|
29
37
|
"license": "MIT",
|
|
30
|
-
"publishConfig": {
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
31
41
|
"peerDependencies": {
|
|
32
42
|
"next": ">=14",
|
|
33
43
|
"react": ">=18"
|
|
34
44
|
},
|
|
35
45
|
"peerDependenciesMeta": {
|
|
36
|
-
"next": {
|
|
37
|
-
|
|
46
|
+
"next": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"react": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
38
52
|
},
|
|
39
53
|
"devDependencies": {
|
|
40
54
|
"@types/react": "^19.2.7",
|
|
41
55
|
"next": "^16.2.11",
|
|
42
56
|
"react": "^19.2.7",
|
|
43
57
|
"typescript": "^5.0.0"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"jwt-decode": "^4.0.0"
|
|
44
61
|
}
|
|
45
62
|
}
|