@dyrected/core 2.5.12 → 2.5.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-DnX9mNsh.d.cts +374 -0
- package/dist/app-DnX9mNsh.d.ts +374 -0
- package/dist/chunk-52T4RCFU.js +1972 -0
- package/dist/chunk-BEONZV6M.js +1951 -0
- package/dist/chunk-C4GEQ5RP.js +1993 -0
- package/dist/chunk-EH7G3LLL.js +2065 -0
- package/dist/chunk-F4ZC6TXR.js +2065 -0
- package/dist/chunk-G2QQLBHW.js +24 -0
- package/dist/chunk-IIY3E7Q7.js +1996 -0
- package/dist/chunk-JJN4J5NS.js +2069 -0
- package/dist/chunk-MRUHCNDM.js +1940 -0
- package/dist/chunk-NYUB4AHZ.js +1951 -0
- package/dist/chunk-OD4SLW7H.js +1930 -0
- package/dist/chunk-OE5MH3NB.js +2070 -0
- package/dist/chunk-VJWUG2XK.js +1951 -0
- package/dist/index.cjs +208 -42
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/password-ACI2YUGZ.js +8 -0
- package/dist/server.cjs +208 -42
- package/dist/server.d.cts +27 -2
- package/dist/server.d.ts +27 -2
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -908,23 +908,102 @@ function normalizeConfig(config) {
|
|
|
908
908
|
const globals = config?.globals || [];
|
|
909
909
|
const needsAudit = collections.some((col) => col.audit);
|
|
910
910
|
const normalizedCollections = collections.map((col) => {
|
|
911
|
-
|
|
911
|
+
let fields = col.fields || [];
|
|
912
912
|
const existingFieldNames = new Set(fields.map((f) => f.name));
|
|
913
|
-
|
|
913
|
+
if (col.auth) {
|
|
914
|
+
if (!existingFieldNames.has("email")) {
|
|
915
|
+
fields = [
|
|
916
|
+
...fields,
|
|
917
|
+
{
|
|
918
|
+
name: "email",
|
|
919
|
+
type: "email",
|
|
920
|
+
label: "Email",
|
|
921
|
+
required: true,
|
|
922
|
+
unique: true,
|
|
923
|
+
promoted: true,
|
|
924
|
+
access: {
|
|
925
|
+
update: "!id"
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
];
|
|
929
|
+
}
|
|
930
|
+
if (!existingFieldNames.has("password")) {
|
|
931
|
+
fields = [
|
|
932
|
+
...fields,
|
|
933
|
+
{
|
|
934
|
+
name: "password",
|
|
935
|
+
type: "text",
|
|
936
|
+
label: "Password",
|
|
937
|
+
required: true,
|
|
938
|
+
access: {
|
|
939
|
+
update: "!id || user.id == id"
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
];
|
|
943
|
+
}
|
|
944
|
+
if (!existingFieldNames.has("roles")) {
|
|
945
|
+
fields = [
|
|
946
|
+
...fields,
|
|
947
|
+
{
|
|
948
|
+
name: "roles",
|
|
949
|
+
type: "select",
|
|
950
|
+
label: "Roles",
|
|
951
|
+
defaultValue: [],
|
|
952
|
+
options: [
|
|
953
|
+
{ value: "admin", label: "Admin" },
|
|
954
|
+
{ value: "editor", label: "Editor" },
|
|
955
|
+
{ value: "viewer", label: "Viewer" }
|
|
956
|
+
],
|
|
957
|
+
access: {
|
|
958
|
+
update: "user.roles && 'admin' in user.roles"
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
];
|
|
962
|
+
}
|
|
963
|
+
fields = fields.map((field) => {
|
|
964
|
+
if (field.name === "email") {
|
|
965
|
+
return {
|
|
966
|
+
...field,
|
|
967
|
+
access: {
|
|
968
|
+
...field.access || {},
|
|
969
|
+
update: "!id"
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
if (field.name === "password") {
|
|
974
|
+
return {
|
|
975
|
+
...field,
|
|
976
|
+
admin: { ...field.admin || {} },
|
|
977
|
+
access: {
|
|
978
|
+
...field.access || {},
|
|
979
|
+
update: "!id || user.id == id"
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
if (field.name === "roles") {
|
|
984
|
+
return {
|
|
985
|
+
...field,
|
|
986
|
+
access: {
|
|
987
|
+
...field.access || {},
|
|
988
|
+
// Must be an admin; cannot edit own roles (no self-elevation).
|
|
989
|
+
update: "user.roles && 'admin' in user.roles && user.id != id"
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
return field;
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
const updatedFieldNames = new Set(fields.map((f) => f.name));
|
|
997
|
+
const fieldsToInject = SYSTEM_FIELDS.filter((f) => !updatedFieldNames.has(f.name));
|
|
914
998
|
return {
|
|
915
999
|
...col,
|
|
916
1000
|
fields: [...fields, ...fieldsToInject]
|
|
917
1001
|
};
|
|
918
1002
|
});
|
|
919
|
-
const hasAuditCollection = normalizedCollections.some(
|
|
920
|
-
(col) => col.slug === AUDIT_COLLECTION_SLUG
|
|
921
|
-
);
|
|
1003
|
+
const hasAuditCollection = normalizedCollections.some((col) => col.slug === AUDIT_COLLECTION_SLUG);
|
|
922
1004
|
return {
|
|
923
1005
|
...config,
|
|
924
|
-
collections: [
|
|
925
|
-
...normalizedCollections,
|
|
926
|
-
...needsAudit && !hasAuditCollection ? [AUDIT_COLLECTION] : []
|
|
927
|
-
],
|
|
1006
|
+
collections: [...normalizedCollections, ...needsAudit && !hasAuditCollection ? [AUDIT_COLLECTION] : []],
|
|
928
1007
|
globals
|
|
929
1008
|
};
|
|
930
1009
|
}
|
|
@@ -1295,6 +1374,26 @@ var AuditService = class {
|
|
|
1295
1374
|
}
|
|
1296
1375
|
};
|
|
1297
1376
|
|
|
1377
|
+
// src/auth/password.ts
|
|
1378
|
+
var import_node_util = require("util");
|
|
1379
|
+
var import_node_crypto = require("crypto");
|
|
1380
|
+
var scryptAsync = (0, import_node_util.promisify)(import_node_crypto.scrypt);
|
|
1381
|
+
var SALT_LEN = 16;
|
|
1382
|
+
var KEY_LEN = 64;
|
|
1383
|
+
async function hashPassword(plain) {
|
|
1384
|
+
const salt = (0, import_node_crypto.randomBytes)(SALT_LEN).toString("hex");
|
|
1385
|
+
const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
|
|
1386
|
+
return `${salt}:${derivedKey.toString("hex")}`;
|
|
1387
|
+
}
|
|
1388
|
+
async function verifyPassword(plain, stored) {
|
|
1389
|
+
const [salt, storedHash] = stored.split(":");
|
|
1390
|
+
if (!salt || !storedHash) return false;
|
|
1391
|
+
const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
|
|
1392
|
+
const storedBuffer = Buffer.from(storedHash, "hex");
|
|
1393
|
+
if (derivedKey.length !== storedBuffer.length) return false;
|
|
1394
|
+
return (0, import_node_crypto.timingSafeEqual)(derivedKey, storedBuffer);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1298
1397
|
// src/controllers/collection.controller.ts
|
|
1299
1398
|
var CollectionController = class {
|
|
1300
1399
|
collection;
|
|
@@ -1384,6 +1483,9 @@ var CollectionController = class {
|
|
|
1384
1483
|
createdBy: user?.sub ?? null,
|
|
1385
1484
|
updatedBy: user?.sub ?? null
|
|
1386
1485
|
};
|
|
1486
|
+
if (this.collection.auth && data.password) {
|
|
1487
|
+
data.password = await hashPassword(data.password);
|
|
1488
|
+
}
|
|
1387
1489
|
const doc = await db.create({ collection: this.collection.slug, data });
|
|
1388
1490
|
if (this.collection.audit && db) {
|
|
1389
1491
|
AuditService.log(db, {
|
|
@@ -1443,11 +1545,16 @@ var CollectionController = class {
|
|
|
1443
1545
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
1444
1546
|
const body = await c.req.json();
|
|
1445
1547
|
const user = c.get("user");
|
|
1446
|
-
const data = {
|
|
1447
|
-
|
|
1548
|
+
const data = { ...body };
|
|
1549
|
+
if (this.collection.auth) {
|
|
1550
|
+
delete data.password;
|
|
1551
|
+
delete data.oldPassword;
|
|
1552
|
+
delete data.confirmPassword;
|
|
1553
|
+
}
|
|
1554
|
+
Object.assign(data, {
|
|
1448
1555
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1449
1556
|
updatedBy: user?.sub ?? null
|
|
1450
|
-
};
|
|
1557
|
+
});
|
|
1451
1558
|
let before = null;
|
|
1452
1559
|
if (this.collection.audit) {
|
|
1453
1560
|
before = await db.findOne({ collection: this.collection.slug, id });
|
|
@@ -1465,6 +1572,77 @@ var CollectionController = class {
|
|
|
1465
1572
|
}
|
|
1466
1573
|
return c.json(doc);
|
|
1467
1574
|
}
|
|
1575
|
+
/**
|
|
1576
|
+
* POST /api/collections/:slug/:id/change-password
|
|
1577
|
+
*
|
|
1578
|
+
* Dedicated endpoint for password changes. Requires the caller to supply:
|
|
1579
|
+
* { oldPassword, newPassword, confirmPassword }
|
|
1580
|
+
*
|
|
1581
|
+
* Rules:
|
|
1582
|
+
* - Only the account owner or an admin may change the password.
|
|
1583
|
+
* - Non-admin callers MUST provide a valid oldPassword.
|
|
1584
|
+
* - newPassword and confirmPassword must match.
|
|
1585
|
+
*/
|
|
1586
|
+
async changePassword(c) {
|
|
1587
|
+
const config = c.get("config");
|
|
1588
|
+
const db = config.db;
|
|
1589
|
+
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1590
|
+
if (!this.collection.auth) {
|
|
1591
|
+
return c.json({ message: "This collection does not support authentication" }, 400);
|
|
1592
|
+
}
|
|
1593
|
+
const id = c.req.param("id");
|
|
1594
|
+
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
1595
|
+
const user = c.get("user");
|
|
1596
|
+
if (!user) return c.json({ message: "Authentication required" }, 401);
|
|
1597
|
+
const body = await c.req.json().catch(() => null);
|
|
1598
|
+
const { oldPassword, newPassword, confirmPassword } = body ?? {};
|
|
1599
|
+
if (!newPassword) {
|
|
1600
|
+
return c.json({ message: "newPassword is required" }, 400);
|
|
1601
|
+
}
|
|
1602
|
+
if (newPassword !== confirmPassword) {
|
|
1603
|
+
return c.json({ message: "Passwords do not match" }, 400);
|
|
1604
|
+
}
|
|
1605
|
+
if (newPassword.length < 8) {
|
|
1606
|
+
return c.json({ message: "Password must be at least 8 characters" }, 400);
|
|
1607
|
+
}
|
|
1608
|
+
const isAdmin = Array.isArray(user.roles) && user.roles.includes("admin");
|
|
1609
|
+
const isSelf = user.sub === id;
|
|
1610
|
+
if (!isAdmin && !isSelf) {
|
|
1611
|
+
return c.json({ message: "You are not authorised to change this password" }, 403);
|
|
1612
|
+
}
|
|
1613
|
+
if (!isAdmin) {
|
|
1614
|
+
if (!oldPassword) {
|
|
1615
|
+
return c.json({ message: "Current password is required" }, 400);
|
|
1616
|
+
}
|
|
1617
|
+
const existing = await db.findOne({ collection: this.collection.slug, id });
|
|
1618
|
+
if (!existing) return c.json({ message: "User not found" }, 404);
|
|
1619
|
+
const valid = await verifyPassword(oldPassword, existing.password);
|
|
1620
|
+
if (!valid) {
|
|
1621
|
+
return c.json({ message: "Invalid current password" }, 400);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
const hashed = await hashPassword(newPassword);
|
|
1625
|
+
const doc = await db.update({
|
|
1626
|
+
collection: this.collection.slug,
|
|
1627
|
+
id,
|
|
1628
|
+
data: {
|
|
1629
|
+
password: hashed,
|
|
1630
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1631
|
+
updatedBy: user.sub
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
if (this.collection.audit) {
|
|
1635
|
+
AuditService.log(db, {
|
|
1636
|
+
operation: "update",
|
|
1637
|
+
collection: this.collection.slug,
|
|
1638
|
+
documentId: id,
|
|
1639
|
+
user: { id: user.sub, collection: user.collection, email: user.email },
|
|
1640
|
+
before: null,
|
|
1641
|
+
after: { id }
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
return c.json({ success: true, message: "Password updated successfully" });
|
|
1645
|
+
}
|
|
1468
1646
|
async delete(c) {
|
|
1469
1647
|
const config = c.get("config");
|
|
1470
1648
|
const db = config.db;
|
|
@@ -1756,26 +1934,6 @@ var MediaController = class {
|
|
|
1756
1934
|
}
|
|
1757
1935
|
};
|
|
1758
1936
|
|
|
1759
|
-
// src/auth/password.ts
|
|
1760
|
-
var import_node_util = require("util");
|
|
1761
|
-
var import_node_crypto = require("crypto");
|
|
1762
|
-
var scryptAsync = (0, import_node_util.promisify)(import_node_crypto.scrypt);
|
|
1763
|
-
var SALT_LEN = 16;
|
|
1764
|
-
var KEY_LEN = 64;
|
|
1765
|
-
async function hashPassword(plain) {
|
|
1766
|
-
const salt = (0, import_node_crypto.randomBytes)(SALT_LEN).toString("hex");
|
|
1767
|
-
const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
|
|
1768
|
-
return `${salt}:${derivedKey.toString("hex")}`;
|
|
1769
|
-
}
|
|
1770
|
-
async function verifyPassword(plain, stored) {
|
|
1771
|
-
const [salt, storedHash] = stored.split(":");
|
|
1772
|
-
if (!salt || !storedHash) return false;
|
|
1773
|
-
const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
|
|
1774
|
-
const storedBuffer = Buffer.from(storedHash, "hex");
|
|
1775
|
-
if (derivedKey.length !== storedBuffer.length) return false;
|
|
1776
|
-
return (0, import_node_crypto.timingSafeEqual)(derivedKey, storedBuffer);
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
1937
|
// src/auth/token.ts
|
|
1780
1938
|
var import_jose = require("jose");
|
|
1781
1939
|
var import_node_util2 = require("util");
|
|
@@ -2685,14 +2843,19 @@ function registerRoutes(app, config) {
|
|
|
2685
2843
|
}
|
|
2686
2844
|
return true;
|
|
2687
2845
|
};
|
|
2846
|
+
const serializeAccess = async (access) => {
|
|
2847
|
+
if (typeof access === "string") return access;
|
|
2848
|
+
if (typeof access === "boolean") return access;
|
|
2849
|
+
return resolveAccess(access);
|
|
2850
|
+
};
|
|
2688
2851
|
const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
2689
2852
|
slug: col.slug,
|
|
2690
2853
|
labels: col.labels,
|
|
2691
2854
|
access: {
|
|
2692
|
-
read: await
|
|
2693
|
-
create: await
|
|
2694
|
-
update: await
|
|
2695
|
-
delete: await
|
|
2855
|
+
read: await serializeAccess(col.access?.read),
|
|
2856
|
+
create: await serializeAccess(col.access?.create),
|
|
2857
|
+
update: await serializeAccess(col.access?.update),
|
|
2858
|
+
delete: await serializeAccess(col.access?.delete)
|
|
2696
2859
|
},
|
|
2697
2860
|
fields: await Promise.all(col.fields.map(async (f) => ({
|
|
2698
2861
|
name: f.name,
|
|
@@ -2707,8 +2870,8 @@ function registerRoutes(app, config) {
|
|
|
2707
2870
|
blocks: f.blocks,
|
|
2708
2871
|
admin: f.admin,
|
|
2709
2872
|
access: {
|
|
2710
|
-
read: await
|
|
2711
|
-
update: await
|
|
2873
|
+
read: await serializeAccess(f.access?.read),
|
|
2874
|
+
update: await serializeAccess(f.access?.update)
|
|
2712
2875
|
}
|
|
2713
2876
|
}))),
|
|
2714
2877
|
upload: !!col.upload,
|
|
@@ -2719,8 +2882,8 @@ function registerRoutes(app, config) {
|
|
|
2719
2882
|
slug: glb.slug,
|
|
2720
2883
|
label: glb.label,
|
|
2721
2884
|
access: {
|
|
2722
|
-
read: await
|
|
2723
|
-
update: await
|
|
2885
|
+
read: await serializeAccess(glb.access?.read),
|
|
2886
|
+
update: await serializeAccess(glb.access?.update)
|
|
2724
2887
|
},
|
|
2725
2888
|
fields: await Promise.all(glb.fields.map(async (f) => ({
|
|
2726
2889
|
name: f.name,
|
|
@@ -2735,8 +2898,8 @@ function registerRoutes(app, config) {
|
|
|
2735
2898
|
blocks: f.blocks,
|
|
2736
2899
|
admin: f.admin,
|
|
2737
2900
|
access: {
|
|
2738
|
-
read: await
|
|
2739
|
-
update: await
|
|
2901
|
+
read: await serializeAccess(f.access?.read),
|
|
2902
|
+
update: await serializeAccess(f.access?.update)
|
|
2740
2903
|
}
|
|
2741
2904
|
}))),
|
|
2742
2905
|
admin: glb.admin
|
|
@@ -2798,6 +2961,9 @@ function registerRoutes(app, config) {
|
|
|
2798
2961
|
app.patch(`${path}/:id`, accessGate(collection, "update"), (c) => controller.update(c));
|
|
2799
2962
|
app.delete(`${path}/:id`, accessGate(collection, "delete"), (c) => controller.delete(c));
|
|
2800
2963
|
app.post(`${path}/seed`, (c) => controller.seed(c));
|
|
2964
|
+
if (collection.auth) {
|
|
2965
|
+
app.post(`${path}/:id/change-password`, requireAuth(), (c) => controller.changePassword(c));
|
|
2966
|
+
}
|
|
2801
2967
|
}
|
|
2802
2968
|
for (const global of config.globals) {
|
|
2803
2969
|
const path = `/api/globals/${global.slug}`;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-
|
|
2
|
-
export { A as AccessFunction, a as AdminConfig, B as Block, b as DatabaseAdapter, c as DyrectedContext, F as Field, d as FieldHook, e as FieldType, f as FileData, H as HookFunction, I as ImageService, P as PaginatedResult, S as StorageAdapter, U as UploadConfig, g as createDyrectedApp } from './app-
|
|
1
|
+
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-DnX9mNsh.cjs';
|
|
2
|
+
export { A as AccessFunction, a as AdminConfig, B as Block, b as DatabaseAdapter, c as DyrectedContext, F as Field, d as FieldHook, e as FieldType, f as FileData, H as HookFunction, I as ImageService, P as PaginatedResult, S as StorageAdapter, U as UploadConfig, g as createDyrectedApp } from './app-DnX9mNsh.cjs';
|
|
3
3
|
import 'hono/types';
|
|
4
4
|
import 'hono';
|
|
5
5
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-
|
|
2
|
-
export { A as AccessFunction, a as AdminConfig, B as Block, b as DatabaseAdapter, c as DyrectedContext, F as Field, d as FieldHook, e as FieldType, f as FileData, H as HookFunction, I as ImageService, P as PaginatedResult, S as StorageAdapter, U as UploadConfig, g as createDyrectedApp } from './app-
|
|
1
|
+
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-DnX9mNsh.js';
|
|
2
|
+
export { A as AccessFunction, a as AdminConfig, B as Block, b as DatabaseAdapter, c as DyrectedContext, F as Field, d as FieldHook, e as FieldType, f as FileData, H as HookFunction, I as ImageService, P as PaginatedResult, S as StorageAdapter, U as UploadConfig, g as createDyrectedApp } from './app-DnX9mNsh.js';
|
|
3
3
|
import 'hono/types';
|
|
4
4
|
import 'hono';
|
|
5
5
|
|
package/dist/index.js
CHANGED