@plank-cms/plank 0.15.1 → 0.15.2
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/admin/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
href="https://fonts.googleapis.com/css2?family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&display=swap"
|
|
13
13
|
rel="stylesheet"
|
|
14
14
|
/>
|
|
15
|
-
<script type="module" crossorigin src="/admin/assets/index-
|
|
15
|
+
<script type="module" crossorigin src="/admin/assets/index-NxBzeHGf.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="/admin/assets/index-DU5dGjXR.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { randomBytes } from "crypto";
|
|
|
7
7
|
import { resolve, join } from "path";
|
|
8
8
|
import fs from "fs-extra";
|
|
9
9
|
import { execa } from "execa";
|
|
10
|
-
var PACKAGE_VERSION = "0.15.
|
|
10
|
+
var PACKAGE_VERSION = "0.15.2";
|
|
11
11
|
function generateSecret() {
|
|
12
12
|
return randomBytes(32).toString("hex");
|
|
13
13
|
}
|
|
@@ -101,7 +101,7 @@ import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
|
101
101
|
async function start() {
|
|
102
102
|
config({ path: resolve2(process.cwd(), ".env") });
|
|
103
103
|
process.env.PLANK_ADMIN_DIST = join2(dirname(fileURLToPath(import.meta.url)), "admin");
|
|
104
|
-
const { start: startServer } = await import("./server-
|
|
104
|
+
const { start: startServer } = await import("./server-YYJGX3SJ.js");
|
|
105
105
|
await startServer();
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -2315,7 +2315,9 @@ var RATE_LIMIT_WINDOW_MS = 15 * 60 * 1e3;
|
|
|
2315
2315
|
var LOGIN_RATE_LIMIT_MAX = 10;
|
|
2316
2316
|
var LOGIN_2FA_RATE_LIMIT_MAX = 5;
|
|
2317
2317
|
var ACCESS_TOKEN_COOKIE = "plank_session";
|
|
2318
|
-
var ACCESS_TOKEN_EXPIRES_SECONDS = 60 *
|
|
2318
|
+
var ACCESS_TOKEN_EXPIRES_SECONDS = 60 * 60 * 24 * 30;
|
|
2319
|
+
var REFRESH_TOKEN_COOKIE = "plank_refresh";
|
|
2320
|
+
var REFRESH_TOKEN_EXPIRES_SECONDS = 60 * 60 * 24 * 30;
|
|
2319
2321
|
function isProduction() {
|
|
2320
2322
|
return process.env.NODE_ENV === "production";
|
|
2321
2323
|
}
|
|
@@ -2328,6 +2330,15 @@ function setSessionCookie(res, token) {
|
|
|
2328
2330
|
maxAge: ACCESS_TOKEN_EXPIRES_SECONDS * 1e3
|
|
2329
2331
|
});
|
|
2330
2332
|
}
|
|
2333
|
+
function setRefreshCookie(res, token) {
|
|
2334
|
+
res.cookie(REFRESH_TOKEN_COOKIE, token, {
|
|
2335
|
+
httpOnly: true,
|
|
2336
|
+
secure: isProduction(),
|
|
2337
|
+
sameSite: "lax",
|
|
2338
|
+
path: "/",
|
|
2339
|
+
maxAge: REFRESH_TOKEN_EXPIRES_SECONDS * 1e3
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2331
2342
|
function clearSessionCookie(res) {
|
|
2332
2343
|
res.clearCookie(ACCESS_TOKEN_COOKIE, {
|
|
2333
2344
|
httpOnly: true,
|
|
@@ -2336,6 +2347,14 @@ function clearSessionCookie(res) {
|
|
|
2336
2347
|
path: "/"
|
|
2337
2348
|
});
|
|
2338
2349
|
}
|
|
2350
|
+
function clearRefreshCookie(res) {
|
|
2351
|
+
res.clearCookie(REFRESH_TOKEN_COOKIE, {
|
|
2352
|
+
httpOnly: true,
|
|
2353
|
+
secure: isProduction(),
|
|
2354
|
+
sameSite: "lax",
|
|
2355
|
+
path: "/"
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2339
2358
|
async function consumeRateLimit(scope, rateKey, max) {
|
|
2340
2359
|
const resetAt = new Date(Date.now() + RATE_LIMIT_WINDOW_MS);
|
|
2341
2360
|
const { rows } = await pool_default.query(`INSERT INTO plank_auth_rate_limits (id, scope, rate_key, count, reset_at)
|
|
@@ -2361,7 +2380,11 @@ async function clearRateLimit(scope, rateKey) {
|
|
|
2361
2380
|
]);
|
|
2362
2381
|
}
|
|
2363
2382
|
function buildAccessToken(payload) {
|
|
2364
|
-
return jwt.sign(payload, process.env.PLANK_JWT_SECRET, { expiresIn: "
|
|
2383
|
+
return jwt.sign(payload, process.env.PLANK_JWT_SECRET, { expiresIn: "30d" });
|
|
2384
|
+
}
|
|
2385
|
+
function buildRefreshToken(payload) {
|
|
2386
|
+
const refreshPayload = { ...payload, type: "refresh" };
|
|
2387
|
+
return jwt.sign(refreshPayload, process.env.PLANK_JWT_SECRET, { expiresIn: "30d" });
|
|
2365
2388
|
}
|
|
2366
2389
|
function buildChallengeToken(payload) {
|
|
2367
2390
|
return jwt.sign(payload, process.env.PLANK_JWT_SECRET, { expiresIn: "5m" });
|
|
@@ -2426,6 +2449,7 @@ async function login(req, res) {
|
|
|
2426
2449
|
}
|
|
2427
2450
|
const auth = await buildAuthPayload(user);
|
|
2428
2451
|
setSessionCookie(res, auth.token);
|
|
2452
|
+
setRefreshCookie(res, buildRefreshToken({ sub: user.id, roleId: user.role_id, sv: user.session_version }));
|
|
2429
2453
|
res.json({
|
|
2430
2454
|
requiresTwoFactor: false,
|
|
2431
2455
|
user: auth.user
|
|
@@ -2494,6 +2518,7 @@ async function loginWithTwoFactor(req, res) {
|
|
|
2494
2518
|
await clearRateLimit("login-2fa", rateKey);
|
|
2495
2519
|
const auth = await buildAuthPayload(user);
|
|
2496
2520
|
setSessionCookie(res, auth.token);
|
|
2521
|
+
setRefreshCookie(res, buildRefreshToken({ sub: user.id, roleId: user.role_id, sv: user.session_version }));
|
|
2497
2522
|
res.json({
|
|
2498
2523
|
requiresTwoFactor: false,
|
|
2499
2524
|
user: auth.user
|
|
@@ -2512,6 +2537,7 @@ async function logout(req, res) {
|
|
|
2512
2537
|
}
|
|
2513
2538
|
}
|
|
2514
2539
|
clearSessionCookie(res);
|
|
2540
|
+
clearRefreshCookie(res);
|
|
2515
2541
|
const ip = req.ip ?? "unknown";
|
|
2516
2542
|
await pool_default.query("DELETE FROM plank_auth_rate_limits WHERE rate_key LIKE $1", [`${ip}:%`]);
|
|
2517
2543
|
res.status(204).end();
|
|
@@ -2553,6 +2579,24 @@ import { Router as Router2 } from "express";
|
|
|
2553
2579
|
|
|
2554
2580
|
// ../core/dist/middlewares/authenticate.js
|
|
2555
2581
|
import jwt2 from "jsonwebtoken";
|
|
2582
|
+
var ACCESS_TOKEN_COOKIE2 = "plank_session";
|
|
2583
|
+
var REFRESH_TOKEN_COOKIE2 = "plank_refresh";
|
|
2584
|
+
var ACCESS_TOKEN_EXPIRES_SECONDS2 = 60 * 60 * 24 * 30;
|
|
2585
|
+
function isProduction2() {
|
|
2586
|
+
return process.env.NODE_ENV === "production";
|
|
2587
|
+
}
|
|
2588
|
+
function setSessionCookie2(res, token) {
|
|
2589
|
+
res.cookie(ACCESS_TOKEN_COOKIE2, token, {
|
|
2590
|
+
httpOnly: true,
|
|
2591
|
+
secure: isProduction2(),
|
|
2592
|
+
sameSite: "lax",
|
|
2593
|
+
path: "/",
|
|
2594
|
+
maxAge: ACCESS_TOKEN_EXPIRES_SECONDS2 * 1e3
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
function buildAccessToken2(payload) {
|
|
2598
|
+
return jwt2.sign(payload, process.env.PLANK_JWT_SECRET, { expiresIn: "30d" });
|
|
2599
|
+
}
|
|
2556
2600
|
function cookieValue(raw, key) {
|
|
2557
2601
|
if (!raw)
|
|
2558
2602
|
return null;
|
|
@@ -2567,27 +2611,61 @@ function cookieValue(raw, key) {
|
|
|
2567
2611
|
async function authenticate(req, res, next) {
|
|
2568
2612
|
const header = req.headers.authorization;
|
|
2569
2613
|
const bearer = header?.startsWith("Bearer ") ? header.slice(7) : null;
|
|
2570
|
-
const cookieToken = cookieValue(req.headers.cookie,
|
|
2614
|
+
const cookieToken = cookieValue(req.headers.cookie, ACCESS_TOKEN_COOKIE2);
|
|
2615
|
+
const refreshToken = cookieValue(req.headers.cookie, REFRESH_TOKEN_COOKIE2);
|
|
2571
2616
|
const token = bearer ?? cookieToken;
|
|
2572
2617
|
if (!token) {
|
|
2573
2618
|
res.status(401).json({ error: "Unauthorized" });
|
|
2574
2619
|
return;
|
|
2575
2620
|
}
|
|
2576
|
-
|
|
2577
|
-
const payload = jwt2.verify(token, process.env.PLANK_JWT_SECRET);
|
|
2621
|
+
async function validateSession(payload) {
|
|
2578
2622
|
if (typeof payload.sv !== "number") {
|
|
2579
|
-
|
|
2580
|
-
return;
|
|
2623
|
+
return { ok: false };
|
|
2581
2624
|
}
|
|
2582
2625
|
const { rows } = await pool_default.query("SELECT session_version FROM plank_users WHERE id = $1", [payload.sub]);
|
|
2583
2626
|
if (!rows[0] || rows[0].session_version !== payload.sv) {
|
|
2627
|
+
return { ok: false };
|
|
2628
|
+
}
|
|
2629
|
+
return { ok: true, sessionVersion: rows[0].session_version };
|
|
2630
|
+
}
|
|
2631
|
+
try {
|
|
2632
|
+
const payload = jwt2.verify(token, process.env.PLANK_JWT_SECRET);
|
|
2633
|
+
const session = await validateSession(payload);
|
|
2634
|
+
if (!session.ok) {
|
|
2584
2635
|
res.status(401).json({ error: "Session has been revoked" });
|
|
2585
2636
|
return;
|
|
2586
2637
|
}
|
|
2587
2638
|
req.user = { id: payload.sub, roleId: payload.roleId };
|
|
2588
2639
|
next();
|
|
2589
2640
|
} catch {
|
|
2590
|
-
|
|
2641
|
+
if (bearer || !refreshToken) {
|
|
2642
|
+
res.status(401).json({ error: "Invalid or expired token" });
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
try {
|
|
2646
|
+
const refreshPayload = jwt2.verify(refreshToken, process.env.PLANK_JWT_SECRET);
|
|
2647
|
+
if (refreshPayload.type !== "refresh") {
|
|
2648
|
+
res.status(401).json({ error: "Invalid or expired token" });
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
const session = await validateSession(refreshPayload);
|
|
2652
|
+
if (!session.ok || typeof session.sessionVersion !== "number") {
|
|
2653
|
+
res.status(401).json({ error: "Session has been revoked" });
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
const renewed = buildAccessToken2({
|
|
2657
|
+
sub: refreshPayload.sub,
|
|
2658
|
+
roleId: refreshPayload.roleId,
|
|
2659
|
+
sv: session.sessionVersion
|
|
2660
|
+
});
|
|
2661
|
+
setSessionCookie2(res, renewed);
|
|
2662
|
+
req.user = { id: refreshPayload.sub, roleId: refreshPayload.roleId };
|
|
2663
|
+
next();
|
|
2664
|
+
return;
|
|
2665
|
+
} catch {
|
|
2666
|
+
res.status(401).json({ error: "Invalid or expired token" });
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2591
2669
|
}
|
|
2592
2670
|
}
|
|
2593
2671
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plank-cms/plank",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "Self-hosted headless CMS. Deploy in minutes on your own infrastructure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/fs-extra": "^11.0.4",
|
|
57
57
|
"tsup": "^8.5.0",
|
|
58
|
-
"@plank-cms/core": "0.15.
|
|
59
|
-
"@plank-cms/db": "0.15.
|
|
60
|
-
"@plank-cms/schema": "0.15.
|
|
58
|
+
"@plank-cms/core": "0.15.2",
|
|
59
|
+
"@plank-cms/db": "0.15.2",
|
|
60
|
+
"@plank-cms/schema": "0.15.2"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsup",
|