@easy-cms/core 1.17.0 → 1.19.0
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/{chunk-LPIABPBA.js → chunk-FZCQXH5M.js} +370 -134
- package/dist/{config-CWTw3zFm.d.ts → config-U1mRwD4t.d.ts} +54 -2
- package/dist/{handler-DzAqr9gY.d.ts → handler-D2y4WSSM.d.ts} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +3 -1
- package/dist/{plugin-BYaJUVpz.d.ts → plugin-DRV5HA4a.d.ts} +1 -1
- package/dist/plugin.d.ts +2 -2
- package/package.json +1 -1
|
@@ -45,6 +45,99 @@ var isStaff = ({ user }) => user !== null && user.member !== true;
|
|
|
45
45
|
var isSignedIn = ({ user }) => user !== null;
|
|
46
46
|
var isAdmin = ({ user }) => user?.role === "admin" && user.scoped !== true;
|
|
47
47
|
|
|
48
|
+
// src/auth/tokens.ts
|
|
49
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
50
|
+
function newToken() {
|
|
51
|
+
return randomBytes(32).toString("base64url");
|
|
52
|
+
}
|
|
53
|
+
function hashToken(token) {
|
|
54
|
+
return createHash("sha256").update(token).digest("hex");
|
|
55
|
+
}
|
|
56
|
+
function hmac(secret, value) {
|
|
57
|
+
return createHmac("sha256", secret).update(value).digest("base64url");
|
|
58
|
+
}
|
|
59
|
+
function secretsOf(config) {
|
|
60
|
+
return [config.secret, ...config.previousSecrets ?? []];
|
|
61
|
+
}
|
|
62
|
+
function signedWith(secrets, value, signature) {
|
|
63
|
+
return [secrets].flat().some((secret) => safeEqual(signature, hmac(secret, value)));
|
|
64
|
+
}
|
|
65
|
+
function signToken(secret, token) {
|
|
66
|
+
return `${token}.${hmac(secret, `session:${token}`)}`;
|
|
67
|
+
}
|
|
68
|
+
function unsignToken(secrets, value) {
|
|
69
|
+
const dot = value.lastIndexOf(".");
|
|
70
|
+
if (dot <= 0) return void 0;
|
|
71
|
+
const token = value.slice(0, dot);
|
|
72
|
+
return signedWith(secrets, `session:${token}`, value.slice(dot + 1)) ? token : void 0;
|
|
73
|
+
}
|
|
74
|
+
function csrfToken(secret, token) {
|
|
75
|
+
return hmac(secret, `csrf:${token}`);
|
|
76
|
+
}
|
|
77
|
+
function csrfMatches(secrets, token, given) {
|
|
78
|
+
return signedWith(secrets, `csrf:${token}`, given);
|
|
79
|
+
}
|
|
80
|
+
function safeEqual(a, b) {
|
|
81
|
+
const left = Buffer.from(a);
|
|
82
|
+
const right = Buffer.from(b);
|
|
83
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
84
|
+
}
|
|
85
|
+
function signPreviewToken(secret, target, expiresAt, user) {
|
|
86
|
+
const payload = Buffer.from(
|
|
87
|
+
JSON.stringify({
|
|
88
|
+
..."global" in target ? { g: target.global } : { c: target.collection, i: target.id },
|
|
89
|
+
e: expiresAt,
|
|
90
|
+
...user !== void 0 ? { u: user } : {}
|
|
91
|
+
})
|
|
92
|
+
).toString("base64url");
|
|
93
|
+
return `${payload}.${hmac(secret, `preview:${payload}`)}`;
|
|
94
|
+
}
|
|
95
|
+
function verifyPreviewToken(secrets, token, now = Date.now()) {
|
|
96
|
+
const dot = token.lastIndexOf(".");
|
|
97
|
+
if (dot <= 0) return null;
|
|
98
|
+
const payload = token.slice(0, dot);
|
|
99
|
+
if (!signedWith(secrets, `preview:${payload}`, token.slice(dot + 1))) return null;
|
|
100
|
+
try {
|
|
101
|
+
const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
102
|
+
if (typeof data.e !== "number" || data.e <= now) return null;
|
|
103
|
+
const user = typeof data.u === "string" ? { user: data.u } : {};
|
|
104
|
+
if (typeof data.g === "string") return { global: data.g, expiresAt: data.e, ...user };
|
|
105
|
+
if (typeof data.c === "string" && typeof data.i === "string")
|
|
106
|
+
return { collection: data.c, id: data.i, expiresAt: data.e, ...user };
|
|
107
|
+
return null;
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function passwordFingerprint(passwordHash) {
|
|
113
|
+
return createHash("sha256").update(typeof passwordHash === "string" ? passwordHash : "no-password").digest("base64url").slice(0, 22);
|
|
114
|
+
}
|
|
115
|
+
function signPasswordToken(secret, args) {
|
|
116
|
+
const payload = Buffer.from(
|
|
117
|
+
JSON.stringify({ u: args.userId, p: args.purpose, e: args.expiresAt })
|
|
118
|
+
).toString("base64url");
|
|
119
|
+
return `${payload}.${hmac(secret, `password:${payload}:${args.fingerprint}`)}`;
|
|
120
|
+
}
|
|
121
|
+
function readPasswordToken(token) {
|
|
122
|
+
const dot = token.lastIndexOf(".");
|
|
123
|
+
if (dot <= 0) return null;
|
|
124
|
+
const payload = token.slice(0, dot);
|
|
125
|
+
try {
|
|
126
|
+
const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
127
|
+
if (typeof data.u !== "string" || typeof data.e !== "number") return null;
|
|
128
|
+
if (data.p !== "reset" && data.p !== "invite" && data.p !== "verify") return null;
|
|
129
|
+
return { userId: data.u, purpose: data.p, expiresAt: data.e, payload };
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function passwordTokenMatches(secrets, token, fingerprint) {
|
|
135
|
+
const dot = token.lastIndexOf(".");
|
|
136
|
+
if (dot <= 0) return false;
|
|
137
|
+
const payload = token.slice(0, dot);
|
|
138
|
+
return signedWith(secrets, `password:${payload}:${fingerprint}`, token.slice(dot + 1));
|
|
139
|
+
}
|
|
140
|
+
|
|
48
141
|
// src/builtins.ts
|
|
49
142
|
var USERS = "users";
|
|
50
143
|
var MEDIA = "media";
|
|
@@ -61,6 +154,7 @@ var ROLES = "user-roles";
|
|
|
61
154
|
var USER_IDENTITIES = "user-identities";
|
|
62
155
|
var AUDIT_LOGS = "audit-logs";
|
|
63
156
|
var OUTBOX = "outbox";
|
|
157
|
+
var RATE_LIMITS = "rate-limits";
|
|
64
158
|
var INTERNAL_COLLECTIONS = /* @__PURE__ */ new Set([
|
|
65
159
|
SESSIONS,
|
|
66
160
|
LOGIN_ATTEMPTS,
|
|
@@ -73,7 +167,8 @@ var INTERNAL_COLLECTIONS = /* @__PURE__ */ new Set([
|
|
|
73
167
|
ROLES,
|
|
74
168
|
USER_IDENTITIES,
|
|
75
169
|
AUDIT_LOGS,
|
|
76
|
-
OUTBOX
|
|
170
|
+
OUTBOX,
|
|
171
|
+
RATE_LIMITS
|
|
77
172
|
]);
|
|
78
173
|
var DEFAULT_ROLES = ["admin", "editor"];
|
|
79
174
|
var CREATED_BY_FIELD = {
|
|
@@ -547,6 +642,17 @@ var outboxCollection = {
|
|
|
547
642
|
{ name: "error", type: "text" }
|
|
548
643
|
]
|
|
549
644
|
};
|
|
645
|
+
var rateLimitsCollection = {
|
|
646
|
+
slug: RATE_LIMITS,
|
|
647
|
+
access: { read: nobody, create: nobody, update: nobody, delete: nobody },
|
|
648
|
+
fields: [
|
|
649
|
+
// `<anonymous|user|apiKey>:<id or HMAC of the IP>:<window number>`
|
|
650
|
+
{ name: "key", type: "text", required: true, unique: true },
|
|
651
|
+
{ name: "count", type: "number", required: true },
|
|
652
|
+
// When the window is over, for clearing old rows.
|
|
653
|
+
{ name: "expiresAt", type: "text", required: true, index: true }
|
|
654
|
+
]
|
|
655
|
+
};
|
|
550
656
|
var emailDeliveriesCollection = {
|
|
551
657
|
slug: EMAIL_DELIVERIES,
|
|
552
658
|
access: { read: nobody, create: nobody, update: nobody, delete: nobody },
|
|
@@ -563,7 +669,7 @@ var emailDeliveriesCollection = {
|
|
|
563
669
|
|
|
564
670
|
// src/audit.ts
|
|
565
671
|
import { AsyncLocalStorage } from "async_hooks";
|
|
566
|
-
import { createHmac } from "crypto";
|
|
672
|
+
import { createHmac as createHmac2 } from "crypto";
|
|
567
673
|
var DEFAULT_AUDIT_KEEP = 365;
|
|
568
674
|
var DEFAULT_FAILED_LOGINS = 20;
|
|
569
675
|
var MAX_VALUE = 500;
|
|
@@ -778,7 +884,7 @@ var AuditLog = class {
|
|
|
778
884
|
});
|
|
779
885
|
for (const row of result.docs) {
|
|
780
886
|
checked++;
|
|
781
|
-
if (
|
|
887
|
+
if (!this.signer(row)) invalid.push(row.id);
|
|
782
888
|
const id = Number(row.id);
|
|
783
889
|
if (previous !== void 0 && Number.isInteger(id) && id > previous + 1)
|
|
784
890
|
gaps += id - previous - 1;
|
|
@@ -797,6 +903,49 @@ var AuditLog = class {
|
|
|
797
903
|
);
|
|
798
904
|
return verification;
|
|
799
905
|
}
|
|
906
|
+
/** The secret an entry was signed with: the current one, a previous one, or none (changed). */
|
|
907
|
+
signer(row) {
|
|
908
|
+
return secretsOf(this.cms.config).find((secret) => row.signature === this.sign(row, secret));
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* After `secret` changed: signs again, with the current secret, the entries signed with one of
|
|
912
|
+
* `previousSecrets`, so they still verify once it is removed. Entries no secret verifies were
|
|
913
|
+
* changed: they are left as they are, and reported. Records `audit.resign`.
|
|
914
|
+
*/
|
|
915
|
+
async resign() {
|
|
916
|
+
const invalid = [];
|
|
917
|
+
let checked = 0;
|
|
918
|
+
let resigned = 0;
|
|
919
|
+
for (let page = 1; ; page++) {
|
|
920
|
+
const result = await this.cms.db.find({
|
|
921
|
+
collection: AUDIT_LOGS,
|
|
922
|
+
sort: ["id"],
|
|
923
|
+
limit: 500,
|
|
924
|
+
page
|
|
925
|
+
});
|
|
926
|
+
for (const row of result.docs) {
|
|
927
|
+
checked++;
|
|
928
|
+
const secret = this.signer(row);
|
|
929
|
+
if (secret === void 0) invalid.push(row.id);
|
|
930
|
+
else if (secret !== this.cms.config.secret) {
|
|
931
|
+
const { id, ...data } = row;
|
|
932
|
+
await this.cms.db.update({
|
|
933
|
+
collection: AUDIT_LOGS,
|
|
934
|
+
id,
|
|
935
|
+
data: { ...data, signature: this.sign(row) }
|
|
936
|
+
});
|
|
937
|
+
resigned++;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (page >= result.totalPages) break;
|
|
941
|
+
}
|
|
942
|
+
await this.record({
|
|
943
|
+
action: "audit.resign",
|
|
944
|
+
target: "audit",
|
|
945
|
+
detail: { checked, resigned, invalid: invalid.length }
|
|
946
|
+
});
|
|
947
|
+
return { checked, resigned, invalid };
|
|
948
|
+
}
|
|
800
949
|
/**
|
|
801
950
|
* Daily upkeep from `runJobs`: deletes entries older than `keep` days, then verifies the log.
|
|
802
951
|
* Runs when the last check is more than a day old.
|
|
@@ -847,11 +996,11 @@ var AuditLog = class {
|
|
|
847
996
|
const scope = this.settings.scope;
|
|
848
997
|
return (scope && context ? scope({ context, user }) : null) ?? null;
|
|
849
998
|
}
|
|
850
|
-
sign(row) {
|
|
999
|
+
sign(row, secret = this.cms.config.secret) {
|
|
851
1000
|
const values = SIGNED.map((key) => row[key] ?? null);
|
|
852
1001
|
if (typeof row.scope === "string" && row.scope) values.push(row.scope);
|
|
853
1002
|
const payload = stable(values);
|
|
854
|
-
return
|
|
1003
|
+
return createHmac2("sha256", secret).update(`audit:${payload}`).digest("base64url");
|
|
855
1004
|
}
|
|
856
1005
|
async lastVerification() {
|
|
857
1006
|
const { docs } = await this.cms.db.find({
|
|
@@ -903,7 +1052,7 @@ function toEntry(row) {
|
|
|
903
1052
|
}
|
|
904
1053
|
|
|
905
1054
|
// src/api-keys.ts
|
|
906
|
-
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
1055
|
+
import { createHash as createHash2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
907
1056
|
var API_KEYS = "api-keys";
|
|
908
1057
|
var API_KEY_OPERATIONS = ["read", "create", "update", "delete", "publish"];
|
|
909
1058
|
var API_KEY_GLOBAL_OPERATIONS = ["read", "update", "publish"];
|
|
@@ -913,12 +1062,12 @@ function isApiKey(token) {
|
|
|
913
1062
|
return token.startsWith("ecms_");
|
|
914
1063
|
}
|
|
915
1064
|
function newApiKey() {
|
|
916
|
-
const prefix =
|
|
917
|
-
const secret =
|
|
1065
|
+
const prefix = randomBytes2(4).toString("hex");
|
|
1066
|
+
const secret = randomBytes2(32).toString("base64url");
|
|
918
1067
|
return { key: `ecms_${prefix}_${secret}`, prefix, hash: hashSecret(secret) };
|
|
919
1068
|
}
|
|
920
1069
|
function hashSecret(secret) {
|
|
921
|
-
return
|
|
1070
|
+
return createHash2("sha256").update(secret).digest("hex");
|
|
922
1071
|
}
|
|
923
1072
|
function parseApiKey(token) {
|
|
924
1073
|
const match = KEY_PATTERN.exec(token);
|
|
@@ -928,7 +1077,7 @@ function secretMatches(secret, hash) {
|
|
|
928
1077
|
if (typeof hash !== "string") return false;
|
|
929
1078
|
const a = Buffer.from(hashSecret(secret), "hex");
|
|
930
1079
|
const b = Buffer.from(hash, "hex");
|
|
931
|
-
return a.length === b.length &&
|
|
1080
|
+
return a.length === b.length && timingSafeEqual2(a, b);
|
|
932
1081
|
}
|
|
933
1082
|
function keyAllows(user, target, operation) {
|
|
934
1083
|
const key = user?.apiKey;
|
|
@@ -1035,20 +1184,23 @@ function cleanPermissions(value, known) {
|
|
|
1035
1184
|
}
|
|
1036
1185
|
|
|
1037
1186
|
// src/spam.ts
|
|
1038
|
-
import { createHmac as
|
|
1187
|
+
import { createHmac as createHmac3, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
1039
1188
|
var MAX_AGE = 24 * 36e5;
|
|
1040
|
-
var sign = (secret, value) =>
|
|
1189
|
+
var sign = (secret, value) => createHmac3("sha256", secret).update(value).digest("base64url");
|
|
1041
1190
|
function formToken(secret, purpose, now = Date.now()) {
|
|
1042
1191
|
return `${now}.${sign(secret, `${purpose}:${now}`)}`;
|
|
1043
1192
|
}
|
|
1044
|
-
function checkFormToken(
|
|
1193
|
+
function checkFormToken(secrets, purpose, token, minTime, now = Date.now()) {
|
|
1045
1194
|
if (typeof token !== "string") return "invalid";
|
|
1046
1195
|
const [time, signature] = token.split(".");
|
|
1047
1196
|
const issued = Number(time);
|
|
1048
1197
|
if (!Number.isFinite(issued) || !signature) return "invalid";
|
|
1049
|
-
const expected = Buffer.from(sign(secret, `${purpose}:${issued}`));
|
|
1050
1198
|
const given = Buffer.from(signature);
|
|
1051
|
-
|
|
1199
|
+
const genuine = [secrets].flat().some((secret) => {
|
|
1200
|
+
const expected = Buffer.from(sign(secret, `${purpose}:${issued}`));
|
|
1201
|
+
return expected.length === given.length && timingSafeEqual3(expected, given);
|
|
1202
|
+
});
|
|
1203
|
+
if (!genuine) return "invalid";
|
|
1052
1204
|
if (now - issued < minTime) return "too-fast";
|
|
1053
1205
|
if (now - issued > MAX_AGE) return "expired";
|
|
1054
1206
|
return "ok";
|
|
@@ -1172,7 +1324,7 @@ var DEFAULT_PASSWORD_EMAILS = {
|
|
|
1172
1324
|
};
|
|
1173
1325
|
|
|
1174
1326
|
// src/auth/password.ts
|
|
1175
|
-
import { randomBytes as
|
|
1327
|
+
import { randomBytes as randomBytes3, scrypt, timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
1176
1328
|
var MIN_PASSWORD_LENGTH = 8;
|
|
1177
1329
|
var N = 2 ** 17;
|
|
1178
1330
|
var R = 8;
|
|
@@ -1191,7 +1343,7 @@ function derive(password, salt, n, r, p, length) {
|
|
|
1191
1343
|
});
|
|
1192
1344
|
}
|
|
1193
1345
|
async function hashPassword(password) {
|
|
1194
|
-
const salt =
|
|
1346
|
+
const salt = randomBytes3(16);
|
|
1195
1347
|
const key = await derive(password, salt, N, R, P, KEY_LENGTH);
|
|
1196
1348
|
return ["scrypt", N, R, P, salt.toString("base64url"), key.toString("base64url")].join("$");
|
|
1197
1349
|
}
|
|
@@ -1207,7 +1359,7 @@ async function verifyPassword(password, stored) {
|
|
|
1207
1359
|
Number(p),
|
|
1208
1360
|
expected.length
|
|
1209
1361
|
);
|
|
1210
|
-
return key.length === expected.length &&
|
|
1362
|
+
return key.length === expected.length && timingSafeEqual4(key, expected);
|
|
1211
1363
|
}
|
|
1212
1364
|
var dummyHash;
|
|
1213
1365
|
async function fakeVerify(password) {
|
|
@@ -1218,92 +1370,6 @@ async function fakeVerify(password) {
|
|
|
1218
1370
|
|
|
1219
1371
|
// src/auth/sso.ts
|
|
1220
1372
|
import { createHmac as createHmac4, randomBytes as randomBytes4 } from "crypto";
|
|
1221
|
-
|
|
1222
|
-
// src/auth/tokens.ts
|
|
1223
|
-
import { createHash as createHash2, createHmac as createHmac3, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
1224
|
-
function newToken() {
|
|
1225
|
-
return randomBytes3(32).toString("base64url");
|
|
1226
|
-
}
|
|
1227
|
-
function hashToken(token) {
|
|
1228
|
-
return createHash2("sha256").update(token).digest("hex");
|
|
1229
|
-
}
|
|
1230
|
-
function hmac(secret, value) {
|
|
1231
|
-
return createHmac3("sha256", secret).update(value).digest("base64url");
|
|
1232
|
-
}
|
|
1233
|
-
function signToken(secret, token) {
|
|
1234
|
-
return `${token}.${hmac(secret, `session:${token}`)}`;
|
|
1235
|
-
}
|
|
1236
|
-
function unsignToken(secret, value) {
|
|
1237
|
-
const dot = value.lastIndexOf(".");
|
|
1238
|
-
if (dot <= 0) return void 0;
|
|
1239
|
-
const token = value.slice(0, dot);
|
|
1240
|
-
return safeEqual(value.slice(dot + 1), hmac(secret, `session:${token}`)) ? token : void 0;
|
|
1241
|
-
}
|
|
1242
|
-
function csrfToken(secret, token) {
|
|
1243
|
-
return hmac(secret, `csrf:${token}`);
|
|
1244
|
-
}
|
|
1245
|
-
function safeEqual(a, b) {
|
|
1246
|
-
const left = Buffer.from(a);
|
|
1247
|
-
const right = Buffer.from(b);
|
|
1248
|
-
return left.length === right.length && timingSafeEqual4(left, right);
|
|
1249
|
-
}
|
|
1250
|
-
function signPreviewToken(secret, target, expiresAt, user) {
|
|
1251
|
-
const payload = Buffer.from(
|
|
1252
|
-
JSON.stringify({
|
|
1253
|
-
..."global" in target ? { g: target.global } : { c: target.collection, i: target.id },
|
|
1254
|
-
e: expiresAt,
|
|
1255
|
-
...user !== void 0 ? { u: user } : {}
|
|
1256
|
-
})
|
|
1257
|
-
).toString("base64url");
|
|
1258
|
-
return `${payload}.${hmac(secret, `preview:${payload}`)}`;
|
|
1259
|
-
}
|
|
1260
|
-
function verifyPreviewToken(secret, token, now = Date.now()) {
|
|
1261
|
-
const dot = token.lastIndexOf(".");
|
|
1262
|
-
if (dot <= 0) return null;
|
|
1263
|
-
const payload = token.slice(0, dot);
|
|
1264
|
-
if (!safeEqual(token.slice(dot + 1), hmac(secret, `preview:${payload}`))) return null;
|
|
1265
|
-
try {
|
|
1266
|
-
const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
1267
|
-
if (typeof data.e !== "number" || data.e <= now) return null;
|
|
1268
|
-
const user = typeof data.u === "string" ? { user: data.u } : {};
|
|
1269
|
-
if (typeof data.g === "string") return { global: data.g, expiresAt: data.e, ...user };
|
|
1270
|
-
if (typeof data.c === "string" && typeof data.i === "string")
|
|
1271
|
-
return { collection: data.c, id: data.i, expiresAt: data.e, ...user };
|
|
1272
|
-
return null;
|
|
1273
|
-
} catch {
|
|
1274
|
-
return null;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
function passwordFingerprint(passwordHash) {
|
|
1278
|
-
return createHash2("sha256").update(typeof passwordHash === "string" ? passwordHash : "no-password").digest("base64url").slice(0, 22);
|
|
1279
|
-
}
|
|
1280
|
-
function signPasswordToken(secret, args) {
|
|
1281
|
-
const payload = Buffer.from(
|
|
1282
|
-
JSON.stringify({ u: args.userId, p: args.purpose, e: args.expiresAt })
|
|
1283
|
-
).toString("base64url");
|
|
1284
|
-
return `${payload}.${hmac(secret, `password:${payload}:${args.fingerprint}`)}`;
|
|
1285
|
-
}
|
|
1286
|
-
function readPasswordToken(token) {
|
|
1287
|
-
const dot = token.lastIndexOf(".");
|
|
1288
|
-
if (dot <= 0) return null;
|
|
1289
|
-
const payload = token.slice(0, dot);
|
|
1290
|
-
try {
|
|
1291
|
-
const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
1292
|
-
if (typeof data.u !== "string" || typeof data.e !== "number") return null;
|
|
1293
|
-
if (data.p !== "reset" && data.p !== "invite" && data.p !== "verify") return null;
|
|
1294
|
-
return { userId: data.u, purpose: data.p, expiresAt: data.e, payload };
|
|
1295
|
-
} catch {
|
|
1296
|
-
return null;
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
function passwordTokenMatches(secret, token, fingerprint) {
|
|
1300
|
-
const dot = token.lastIndexOf(".");
|
|
1301
|
-
if (dot <= 0) return false;
|
|
1302
|
-
const payload = token.slice(0, dot);
|
|
1303
|
-
return safeEqual(token.slice(dot + 1), hmac(secret, `password:${payload}:${fingerprint}`));
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// src/auth/sso.ts
|
|
1307
1373
|
var SSO_COOKIE = "ecms-sso";
|
|
1308
1374
|
var PENDING_TTL = 10 * 6e4;
|
|
1309
1375
|
var random = () => randomBytes4(32).toString("base64url");
|
|
@@ -1314,12 +1380,13 @@ function seal(secret, pending) {
|
|
|
1314
1380
|
const payload = Buffer.from(JSON.stringify(pending)).toString("base64url");
|
|
1315
1381
|
return `${payload}.${sign2(secret, payload)}`;
|
|
1316
1382
|
}
|
|
1317
|
-
function unseal(
|
|
1383
|
+
function unseal(secrets, value) {
|
|
1318
1384
|
if (!value) return null;
|
|
1319
1385
|
const dot = value.lastIndexOf(".");
|
|
1320
1386
|
if (dot <= 0) return null;
|
|
1321
1387
|
const payload = value.slice(0, dot);
|
|
1322
|
-
|
|
1388
|
+
const signature = value.slice(dot + 1);
|
|
1389
|
+
if (![secrets].flat().some((secret) => safeEqual(signature, sign2(secret, payload)))) return null;
|
|
1323
1390
|
try {
|
|
1324
1391
|
const pending = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
1325
1392
|
return typeof pending.e === "number" && pending.e > Date.now() ? pending : null;
|
|
@@ -1387,7 +1454,7 @@ var SingleSignOn = class {
|
|
|
1387
1454
|
*/
|
|
1388
1455
|
async finish(providerId, options) {
|
|
1389
1456
|
const admin = this.config.admin.path.replace(/\/+$/, "");
|
|
1390
|
-
const pending = unseal(this.config
|
|
1457
|
+
const pending = unseal(secretsOf(this.config), options.cookie);
|
|
1391
1458
|
let email2 = null;
|
|
1392
1459
|
const back = async (page2, outcome) => {
|
|
1393
1460
|
await this.cms.audit.record({
|
|
@@ -1839,7 +1906,7 @@ var Auth = class {
|
|
|
1839
1906
|
await this.recordFailure(key);
|
|
1840
1907
|
throw refuse("form", "could not be sent");
|
|
1841
1908
|
}
|
|
1842
|
-
const checked = checkFormToken(this.
|
|
1909
|
+
const checked = checkFormToken(this.secrets, SIGNUP_FORM, args.token, SIGNUP_MIN_TIME);
|
|
1843
1910
|
if (checked !== "ok")
|
|
1844
1911
|
throw refuse(
|
|
1845
1912
|
"form",
|
|
@@ -1899,7 +1966,7 @@ var Auth = class {
|
|
|
1899
1966
|
const id = claims && (/^\d+$/.test(claims.userId) ? Number(claims.userId) : claims.userId);
|
|
1900
1967
|
const user = claims && claims.purpose === "verify" && claims.expiresAt > Date.now() && id !== null ? await this.db.findById({ collection: USERS, id }) : null;
|
|
1901
1968
|
if (!user || user.active === false || // A link confirms once: it doesn't sign in again afterwards.
|
|
1902
|
-
user.emailVerified !== false || !passwordTokenMatches(this.
|
|
1969
|
+
user.emailVerified !== false || !passwordTokenMatches(this.secrets, token, passwordFingerprint(user.passwordHash))) {
|
|
1903
1970
|
await this.recordFailure(key);
|
|
1904
1971
|
throw new ValidationError(USERS, [
|
|
1905
1972
|
{ field: "token", message: "This link has expired or is not valid" }
|
|
@@ -1969,7 +2036,7 @@ var Auth = class {
|
|
|
1969
2036
|
}
|
|
1970
2037
|
/** Ends the session for a signed token. Unknown tokens are ignored. */
|
|
1971
2038
|
async logout(signedToken) {
|
|
1972
|
-
const token = unsignToken(this.
|
|
2039
|
+
const token = unsignToken(this.secrets, signedToken);
|
|
1973
2040
|
if (!token) return;
|
|
1974
2041
|
const session = await this.findSession(token);
|
|
1975
2042
|
if (!session) return;
|
|
@@ -1999,7 +2066,7 @@ var Auth = class {
|
|
|
1999
2066
|
async verify(signedToken) {
|
|
2000
2067
|
if (!signedToken) return null;
|
|
2001
2068
|
if (isApiKey(signedToken)) return this.verifyApiKey(signedToken);
|
|
2002
|
-
const token = unsignToken(this.
|
|
2069
|
+
const token = unsignToken(this.secrets, signedToken);
|
|
2003
2070
|
if (!token) return null;
|
|
2004
2071
|
const session = await this.findSession(token);
|
|
2005
2072
|
if (!session) return null;
|
|
@@ -2019,9 +2086,21 @@ var Auth = class {
|
|
|
2019
2086
|
}
|
|
2020
2087
|
/** The CSRF token clients must echo for a signed session token. @internal */
|
|
2021
2088
|
csrfFor(signedToken) {
|
|
2022
|
-
const token = unsignToken(this.
|
|
2089
|
+
const token = unsignToken(this.secrets, signedToken);
|
|
2023
2090
|
return token ? csrfToken(this.config.secret, token) : void 0;
|
|
2024
2091
|
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Whether `given` is the session's CSRF token, made with the current secret or a previous one
|
|
2094
|
+
* (a client that fetched it before the secret changed). @internal
|
|
2095
|
+
*/
|
|
2096
|
+
csrfMatches(signedToken, given) {
|
|
2097
|
+
const token = unsignToken(this.secrets, signedToken);
|
|
2098
|
+
return token !== void 0 && csrfMatches(this.secrets, token, given);
|
|
2099
|
+
}
|
|
2100
|
+
/** The current secret and `previousSecrets`, which still verify. */
|
|
2101
|
+
get secrets() {
|
|
2102
|
+
return secretsOf(this.config);
|
|
2103
|
+
}
|
|
2025
2104
|
/** Starts a new session for a user, e.g. after they changed their own password. */
|
|
2026
2105
|
async createSession(userId) {
|
|
2027
2106
|
const user = await this.db.findById({ collection: USERS, id: userId });
|
|
@@ -2144,7 +2223,7 @@ var Auth = class {
|
|
|
2144
2223
|
const user = await this.db.findById({ collection: USERS, id });
|
|
2145
2224
|
if (!user || user.active === false || !this.sso.passwordAllowed(user)) return null;
|
|
2146
2225
|
const fingerprint = passwordFingerprint(user.passwordHash);
|
|
2147
|
-
if (!passwordTokenMatches(this.
|
|
2226
|
+
if (!passwordTokenMatches(this.secrets, token, fingerprint)) return null;
|
|
2148
2227
|
return { user, purpose: claims.purpose };
|
|
2149
2228
|
}
|
|
2150
2229
|
async mailPasswordLink(user, purpose, options) {
|
|
@@ -3911,7 +3990,7 @@ var VersionStore = class {
|
|
|
3911
3990
|
};
|
|
3912
3991
|
|
|
3913
3992
|
// src/local-api.ts
|
|
3914
|
-
import { createHash as createHash5, createHmac as
|
|
3993
|
+
import { createHash as createHash5, createHmac as createHmac8, randomBytes as randomBytes6, randomUUID as randomUUID3 } from "crypto";
|
|
3915
3994
|
|
|
3916
3995
|
// src/auth/common-passwords.ts
|
|
3917
3996
|
var LIST = `
|
|
@@ -4407,6 +4486,141 @@ function entryOf(row) {
|
|
|
4407
4486
|
};
|
|
4408
4487
|
}
|
|
4409
4488
|
|
|
4489
|
+
// src/rate-limit.ts
|
|
4490
|
+
import { createHmac as createHmac5 } from "crypto";
|
|
4491
|
+
var SYNC_MS = 5e3;
|
|
4492
|
+
var DEFAULTS = { window: 60, anonymous: 300, users: 1200, apiKeys: 1200 };
|
|
4493
|
+
var RateLimiter = class {
|
|
4494
|
+
constructor(db, secret, logger, settings) {
|
|
4495
|
+
this.db = db;
|
|
4496
|
+
this.secret = secret;
|
|
4497
|
+
this.logger = logger;
|
|
4498
|
+
this.settings = { ...DEFAULTS, ...settings };
|
|
4499
|
+
}
|
|
4500
|
+
db;
|
|
4501
|
+
secret;
|
|
4502
|
+
logger;
|
|
4503
|
+
counts = /* @__PURE__ */ new Map();
|
|
4504
|
+
settings;
|
|
4505
|
+
lastSync = Date.now();
|
|
4506
|
+
syncing;
|
|
4507
|
+
warnedNoIp = false;
|
|
4508
|
+
/**
|
|
4509
|
+
* Counts a request, or returns the seconds until the client may send another when it is over
|
|
4510
|
+
* its limit. Health checks and the cron's `jobs/run` aren't counted (the caller skips them).
|
|
4511
|
+
*/
|
|
4512
|
+
hit(user, ip, now = Date.now()) {
|
|
4513
|
+
const client = this.client(user, ip);
|
|
4514
|
+
if (!client) return void 0;
|
|
4515
|
+
const [kind, id, limit] = client;
|
|
4516
|
+
const windowMs = this.settings.window * 1e3;
|
|
4517
|
+
const bucket = Math.floor(now / windowMs);
|
|
4518
|
+
const key = `${kind}:${id}:${bucket}`;
|
|
4519
|
+
let count = this.counts.get(key);
|
|
4520
|
+
if (!count) {
|
|
4521
|
+
count = { total: 0, pending: 0, ends: (bucket + 1) * windowMs };
|
|
4522
|
+
this.counts.set(key, count);
|
|
4523
|
+
}
|
|
4524
|
+
if (now - this.lastSync >= SYNC_MS && !this.syncing) void this.sync(now);
|
|
4525
|
+
if (count.total + count.pending >= limit)
|
|
4526
|
+
return Math.max(1, Math.ceil((count.ends - now) / 1e3));
|
|
4527
|
+
count.pending++;
|
|
4528
|
+
return void 0;
|
|
4529
|
+
}
|
|
4530
|
+
/** Who is counted, and their limit; `undefined` when nothing limits them. */
|
|
4531
|
+
client(user, ip) {
|
|
4532
|
+
const { anonymous, users, apiKeys } = this.settings;
|
|
4533
|
+
const key = user?.apiKey;
|
|
4534
|
+
if (key) return apiKeys === false ? void 0 : ["apiKey", String(key.id), apiKeys];
|
|
4535
|
+
if (user) return users === false ? void 0 : ["user", String(user.id), users];
|
|
4536
|
+
if (anonymous === false) return void 0;
|
|
4537
|
+
if (!ip) {
|
|
4538
|
+
if (!this.warnedNoIp) {
|
|
4539
|
+
this.warnedNoIp = true;
|
|
4540
|
+
this.logger.warn(
|
|
4541
|
+
"rateLimit: the client IP is unknown, so visitors who are not signed in are not limited. Behind a proxy, set trustProxy on the adapter."
|
|
4542
|
+
);
|
|
4543
|
+
}
|
|
4544
|
+
return void 0;
|
|
4545
|
+
}
|
|
4546
|
+
const hashed = createHmac5("sha256", this.secret).update(`rate:${ip}`).digest("base64url");
|
|
4547
|
+
return ["anonymous", hashed.slice(0, 22), anonymous];
|
|
4548
|
+
}
|
|
4549
|
+
/**
|
|
4550
|
+
* Adds what this instance counted to the database and reads the totals back; forgets windows
|
|
4551
|
+
* that are over. Runs in the background every few seconds; `await` it in tests.
|
|
4552
|
+
*/
|
|
4553
|
+
sync(now = Date.now()) {
|
|
4554
|
+
this.lastSync = now;
|
|
4555
|
+
this.syncing ??= this.write(now).catch((error) => this.logger.warn(`rateLimit: counts not saved: ${String(error)}`)).finally(() => {
|
|
4556
|
+
this.syncing = void 0;
|
|
4557
|
+
});
|
|
4558
|
+
return this.syncing;
|
|
4559
|
+
}
|
|
4560
|
+
async write(now) {
|
|
4561
|
+
for (const [key, count] of this.counts) {
|
|
4562
|
+
if (count.ends <= now) {
|
|
4563
|
+
this.counts.delete(key);
|
|
4564
|
+
continue;
|
|
4565
|
+
}
|
|
4566
|
+
const added = count.pending;
|
|
4567
|
+
count.pending = 0;
|
|
4568
|
+
count.total = await this.add(key, count, added) ?? count.total + added;
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
/** Adds `by` to the key's row (made if missing) and returns the new total. */
|
|
4572
|
+
async add(key, count, by) {
|
|
4573
|
+
count.id ??= await this.rowId(key);
|
|
4574
|
+
if (count.id === void 0) {
|
|
4575
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4576
|
+
try {
|
|
4577
|
+
const created = await this.db.create({
|
|
4578
|
+
collection: RATE_LIMITS,
|
|
4579
|
+
data: {
|
|
4580
|
+
key,
|
|
4581
|
+
count: by,
|
|
4582
|
+
expiresAt: new Date(count.ends).toISOString(),
|
|
4583
|
+
createdAt: stamp,
|
|
4584
|
+
updatedAt: stamp
|
|
4585
|
+
}
|
|
4586
|
+
});
|
|
4587
|
+
count.id = created.id;
|
|
4588
|
+
return by;
|
|
4589
|
+
} catch (error) {
|
|
4590
|
+
count.id = await this.rowId(key);
|
|
4591
|
+
if (count.id === void 0) throw error;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
return this.db.increment({ collection: RATE_LIMITS, id: count.id, field: "count", by });
|
|
4595
|
+
}
|
|
4596
|
+
async rowId(key) {
|
|
4597
|
+
const { docs } = await this.db.find({
|
|
4598
|
+
collection: RATE_LIMITS,
|
|
4599
|
+
where: { key: { equals: key } },
|
|
4600
|
+
sort: [],
|
|
4601
|
+
limit: 1,
|
|
4602
|
+
page: 1
|
|
4603
|
+
});
|
|
4604
|
+
return docs[0]?.id;
|
|
4605
|
+
}
|
|
4606
|
+
/** Deletes the rows of windows that are over. From `runJobs`. */
|
|
4607
|
+
async clear(now = /* @__PURE__ */ new Date()) {
|
|
4608
|
+
let deleted = 0;
|
|
4609
|
+
for (; ; ) {
|
|
4610
|
+
const { docs } = await this.db.find({
|
|
4611
|
+
collection: RATE_LIMITS,
|
|
4612
|
+
where: { expiresAt: { lt: now.toISOString() } },
|
|
4613
|
+
sort: ["id"],
|
|
4614
|
+
limit: 500,
|
|
4615
|
+
page: 1
|
|
4616
|
+
});
|
|
4617
|
+
for (const row of docs) await this.db.delete({ collection: RATE_LIMITS, id: row.id });
|
|
4618
|
+
deleted += docs.length;
|
|
4619
|
+
if (docs.length < 500) return deleted;
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
};
|
|
4623
|
+
|
|
4410
4624
|
// src/remote-file.ts
|
|
4411
4625
|
import { lookup as dnsLookup } from "dns";
|
|
4412
4626
|
import { request as httpRequest } from "http";
|
|
@@ -4647,9 +4861,9 @@ function read(response, maxBytes, deadline) {
|
|
|
4647
4861
|
|
|
4648
4862
|
// src/config-keys.ts
|
|
4649
4863
|
var keys = (list) => new Set(list.split(/\s+/).filter(Boolean));
|
|
4650
|
-
var CONFIG = keys(`secret db serverURL cors cronSecret timeZone webhooks events jobs localization routes admin
|
|
4864
|
+
var CONFIG = keys(`secret previousSecrets db serverURL cors cronSecret timeZone webhooks events jobs localization routes admin
|
|
4651
4865
|
upload auth collections globals endpoints cliCommands onRequest fieldTypes apiKeys email backups
|
|
4652
|
-
audit logs countCacheSeconds maxPage httpCache outbox search telemetry plugins`);
|
|
4866
|
+
audit logs countCacheSeconds maxPage httpCache outbox rateLimit search telemetry plugins`);
|
|
4653
4867
|
var ADMIN = keys("path locale brand siteURL nav commands modules pages dashboard switcher");
|
|
4654
4868
|
var AUTH = keys(`roles rbac tokenExpiration maxLoginAttempts lockWindow trustedOrigins
|
|
4655
4869
|
resetPasswordExpiration inviteExpiration providers providerSignUp password setupCode members emails`);
|
|
@@ -4657,6 +4871,7 @@ var MEMBERS = keys("roles signUp pages emails");
|
|
|
4657
4871
|
var UPLOAD = keys("dir maxFileSize mimeTypes storage imageSizes fromURL folders privateStorage");
|
|
4658
4872
|
var BACKUPS = keys("frequency at keep dir storage sqlite encryptionKey");
|
|
4659
4873
|
var AUDIT = keys("keepDays values failedLogins scope");
|
|
4874
|
+
var RATE_LIMIT = keys("window anonymous users apiKeys");
|
|
4660
4875
|
var LOGS = keys("format requests");
|
|
4661
4876
|
var COLLECTION = keys(`slug labels fields useAsTitle drafts versions schedule preview access hooks
|
|
4662
4877
|
admin httpCache search`);
|
|
@@ -4782,6 +4997,7 @@ function checkConfigKeys(config, add) {
|
|
|
4782
4997
|
check2(c.upload, "upload", "upload", UPLOAD, add);
|
|
4783
4998
|
check2(c.backups, "backups", "backups", BACKUPS, add);
|
|
4784
4999
|
if (isObject(c.audit)) check2(c.audit, "audit", "audit", AUDIT, add);
|
|
5000
|
+
if (isObject(c.rateLimit)) check2(c.rateLimit, "rateLimit", "rateLimit", RATE_LIMIT, add);
|
|
4785
5001
|
check2(c.logs, "logs", "logs", LOGS, add);
|
|
4786
5002
|
for (const [kind, list] of [
|
|
4787
5003
|
["collections", c.collections],
|
|
@@ -5138,13 +5354,13 @@ var SearchIndex = class {
|
|
|
5138
5354
|
};
|
|
5139
5355
|
|
|
5140
5356
|
// src/webhooks.ts
|
|
5141
|
-
import { createHmac as
|
|
5357
|
+
import { createHmac as createHmac7, randomUUID as randomUUID2 } from "crypto";
|
|
5142
5358
|
|
|
5143
5359
|
// src/webhook-signature.ts
|
|
5144
|
-
import { createHmac as
|
|
5360
|
+
import { createHmac as createHmac6, timingSafeEqual as timingSafeEqual5 } from "crypto";
|
|
5145
5361
|
var TOLERANCE_SECONDS = 5 * 60;
|
|
5146
5362
|
function signWebhook(secret, delivery, timestamp, body) {
|
|
5147
|
-
return `v1=${
|
|
5363
|
+
return `v1=${createHmac6("sha256", secret).update(`${delivery}.${timestamp}.${body}`).digest("hex")}`;
|
|
5148
5364
|
}
|
|
5149
5365
|
function header(headers, name) {
|
|
5150
5366
|
if (headers instanceof Headers) return headers.get(name) ?? void 0;
|
|
@@ -5345,7 +5561,7 @@ var Webhooks = class {
|
|
|
5345
5561
|
const timestamp = Math.floor(Date.now() / 1e3);
|
|
5346
5562
|
headers["x-easy-cms-timestamp"] = String(timestamp);
|
|
5347
5563
|
headers["x-easy-cms-signature-v2"] = signWebhook(hook.secret, delivery, timestamp, body);
|
|
5348
|
-
headers["x-easy-cms-signature"] = `sha256=${
|
|
5564
|
+
headers["x-easy-cms-signature"] = `sha256=${createHmac7("sha256", hook.secret).update(body).digest("hex")}`;
|
|
5349
5565
|
}
|
|
5350
5566
|
try {
|
|
5351
5567
|
const response = await this.fetcher(hook.url, {
|
|
@@ -5417,6 +5633,7 @@ function validateConfig(config) {
|
|
|
5417
5633
|
return issues;
|
|
5418
5634
|
}
|
|
5419
5635
|
validateSecret(config.secret, add);
|
|
5636
|
+
validatePreviousSecrets(config.previousSecrets, config.secret, add);
|
|
5420
5637
|
validateCronSecret(config.cronSecret, (issue) => issues.push(issue));
|
|
5421
5638
|
checkConfigKeys(config, (issue) => issues.push(issue));
|
|
5422
5639
|
validateAdapterVersions(config, add);
|
|
@@ -5502,6 +5719,22 @@ function validateSecret(secret, add) {
|
|
|
5502
5719
|
add("secret", "looks like an example or a repeated pattern, not a random string", hint);
|
|
5503
5720
|
}
|
|
5504
5721
|
}
|
|
5722
|
+
function validatePreviousSecrets(value, secret, add) {
|
|
5723
|
+
if (value === void 0) return;
|
|
5724
|
+
if (!Array.isArray(value)) {
|
|
5725
|
+
add("previousSecrets", "must be an array of strings", "e.g. previousSecrets: [oldSecret]");
|
|
5726
|
+
return;
|
|
5727
|
+
}
|
|
5728
|
+
for (const [i, previous] of value.entries()) {
|
|
5729
|
+
if (typeof previous !== "string" || previous.length < MIN_SECRET_LENGTH)
|
|
5730
|
+
add(
|
|
5731
|
+
`previousSecrets[${i}]`,
|
|
5732
|
+
`must be a string of at least ${MIN_SECRET_LENGTH} characters, like secret`
|
|
5733
|
+
);
|
|
5734
|
+
else if (previous === secret)
|
|
5735
|
+
add(`previousSecrets[${i}]`, "is the same as secret", "list only secrets used before it");
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5505
5738
|
function validateCronSecret(value, push) {
|
|
5506
5739
|
const hint = `use a random string of at least ${MIN_SECRET_LENGTH} characters, e.g. \`openssl rand -hex 32\``;
|
|
5507
5740
|
if (value !== void 0) {
|
|
@@ -7013,6 +7246,7 @@ async function resolveConfig(input) {
|
|
|
7013
7246
|
...config.email ? [emailDeliveriesCollection] : [],
|
|
7014
7247
|
...(config.jobs?.length ?? 0) > 0 ? [jobRunsCollection] : [],
|
|
7015
7248
|
...config.outbox ? [outboxCollection] : [],
|
|
7249
|
+
...config.rateLimit ? [rateLimitsCollection] : [],
|
|
7016
7250
|
databaseBackupsCollection,
|
|
7017
7251
|
...config.auth?.rbac ? [rolesCollection] : [],
|
|
7018
7252
|
...(config.auth?.providers?.length ?? 0) > 0 ? [userIdentitiesCollection] : [],
|
|
@@ -7083,7 +7317,7 @@ function cacheableResponse(request, body, status, headers, times, tags) {
|
|
|
7083
7317
|
}
|
|
7084
7318
|
|
|
7085
7319
|
// src/version.ts
|
|
7086
|
-
var VERSION = true ? "1.
|
|
7320
|
+
var VERSION = true ? "1.19.0" : "0.0.0-dev";
|
|
7087
7321
|
|
|
7088
7322
|
// src/telemetry.ts
|
|
7089
7323
|
var DB_CALLS = /* @__PURE__ */ new Set([
|
|
@@ -7361,6 +7595,8 @@ var EasyCMS = class {
|
|
|
7361
7595
|
mailer;
|
|
7362
7596
|
/** With `outbox: true`: runs after hooks and events from rows saves write. */
|
|
7363
7597
|
outbox;
|
|
7598
|
+
/** @internal Requests per client (`rateLimit`), for the REST API. */
|
|
7599
|
+
rateLimiter;
|
|
7364
7600
|
/** With `search`: keeps the index in step with published documents. */
|
|
7365
7601
|
searchIndex;
|
|
7366
7602
|
/** @internal With `telemetry`: OpenTelemetry spans and metrics (set by `createEasyCMS`). */
|
|
@@ -7401,6 +7637,7 @@ var EasyCMS = class {
|
|
|
7401
7637
|
() => this.runOutbox(entry, done, mark)
|
|
7402
7638
|
) : this.runOutbox(entry, done, mark)
|
|
7403
7639
|
) : void 0;
|
|
7640
|
+
this.rateLimiter = config.rateLimit ? new RateLimiter(db, config.secret, logger, config.rateLimit) : void 0;
|
|
7404
7641
|
this.searchIndex = config.search ? new SearchIndex(
|
|
7405
7642
|
config.search,
|
|
7406
7643
|
config.collections,
|
|
@@ -7747,18 +7984,14 @@ var EasyCMS = class {
|
|
|
7747
7984
|
/** A value with its HMAC (`secret`), for `purpose` only: `<base64url JSON>.<signature>`. */
|
|
7748
7985
|
sign(purpose, value) {
|
|
7749
7986
|
const payload = Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
7750
|
-
const mac =
|
|
7987
|
+
const mac = createHmac8("sha256", this.config.secret).update(`${purpose}:${payload}`);
|
|
7751
7988
|
return `${payload}.${mac.digest("base64url")}`;
|
|
7752
7989
|
}
|
|
7753
7990
|
/** The value of a token made by `sign`, or `undefined` when it isn't genuine. */
|
|
7754
7991
|
verify(purpose, token) {
|
|
7755
7992
|
const [payload, signature] = String(token).split(".");
|
|
7756
7993
|
if (!payload || !signature) return void 0;
|
|
7757
|
-
|
|
7758
|
-
createHmac7("sha256", this.config.secret).update(`${purpose}:${payload}`).digest("base64url")
|
|
7759
|
-
);
|
|
7760
|
-
const given = Buffer.from(signature);
|
|
7761
|
-
if (expected.length !== given.length || !timingSafeEqual6(expected, given)) return void 0;
|
|
7994
|
+
if (!signedWith(secretsOf(this.config), `${purpose}:${payload}`, signature)) return void 0;
|
|
7762
7995
|
try {
|
|
7763
7996
|
return JSON.parse(Buffer.from(payload, "base64url").toString());
|
|
7764
7997
|
} catch {
|
|
@@ -7875,12 +8108,10 @@ var EasyCMS = class {
|
|
|
7875
8108
|
verifyMediaSignature(key, expires, signature) {
|
|
7876
8109
|
const at = Number(expires);
|
|
7877
8110
|
if (!signature || !Number.isInteger(at) || at < Date.now() / 1e3) return false;
|
|
7878
|
-
|
|
7879
|
-
const given = Buffer.from(signature);
|
|
7880
|
-
return expected.length === given.length && timingSafeEqual6(expected, given);
|
|
8111
|
+
return signedWith(secretsOf(this.config), `media:${key}:${at}`, signature);
|
|
7881
8112
|
}
|
|
7882
8113
|
mediaSignature(key, expires) {
|
|
7883
|
-
return
|
|
8114
|
+
return createHmac8("sha256", this.config.secret).update(`media:${key}:${expires}`).digest("base64url");
|
|
7884
8115
|
}
|
|
7885
8116
|
/**
|
|
7886
8117
|
* How many documents use any of these files in a top-level upload field (rich text and fields
|
|
@@ -8170,7 +8401,7 @@ var EasyCMS = class {
|
|
|
8170
8401
|
}
|
|
8171
8402
|
/** What a preview token opens, or `null` when it is invalid or expired. */
|
|
8172
8403
|
verifyPreviewToken(token) {
|
|
8173
|
-
return token ? verifyPreviewToken(this.config
|
|
8404
|
+
return token ? verifyPreviewToken(secretsOf(this.config), token) : null;
|
|
8174
8405
|
}
|
|
8175
8406
|
/** Live preview of a global. See `preview`. */
|
|
8176
8407
|
async previewGlobal(slug2, data, options = {}) {
|
|
@@ -8897,6 +9128,7 @@ var EasyCMS = class {
|
|
|
8897
9128
|
(error) => this.logger.error(`Backups: ${error.message}`, { error })
|
|
8898
9129
|
);
|
|
8899
9130
|
await this.audit.upkeep(now).catch((error) => this.logger.error(`Audit log: ${error.message}`, { error }));
|
|
9131
|
+
await this.rateLimiter?.clear(now).catch((error) => this.logger.error(`Rate limits: ${error.message}`, { error }));
|
|
8900
9132
|
const result = {
|
|
8901
9133
|
scheduled,
|
|
8902
9134
|
webhooks: await this.retryWebhooks(now),
|
|
@@ -12165,6 +12397,10 @@ async function respondNow(cms, request, url, path, root, options, production, re
|
|
|
12165
12397
|
trustProxy: options.trustProxy === true
|
|
12166
12398
|
};
|
|
12167
12399
|
const method = request.method.toUpperCase();
|
|
12400
|
+
const isJobsRun = !root && segments[0] === "jobs" && segments[1] === "run";
|
|
12401
|
+
const wait = isJobsRun ? void 0 : cms.rateLimiter?.hit(user, options.getClientIp?.(request));
|
|
12402
|
+
if (wait !== void 0)
|
|
12403
|
+
throw new TooManyRequestsError("Too many requests: try again later", wait);
|
|
12168
12404
|
if (method !== "GET" && method !== "HEAD") checkCsrf(cms, ctx);
|
|
12169
12405
|
const audit = {
|
|
12170
12406
|
user,
|
|
@@ -13136,9 +13372,8 @@ function checkCsrf(cms, ctx) {
|
|
|
13136
13372
|
if (origin === null && fetchSite === "cross-site")
|
|
13137
13373
|
throw new ForbiddenError("CSRF check failed: cross-site request");
|
|
13138
13374
|
if (ctx.via === "cookie" && ctx.token) {
|
|
13139
|
-
const expected = cms.auth.csrfFor(ctx.token);
|
|
13140
13375
|
const given = ctx.request.headers.get(CSRF_HEADER);
|
|
13141
|
-
if (!
|
|
13376
|
+
if (!given || !cms.auth.csrfMatches(ctx.token, given)) {
|
|
13142
13377
|
throw new ForbiddenError(
|
|
13143
13378
|
`CSRF check failed: send the ${CSRF_HEADER} header from GET /auth/me`
|
|
13144
13379
|
);
|
|
@@ -13538,6 +13773,7 @@ export {
|
|
|
13538
13773
|
isStaff,
|
|
13539
13774
|
isSignedIn,
|
|
13540
13775
|
isAdmin,
|
|
13776
|
+
secretsOf,
|
|
13541
13777
|
MEDIA,
|
|
13542
13778
|
MEDIA_FOLDERS,
|
|
13543
13779
|
INTERNAL_COLLECTIONS,
|
|
@@ -356,6 +356,16 @@ interface BundleHints {
|
|
|
356
356
|
readonly traceInclude: readonly string[];
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Secrets that verify: the current one first, then `previousSecrets` while one is replaced. New
|
|
361
|
+
* values are always signed with the current one.
|
|
362
|
+
*/
|
|
363
|
+
type Secrets = string | readonly string[];
|
|
364
|
+
/** The secrets of a config, the current one first. */
|
|
365
|
+
declare function secretsOf(config: {
|
|
366
|
+
readonly secret: string;
|
|
367
|
+
readonly previousSecrets?: readonly string[] | undefined;
|
|
368
|
+
}): readonly string[];
|
|
359
369
|
/** What a preview token opens: one document of a collection, or one global. */
|
|
360
370
|
type PreviewTarget = {
|
|
361
371
|
readonly collection: string;
|
|
@@ -613,6 +623,8 @@ declare class Auth {
|
|
|
613
623
|
* on the user), or `null` if it is invalid or expired.
|
|
614
624
|
*/
|
|
615
625
|
verify(signedToken: string | undefined | null): Promise<AuthUser | null>;
|
|
626
|
+
/** The current secret and `previousSecrets`, which still verify. */
|
|
627
|
+
private get secrets();
|
|
616
628
|
/** Starts a new session for a user, e.g. after they changed their own password. */
|
|
617
629
|
createSession(userId: ID): Promise<Session>;
|
|
618
630
|
/** Signs a user out everywhere. */
|
|
@@ -2460,6 +2472,18 @@ declare class AuditLog {
|
|
|
2460
2472
|
csv(filter: AuditFilter): Promise<string>;
|
|
2461
2473
|
/** Checks every entry's signature, and that no ids are missing. */
|
|
2462
2474
|
verify(): Promise<AuditVerification>;
|
|
2475
|
+
/** The secret an entry was signed with: the current one, a previous one, or none (changed). */
|
|
2476
|
+
private signer;
|
|
2477
|
+
/**
|
|
2478
|
+
* After `secret` changed: signs again, with the current secret, the entries signed with one of
|
|
2479
|
+
* `previousSecrets`, so they still verify once it is removed. Entries no secret verifies were
|
|
2480
|
+
* changed: they are left as they are, and reported. Records `audit.resign`.
|
|
2481
|
+
*/
|
|
2482
|
+
resign(): Promise<{
|
|
2483
|
+
checked: number;
|
|
2484
|
+
resigned: number;
|
|
2485
|
+
invalid: ID[];
|
|
2486
|
+
}>;
|
|
2463
2487
|
/** What the dashboard warns about: many failed logins, entries not written, a failed check. */
|
|
2464
2488
|
attention(now?: Date): Promise<({
|
|
2465
2489
|
id: 'failed-logins';
|
|
@@ -3342,6 +3366,20 @@ interface AuthConfig {
|
|
|
3342
3366
|
readonly passwordChanged?: PasswordEmailFn;
|
|
3343
3367
|
};
|
|
3344
3368
|
}
|
|
3369
|
+
interface RateLimitConfig {
|
|
3370
|
+
/** Seconds each count lasts. Default 60. */
|
|
3371
|
+
readonly window?: number;
|
|
3372
|
+
/**
|
|
3373
|
+
* Requests per window from one IP without signing in. Default 300; `false`: no limit. Needs the
|
|
3374
|
+
* client's IP: behind a proxy, set `trustProxy` on the adapter (without it, visitors aren't
|
|
3375
|
+
* limited, and the log says so once).
|
|
3376
|
+
*/
|
|
3377
|
+
readonly anonymous?: number | false;
|
|
3378
|
+
/** Requests per window from one signed-in user (the admin included). Default 1200; `false`: no limit. */
|
|
3379
|
+
readonly users?: number | false;
|
|
3380
|
+
/** Requests per window with one API key. Default 1200; `false`: no limit. */
|
|
3381
|
+
readonly apiKeys?: number | false;
|
|
3382
|
+
}
|
|
3345
3383
|
/** What `onRequest` receives. */
|
|
3346
3384
|
interface OnRequestArgs {
|
|
3347
3385
|
readonly headers: Headers;
|
|
@@ -3531,6 +3569,13 @@ interface BackupsConfig {
|
|
|
3531
3569
|
interface Config {
|
|
3532
3570
|
/** Signs sessions. At least 32 characters; read it from `process.env.EASY_CMS_SECRET`. */
|
|
3533
3571
|
readonly secret: string;
|
|
3572
|
+
/**
|
|
3573
|
+
* Secrets used before `secret`, while it is replaced: what they signed (sessions, links,
|
|
3574
|
+
* tokens, the audit log) still verifies; new values are signed with `secret` only. Keep one
|
|
3575
|
+
* until sessions and links made with it have expired (7 days by default), and run
|
|
3576
|
+
* `easy-cms audit:resign` before removing it. At least 32 characters each, like `secret`.
|
|
3577
|
+
*/
|
|
3578
|
+
readonly previousSecrets?: readonly string[];
|
|
3534
3579
|
readonly db: DatabaseAdapter;
|
|
3535
3580
|
/**
|
|
3536
3581
|
* Public origin of the app, e.g. `https://example.com`. When set, media URLs are absolute
|
|
@@ -3642,6 +3687,13 @@ interface Config {
|
|
|
3642
3687
|
* Default `false`: hooks run in the request, after the save.
|
|
3643
3688
|
*/
|
|
3644
3689
|
readonly outbox?: boolean;
|
|
3690
|
+
/**
|
|
3691
|
+
* Limits requests to the API per client and minute, counted across instances: each visitor's
|
|
3692
|
+
* IP, each signed-in user and each API key. Over the limit, the API answers `429` with
|
|
3693
|
+
* `Retry-After`. `{}` uses the defaults. Needs a migration for the `rate-limits` table. Default
|
|
3694
|
+
* off.
|
|
3695
|
+
*/
|
|
3696
|
+
readonly rateLimit?: RateLimitConfig;
|
|
3645
3697
|
/**
|
|
3646
3698
|
* Relevance-ranked search of the collections that have `search`, e.g. `memorySearch()` for
|
|
3647
3699
|
* development and small sites, or an adapter for a search engine. Published documents are
|
|
@@ -3687,5 +3739,5 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
|
|
|
3687
3739
|
*/
|
|
3688
3740
|
declare function defineConfig<const TConfig extends Config>(config: TConfig): TConfig;
|
|
3689
3741
|
|
|
3690
|
-
export { ADAPTER_API_VERSION as A, EasyCMS as E, AuditLog as S, Auth as V, EasyCMSError as aD, FIELD_TYPES as aK, ConfigError as al, ForbiddenError as b2, PLUGIN_API_VERSION as bB, PayloadTooLargeError as bG, QueryError as bM, ROLE_OPERATIONS as bN, Roles as bX, SchemaError as b_, NotFoundError as bv, API_KEYS as c$, WEBHOOK_EVENTS as cC, anyone as cH, consoleEmail as cI, consoleLogger as cJ, createEasyCMS as cK, defineConfig as cL, defineFieldType as cM, definePlugin as cN, diskStorage as cO, hasRows as cP, isAdmin as cQ, isHasMany as cR, isSignedIn as cS, isStaff as cT, isSystemAdmin as cU, jsonLogger as cV, memorySearch as cW, mimeAllowedBy as cX, rowFields as cY, silentLogger as cZ, uniqueWithinOf as c_, SingleSignOn as cd, TRIGRAM_LIKE_MIN as ck, TooManyRequestsError as co, UnauthorizedError as cq, ValidationError as cy, API_KEY_GLOBAL_OPERATIONS as d0, API_KEY_OPERATIONS as d1, DEFAULT_MAX_VERSIONS as d2, DEFAULT_PASSWORD_EMAILS as d3, FIELD_TYPE_BASES as d4, FOLDER_LEVELS as d5, MediaFolders as d6, SSO_COOKIE as d7, codeOfStatus as
|
|
3691
|
-
export type { AuthUser as $, AfterDeleteHook as B, Config as C, AfterReadHook as D, Field as F, ApiKeyContext as G, ApiKeyGlobalOperation as H, ID as I, ApiKeyOperation as J, ApiKeyPermissions as K, LocalizationConfig as L, ArrayField as M, AuditChange as N, AuditConfig as O, PaginatedDocs as P, AuditEntry as Q, ResolvedConfig as R, AuditPage as T, AuditVerification as U, WebhookPayload as W, AuthConfig as X, AuthProvider as Y, AuthProviderProfile as Z, AuthProviderRequest as _, FieldError as a, FindOptions as a$, BackupsConfig as a0, BeforeChangeHook as a1, BeforeDeleteHook as a2, BeforeValidateHook as a3, Block as a4, BlocksField as a5, BooleanField as a6, BuiltinApiKeysCollection as a7, BuiltinMediaCollection as a8, BuiltinMediaFoldersCollection as a9, DirectUpload as aA, DiskStorageOptions as aB, DocumentOf as aC, EmailAdapter as aE, EmailField as aF, EmailMessage as aG, Endpoint as aH, EndpointRequest as aI, ErrorCode as aJ, FieldAccess as aL, FieldAccessFn as aM, FieldAdmin as aN, FieldRule as aO, FieldType as aP, FieldTypeBase as aQ, FieldTypeDefinition as aR, FieldTypeValidateContext as aS, FieldValidate as aT, FieldValidateContext as aU, FieldValue as aV, FieldsInput as aW, FieldsValue as aX, FilterOptionsArgs as aY, FindAfterArgs as aZ, FindArgs as a_, BuiltinUsersCollection as aa, BundleHints as ab, CliCommand as ac, CollectionAccess as ad, CollectionAdmin as ae, CollectionConfig as af, CollectionDocument as ag, CollectionHooks as ah, CollectionListAdmin as ai, CollectionSearch as aj, CollectionSlug as ak, ConfigIssue as am, ContainerAdmin as an, CreateInput as ao, CreateMigrationResult as ap, CursorDocs as aq, CustomField as ar, CustomFieldTypes as as, DashboardWidget as at, Database as au, DatabaseAdapter as av, DatabaseInitArgs as aw, DatabaseTransfer as ax, DateField as ay, DepthOptions as az, FilterOptions as b, SchemaMode as b$, FolderLevel as b0, FolderPermissions as b1, GlobalAccess as b3, GlobalConfig as b4, GlobalDocument as b5, GlobalHooks as b6, GlobalInput as b7, GlobalSlug as b8, GroupField as b9, OutboxRow as bA, PasswordEmail as bC, PasswordEmailArgs as bD, PasswordEmailFn as bE, PasswordLinkOptions as bF, Plugin as bH, PluginInfo as bI, PluginTypes as bJ, PreviewTarget as bK, PreviewURL as bL, RawDocument as bO, ReadOptions as bP, RelationshipField as bQ, RequestContext as bR, RichTextDocument as bS, RichTextField as bT, Role as bU, RoleOperation as bV, RolePermissions as bW, RoutesConfig as bY, ScheduledPublish as bZ, ImageSize as ba, IncrementOptions as bb, InferCollection as bc, InferGlobal as bd, JobConfig as be, JsonField as bf, KeysetPage as bg, Label as bh, LayoutNode as bi, LivePreview as bj, LocaleOf as bk, LogDetails as bl, Logger as bm, LoginArgs as bn, LogsConfig as bo, MediaDocument as bp, MediaSize as bq, MembersConfig as br, MembersSignup as bs, MigrationInfo as bt, NavGroup as bu, NumberField as bw, OnRequest as bx, OnRequestArgs as by, Operation as bz, CreateEasyCMSOptions as c, SearchAdapter as c0, SearchEntry as c1, SearchHits as c2, SearchOptions as c3, SearchQuery as c4, SearchResults as c5, SearchSetting as c6, SelectField as c7, SelectOption as c8, Session as c9, VersionSummary as cA, VersionsConfig as cB, WebhookConfig as cD, WebhookEvent as cE, Where as cF, WhereOperators as cG, SidebarPanel as ca, SignupArgs as cb, SignupResult as cc, SlugField as ce, SlugOf as cf, SsoOutcome as cg, SsoProviderRef as ch, StorageAdapter as ci, StoredFile as cj, TelemetryConfig as cl, TextField as cm, TextareaField as cn, TypedPlugin as cp, UncountedDocs as cr, UpdateInput as cs, UpdateOptions as ct, UploadConfig as cu, UploadField as cv, UploadFromURLConfig as cw, UserIdentity as cx, Version as cz, EmailSetting as d, Access as f, AccessArgs as g, AccessOptions as h, AdminBrand as i, AdminCommand as j, AdminComponent as k, AdminConfig as l, AdminFieldArgs as m, AdminIcon as n, AdminLocale as o, AdminPage as p, AdminRole as q, AdminRoleChange as r, AdminRoleField as s, AdminRoleTarget as t, AdminRoleView as u, AdminRoles as v, AdminSso as w, AdminSwitcher as x, AdminViewAccess as y, AfterChangeHook as z };
|
|
3742
|
+
export { ADAPTER_API_VERSION as A, EasyCMS as E, AuditLog as S, Auth as V, EasyCMSError as aD, FIELD_TYPES as aK, ConfigError as al, ForbiddenError as b2, PLUGIN_API_VERSION as bB, PayloadTooLargeError as bG, QueryError as bM, ROLE_OPERATIONS as bN, Roles as bX, SchemaError as b_, NotFoundError as bv, API_KEYS as c$, WEBHOOK_EVENTS as cC, anyone as cH, consoleEmail as cI, consoleLogger as cJ, createEasyCMS as cK, defineConfig as cL, defineFieldType as cM, definePlugin as cN, diskStorage as cO, hasRows as cP, isAdmin as cQ, isHasMany as cR, isSignedIn as cS, isStaff as cT, isSystemAdmin as cU, jsonLogger as cV, memorySearch as cW, mimeAllowedBy as cX, rowFields as cY, silentLogger as cZ, uniqueWithinOf as c_, SingleSignOn as cd, TRIGRAM_LIKE_MIN as ck, TooManyRequestsError as co, UnauthorizedError as cq, ValidationError as cy, API_KEY_GLOBAL_OPERATIONS as d0, API_KEY_OPERATIONS as d1, DEFAULT_MAX_VERSIONS as d2, DEFAULT_PASSWORD_EMAILS as d3, FIELD_TYPE_BASES as d4, FOLDER_LEVELS as d5, MediaFolders as d6, SSO_COOKIE as d7, codeOfStatus as d9, isJsonLogger as da, keyAllows as db, logFormat as dc, secretsOf as dd, ADMIN_ICONS as e };
|
|
3743
|
+
export type { AuthUser as $, AfterDeleteHook as B, Config as C, AfterReadHook as D, Field as F, ApiKeyContext as G, ApiKeyGlobalOperation as H, ID as I, ApiKeyOperation as J, ApiKeyPermissions as K, LocalizationConfig as L, ArrayField as M, AuditChange as N, AuditConfig as O, PaginatedDocs as P, AuditEntry as Q, ResolvedConfig as R, AuditPage as T, AuditVerification as U, WebhookPayload as W, AuthConfig as X, AuthProvider as Y, AuthProviderProfile as Z, AuthProviderRequest as _, FieldError as a, FindOptions as a$, BackupsConfig as a0, BeforeChangeHook as a1, BeforeDeleteHook as a2, BeforeValidateHook as a3, Block as a4, BlocksField as a5, BooleanField as a6, BuiltinApiKeysCollection as a7, BuiltinMediaCollection as a8, BuiltinMediaFoldersCollection as a9, DirectUpload as aA, DiskStorageOptions as aB, DocumentOf as aC, EmailAdapter as aE, EmailField as aF, EmailMessage as aG, Endpoint as aH, EndpointRequest as aI, ErrorCode as aJ, FieldAccess as aL, FieldAccessFn as aM, FieldAdmin as aN, FieldRule as aO, FieldType as aP, FieldTypeBase as aQ, FieldTypeDefinition as aR, FieldTypeValidateContext as aS, FieldValidate as aT, FieldValidateContext as aU, FieldValue as aV, FieldsInput as aW, FieldsValue as aX, FilterOptionsArgs as aY, FindAfterArgs as aZ, FindArgs as a_, BuiltinUsersCollection as aa, BundleHints as ab, CliCommand as ac, CollectionAccess as ad, CollectionAdmin as ae, CollectionConfig as af, CollectionDocument as ag, CollectionHooks as ah, CollectionListAdmin as ai, CollectionSearch as aj, CollectionSlug as ak, ConfigIssue as am, ContainerAdmin as an, CreateInput as ao, CreateMigrationResult as ap, CursorDocs as aq, CustomField as ar, CustomFieldTypes as as, DashboardWidget as at, Database as au, DatabaseAdapter as av, DatabaseInitArgs as aw, DatabaseTransfer as ax, DateField as ay, DepthOptions as az, FilterOptions as b, SchemaMode as b$, FolderLevel as b0, FolderPermissions as b1, GlobalAccess as b3, GlobalConfig as b4, GlobalDocument as b5, GlobalHooks as b6, GlobalInput as b7, GlobalSlug as b8, GroupField as b9, OutboxRow as bA, PasswordEmail as bC, PasswordEmailArgs as bD, PasswordEmailFn as bE, PasswordLinkOptions as bF, Plugin as bH, PluginInfo as bI, PluginTypes as bJ, PreviewTarget as bK, PreviewURL as bL, RawDocument as bO, ReadOptions as bP, RelationshipField as bQ, RequestContext as bR, RichTextDocument as bS, RichTextField as bT, Role as bU, RoleOperation as bV, RolePermissions as bW, RoutesConfig as bY, ScheduledPublish as bZ, ImageSize as ba, IncrementOptions as bb, InferCollection as bc, InferGlobal as bd, JobConfig as be, JsonField as bf, KeysetPage as bg, Label as bh, LayoutNode as bi, LivePreview as bj, LocaleOf as bk, LogDetails as bl, Logger as bm, LoginArgs as bn, LogsConfig as bo, MediaDocument as bp, MediaSize as bq, MembersConfig as br, MembersSignup as bs, MigrationInfo as bt, NavGroup as bu, NumberField as bw, OnRequest as bx, OnRequestArgs as by, Operation as bz, CreateEasyCMSOptions as c, SearchAdapter as c0, SearchEntry as c1, SearchHits as c2, SearchOptions as c3, SearchQuery as c4, SearchResults as c5, SearchSetting as c6, SelectField as c7, SelectOption as c8, Session as c9, VersionSummary as cA, VersionsConfig as cB, WebhookConfig as cD, WebhookEvent as cE, Where as cF, WhereOperators as cG, SidebarPanel as ca, SignupArgs as cb, SignupResult as cc, SlugField as ce, SlugOf as cf, SsoOutcome as cg, SsoProviderRef as ch, StorageAdapter as ci, StoredFile as cj, TelemetryConfig as cl, TextField as cm, TextareaField as cn, TypedPlugin as cp, UncountedDocs as cr, UpdateInput as cs, UpdateOptions as ct, UploadConfig as cu, UploadField as cv, UploadFromURLConfig as cw, UserIdentity as cx, Version as cz, EmailSetting as d, Secrets as d8, Access as f, AccessArgs as g, AccessOptions as h, AdminBrand as i, AdminCommand as j, AdminComponent as k, AdminConfig as l, AdminFieldArgs as m, AdminIcon as n, AdminLocale as o, AdminPage as p, AdminRole as q, AdminRoleChange as r, AdminRoleField as s, AdminRoleTarget as t, AdminRoleView as u, AdminRoles as v, AdminSso as w, AdminSwitcher as x, AdminViewAccess as y, AfterChangeHook as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { I as ID, C as Config, E as EasyCMS, L as LocalizationConfig, F as Field, a as FieldError, b as FilterOptions, c as CreateEasyCMSOptions, R as ResolvedConfig } from './config-
|
|
1
|
+
import { I as ID, C as Config, E as EasyCMS, L as LocalizationConfig, F as Field, a as FieldError, b as FilterOptions, c as CreateEasyCMSOptions, R as ResolvedConfig } from './config-U1mRwD4t.js';
|
|
2
2
|
|
|
3
3
|
/** Decrypts a file from `encryptBackup`; throws for another key or a changed file. */
|
|
4
4
|
declare function decryptBackup(data: Uint8Array, encryptionKey: string): Uint8Array;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { P as PaginatedDocs, I as ID, d as EmailSetting, W as WebhookPayload } from './config-
|
|
2
|
-
export { A as ADAPTER_API_VERSION, e as ADMIN_ICONS, f as Access, g as AccessArgs, h as AccessOptions, i as AdminBrand, j as AdminCommand, k as AdminComponent, l as AdminConfig, m as AdminFieldArgs, n as AdminIcon, o as AdminLocale, p as AdminPage, q as AdminRole, r as AdminRoleChange, s as AdminRoleField, t as AdminRoleTarget, u as AdminRoleView, v as AdminRoles, w as AdminSso, x as AdminSwitcher, y as AdminViewAccess, z as AfterChangeHook, B as AfterDeleteHook, D as AfterReadHook, G as ApiKeyContext, H as ApiKeyGlobalOperation, J as ApiKeyOperation, K as ApiKeyPermissions, M as ArrayField, N as AuditChange, O as AuditConfig, Q as AuditEntry, S as AuditLog, T as AuditPage, U as AuditVerification, V as Auth, X as AuthConfig, Y as AuthProvider, Z as AuthProviderProfile, _ as AuthProviderRequest, $ as AuthUser, a0 as BackupsConfig, a1 as BeforeChangeHook, a2 as BeforeDeleteHook, a3 as BeforeValidateHook, a4 as Block, a5 as BlocksField, a6 as BooleanField, a7 as BuiltinApiKeysCollection, a8 as BuiltinMediaCollection, a9 as BuiltinMediaFoldersCollection, aa as BuiltinUsersCollection, ab as BundleHints, ac as CliCommand, ad as CollectionAccess, ae as CollectionAdmin, af as CollectionConfig, ag as CollectionDocument, ah as CollectionHooks, ai as CollectionListAdmin, aj as CollectionSearch, ak as CollectionSlug, C as Config, al as ConfigError, am as ConfigIssue, an as ContainerAdmin, c as CreateEasyCMSOptions, ao as CreateInput, ap as CreateMigrationResult, aq as CursorDocs, ar as CustomField, as as CustomFieldTypes, at as DashboardWidget, au as Database, av as DatabaseAdapter, aw as DatabaseInitArgs, ax as DatabaseTransfer, ay as DateField, az as DepthOptions, aA as DirectUpload, aB as DiskStorageOptions, aC as DocumentOf, E as EasyCMS, aD as EasyCMSError, aE as EmailAdapter, aF as EmailField, aG as EmailMessage, aH as Endpoint, aI as EndpointRequest, aJ as ErrorCode, aK as FIELD_TYPES, F as Field, aL as FieldAccess, aM as FieldAccessFn, aN as FieldAdmin, a as FieldError, aO as FieldRule, aP as FieldType, aQ as FieldTypeBase, aR as FieldTypeDefinition, aS as FieldTypeValidateContext, aT as FieldValidate, aU as FieldValidateContext, aV as FieldValue, aW as FieldsInput, aX as FieldsValue, b as FilterOptions, aY as FilterOptionsArgs, aZ as FindAfterArgs, a_ as FindArgs, a$ as FindOptions, b0 as FolderLevel, b1 as FolderPermissions, b2 as ForbiddenError, b3 as GlobalAccess, b4 as GlobalConfig, b5 as GlobalDocument, b6 as GlobalHooks, b7 as GlobalInput, b8 as GlobalSlug, b9 as GroupField, ba as ImageSize, bb as IncrementOptions, bc as InferCollection, bd as InferGlobal, be as JobConfig, bf as JsonField, bg as KeysetPage, bh as Label, bi as LayoutNode, bj as LivePreview, bk as LocaleOf, L as LocalizationConfig, bl as LogDetails, bm as Logger, bn as LoginArgs, bo as LogsConfig, bp as MediaDocument, bq as MediaSize, br as MembersConfig, bs as MembersSignup, bt as MigrationInfo, bu as NavGroup, bv as NotFoundError, bw as NumberField, bx as OnRequest, by as OnRequestArgs, bz as Operation, bA as OutboxRow, bB as PLUGIN_API_VERSION, bC as PasswordEmail, bD as PasswordEmailArgs, bE as PasswordEmailFn, bF as PasswordLinkOptions, bG as PayloadTooLargeError, bH as Plugin, bI as PluginInfo, bJ as PluginTypes, bK as PreviewTarget, bL as PreviewURL, bM as QueryError, bN as ROLE_OPERATIONS, bO as RawDocument, bP as ReadOptions, bQ as RelationshipField, bR as RequestContext, R as ResolvedConfig, bS as RichTextDocument, bT as RichTextField, bU as Role, bV as RoleOperation, bW as RolePermissions, bX as Roles, bY as RoutesConfig, bZ as ScheduledPublish, b_ as SchemaError, b$ as SchemaMode, c0 as SearchAdapter, c1 as SearchEntry, c2 as SearchHits, c3 as SearchOptions, c4 as SearchQuery, c5 as SearchResults, c6 as SearchSetting, c7 as SelectField, c8 as SelectOption, c9 as Session, ca as SidebarPanel, cb as SignupArgs, cc as SignupResult, cd as SingleSignOn, ce as SlugField, cf as SlugOf, cg as SsoOutcome, ch as SsoProviderRef, ci as StorageAdapter, cj as StoredFile, ck as TRIGRAM_LIKE_MIN, cl as TelemetryConfig, cm as TextField, cn as TextareaField, co as TooManyRequestsError, cp as TypedPlugin, cq as UnauthorizedError, cr as UncountedDocs, cs as UpdateInput, ct as UpdateOptions, cu as UploadConfig, cv as UploadField, cw as UploadFromURLConfig, cx as UserIdentity, cy as ValidationError, cz as Version, cA as VersionSummary, cB as VersionsConfig, cC as WEBHOOK_EVENTS, cD as WebhookConfig, cE as WebhookEvent, cF as Where, cG as WhereOperators, cH as anyone, cI as consoleEmail, cJ as consoleLogger, cK as createEasyCMS, cL as defineConfig, cM as defineFieldType, cN as definePlugin, cO as diskStorage, cP as hasRows, cQ as isAdmin, cR as isHasMany, cS as isSignedIn, cT as isStaff, cU as isSystemAdmin, cV as jsonLogger, cW as memorySearch, cX as mimeAllowedBy, cY as rowFields, cZ as silentLogger, c_ as uniqueWithinOf } from './config-
|
|
3
|
-
export { A as ADMIN_API_VERSION, a as AdminCollection, b as AdminComponentRef, c as AdminElementProps, d as AdminField, e as AdminGlobal, f as AdminLayoutNode, g as AdminNavGroup, h as AdminNavItem, i as AdminNavNode, j as AdminPageRef, k as AdminPageRoute, l as AdminSchema, m as AdminWidgetRef, S as SetFieldDetail, w as warnDeprecated } from './plugin-
|
|
4
|
-
export { A as AdminBackup, a as AdminBackups, b as ApiHandlerOptions, B as BackupState, L as LoadConfigOptions, R as RestHandler, c as RestHandlerOptions, d as createApiHandler, e as createRestHandler, l as loadConfig, r as resolveConfig, s as sharedEasyCMS, f as slugify } from './handler-
|
|
1
|
+
import { P as PaginatedDocs, I as ID, d as EmailSetting, W as WebhookPayload } from './config-U1mRwD4t.js';
|
|
2
|
+
export { A as ADAPTER_API_VERSION, e as ADMIN_ICONS, f as Access, g as AccessArgs, h as AccessOptions, i as AdminBrand, j as AdminCommand, k as AdminComponent, l as AdminConfig, m as AdminFieldArgs, n as AdminIcon, o as AdminLocale, p as AdminPage, q as AdminRole, r as AdminRoleChange, s as AdminRoleField, t as AdminRoleTarget, u as AdminRoleView, v as AdminRoles, w as AdminSso, x as AdminSwitcher, y as AdminViewAccess, z as AfterChangeHook, B as AfterDeleteHook, D as AfterReadHook, G as ApiKeyContext, H as ApiKeyGlobalOperation, J as ApiKeyOperation, K as ApiKeyPermissions, M as ArrayField, N as AuditChange, O as AuditConfig, Q as AuditEntry, S as AuditLog, T as AuditPage, U as AuditVerification, V as Auth, X as AuthConfig, Y as AuthProvider, Z as AuthProviderProfile, _ as AuthProviderRequest, $ as AuthUser, a0 as BackupsConfig, a1 as BeforeChangeHook, a2 as BeforeDeleteHook, a3 as BeforeValidateHook, a4 as Block, a5 as BlocksField, a6 as BooleanField, a7 as BuiltinApiKeysCollection, a8 as BuiltinMediaCollection, a9 as BuiltinMediaFoldersCollection, aa as BuiltinUsersCollection, ab as BundleHints, ac as CliCommand, ad as CollectionAccess, ae as CollectionAdmin, af as CollectionConfig, ag as CollectionDocument, ah as CollectionHooks, ai as CollectionListAdmin, aj as CollectionSearch, ak as CollectionSlug, C as Config, al as ConfigError, am as ConfigIssue, an as ContainerAdmin, c as CreateEasyCMSOptions, ao as CreateInput, ap as CreateMigrationResult, aq as CursorDocs, ar as CustomField, as as CustomFieldTypes, at as DashboardWidget, au as Database, av as DatabaseAdapter, aw as DatabaseInitArgs, ax as DatabaseTransfer, ay as DateField, az as DepthOptions, aA as DirectUpload, aB as DiskStorageOptions, aC as DocumentOf, E as EasyCMS, aD as EasyCMSError, aE as EmailAdapter, aF as EmailField, aG as EmailMessage, aH as Endpoint, aI as EndpointRequest, aJ as ErrorCode, aK as FIELD_TYPES, F as Field, aL as FieldAccess, aM as FieldAccessFn, aN as FieldAdmin, a as FieldError, aO as FieldRule, aP as FieldType, aQ as FieldTypeBase, aR as FieldTypeDefinition, aS as FieldTypeValidateContext, aT as FieldValidate, aU as FieldValidateContext, aV as FieldValue, aW as FieldsInput, aX as FieldsValue, b as FilterOptions, aY as FilterOptionsArgs, aZ as FindAfterArgs, a_ as FindArgs, a$ as FindOptions, b0 as FolderLevel, b1 as FolderPermissions, b2 as ForbiddenError, b3 as GlobalAccess, b4 as GlobalConfig, b5 as GlobalDocument, b6 as GlobalHooks, b7 as GlobalInput, b8 as GlobalSlug, b9 as GroupField, ba as ImageSize, bb as IncrementOptions, bc as InferCollection, bd as InferGlobal, be as JobConfig, bf as JsonField, bg as KeysetPage, bh as Label, bi as LayoutNode, bj as LivePreview, bk as LocaleOf, L as LocalizationConfig, bl as LogDetails, bm as Logger, bn as LoginArgs, bo as LogsConfig, bp as MediaDocument, bq as MediaSize, br as MembersConfig, bs as MembersSignup, bt as MigrationInfo, bu as NavGroup, bv as NotFoundError, bw as NumberField, bx as OnRequest, by as OnRequestArgs, bz as Operation, bA as OutboxRow, bB as PLUGIN_API_VERSION, bC as PasswordEmail, bD as PasswordEmailArgs, bE as PasswordEmailFn, bF as PasswordLinkOptions, bG as PayloadTooLargeError, bH as Plugin, bI as PluginInfo, bJ as PluginTypes, bK as PreviewTarget, bL as PreviewURL, bM as QueryError, bN as ROLE_OPERATIONS, bO as RawDocument, bP as ReadOptions, bQ as RelationshipField, bR as RequestContext, R as ResolvedConfig, bS as RichTextDocument, bT as RichTextField, bU as Role, bV as RoleOperation, bW as RolePermissions, bX as Roles, bY as RoutesConfig, bZ as ScheduledPublish, b_ as SchemaError, b$ as SchemaMode, c0 as SearchAdapter, c1 as SearchEntry, c2 as SearchHits, c3 as SearchOptions, c4 as SearchQuery, c5 as SearchResults, c6 as SearchSetting, c7 as SelectField, c8 as SelectOption, c9 as Session, ca as SidebarPanel, cb as SignupArgs, cc as SignupResult, cd as SingleSignOn, ce as SlugField, cf as SlugOf, cg as SsoOutcome, ch as SsoProviderRef, ci as StorageAdapter, cj as StoredFile, ck as TRIGRAM_LIKE_MIN, cl as TelemetryConfig, cm as TextField, cn as TextareaField, co as TooManyRequestsError, cp as TypedPlugin, cq as UnauthorizedError, cr as UncountedDocs, cs as UpdateInput, ct as UpdateOptions, cu as UploadConfig, cv as UploadField, cw as UploadFromURLConfig, cx as UserIdentity, cy as ValidationError, cz as Version, cA as VersionSummary, cB as VersionsConfig, cC as WEBHOOK_EVENTS, cD as WebhookConfig, cE as WebhookEvent, cF as Where, cG as WhereOperators, cH as anyone, cI as consoleEmail, cJ as consoleLogger, cK as createEasyCMS, cL as defineConfig, cM as defineFieldType, cN as definePlugin, cO as diskStorage, cP as hasRows, cQ as isAdmin, cR as isHasMany, cS as isSignedIn, cT as isStaff, cU as isSystemAdmin, cV as jsonLogger, cW as memorySearch, cX as mimeAllowedBy, cY as rowFields, cZ as silentLogger, c_ as uniqueWithinOf } from './config-U1mRwD4t.js';
|
|
3
|
+
export { A as ADMIN_API_VERSION, a as AdminCollection, b as AdminComponentRef, c as AdminElementProps, d as AdminField, e as AdminGlobal, f as AdminLayoutNode, g as AdminNavGroup, h as AdminNavItem, i as AdminNavNode, j as AdminPageRef, k as AdminPageRoute, l as AdminSchema, m as AdminWidgetRef, S as SetFieldDetail, w as warnDeprecated } from './plugin-DRV5HA4a.js';
|
|
4
|
+
export { A as AdminBackup, a as AdminBackups, b as ApiHandlerOptions, B as BackupState, L as LoadConfigOptions, R as RestHandler, c as RestHandlerOptions, d as createApiHandler, e as createRestHandler, l as loadConfig, r as resolveConfig, s as sharedEasyCMS, f as slugify } from './handler-D2y4WSSM.js';
|
|
5
5
|
export { FieldCondition, matchesCondition } from './conditions.js';
|
|
6
6
|
|
|
7
7
|
type DeliveryKind = 'webhook' | 'email';
|
package/dist/index.js
CHANGED
package/dist/internal.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Config, Database, EasyCMS, NavGroup, CollectionConfig, ID, RawDocument, Field, ResolvedConfig, ConfigIssue } from './index.js';
|
|
2
|
-
export { c$ as API_KEYS, d0 as API_KEY_GLOBAL_OPERATIONS, d1 as API_KEY_OPERATIONS, d2 as DEFAULT_MAX_VERSIONS, d3 as DEFAULT_PASSWORD_EMAILS, d4 as FIELD_TYPE_BASES, d5 as FOLDER_LEVELS, d6 as MediaFolders, d7 as SSO_COOKIE, ck as TRIGRAM_LIKE_MIN,
|
|
3
|
-
export { C as CONFIG_FILE_NAMES, g as CSRF_COOKIE, h as CSRF_HEADER, D as DEFAULT_ADMIN_PATH, i as DEFAULT_API_PATH, j as DEFAULT_MAX_FILE_SIZE, k as DEFAULT_TOKEN_EXPIRATION, m as applyDefaults, n as applyPlugins, o as createRootEndpointHandler, p as decryptBackup, q as fillMissing, t as findConfigFile, u as forwardedClientIp, v as generateSlugs, w as importConfig, x as isEmail, y as mergeForUpdate, z as parseId, E as platformClientIp, F as readCookie, G as validateFields, H as writeBackupFile } from './handler-
|
|
2
|
+
export { c$ as API_KEYS, d0 as API_KEY_GLOBAL_OPERATIONS, d1 as API_KEY_OPERATIONS, d2 as DEFAULT_MAX_VERSIONS, d3 as DEFAULT_PASSWORD_EMAILS, d4 as FIELD_TYPE_BASES, d5 as FOLDER_LEVELS, d6 as MediaFolders, d7 as SSO_COOKIE, d8 as Secrets, ck as TRIGRAM_LIKE_MIN, d9 as codeOfStatus, cP as hasRows, cR as isHasMany, da as isJsonLogger, db as keyAllows, dc as logFormat, cX as mimeAllowedBy, cY as rowFields, dd as secretsOf, c_ as uniqueWithinOf } from './config-U1mRwD4t.js';
|
|
3
|
+
export { C as CONFIG_FILE_NAMES, g as CSRF_COOKIE, h as CSRF_HEADER, D as DEFAULT_ADMIN_PATH, i as DEFAULT_API_PATH, j as DEFAULT_MAX_FILE_SIZE, k as DEFAULT_TOKEN_EXPIRATION, m as applyDefaults, n as applyPlugins, o as createRootEndpointHandler, p as decryptBackup, q as fillMissing, t as findConfigFile, u as forwardedClientIp, v as generateSlugs, w as importConfig, x as isEmail, y as mergeForUpdate, z as parseId, E as platformClientIp, F as readCookie, G as validateFields, H as writeBackupFile } from './handler-D2y4WSSM.js';
|
|
4
4
|
export { conditionIssues } from './conditions.js';
|
|
5
5
|
|
|
6
6
|
/** The session cookie the REST API sets at login. */
|
|
@@ -112,7 +112,7 @@ declare function formToken(secret: string, purpose: string, now?: number): strin
|
|
|
112
112
|
* Checks a token: `ok` when the form was loaded at least `minTime` ms ago (people take a few
|
|
113
113
|
* seconds; bots don't) and less than a day ago.
|
|
114
114
|
*/
|
|
115
|
-
declare function checkFormToken(
|
|
115
|
+
declare function checkFormToken(secrets: string | readonly string[], purpose: string, token: unknown, minTime: number, now?: number): 'ok' | 'invalid' | 'too-fast' | 'expired';
|
|
116
116
|
/**
|
|
117
117
|
* Rate-limit keys for `subject` (e.g. an IP and a form) in the current and previous window of
|
|
118
118
|
* `window` seconds. A hash with the window in it: no IP is kept, and keys from different
|
package/dist/internal.js
CHANGED
|
@@ -52,12 +52,13 @@ import {
|
|
|
52
52
|
rateKeys,
|
|
53
53
|
readCookie,
|
|
54
54
|
resolveAdminModule,
|
|
55
|
+
secretsOf,
|
|
55
56
|
validateConfig,
|
|
56
57
|
validateFields,
|
|
57
58
|
verifyPassword,
|
|
58
59
|
verifyTurnstile,
|
|
59
60
|
writeBackupFile
|
|
60
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-FZCQXH5M.js";
|
|
61
62
|
import {
|
|
62
63
|
conditionIssues
|
|
63
64
|
} from "./chunk-U266LV4M.js";
|
|
@@ -311,6 +312,7 @@ export {
|
|
|
311
312
|
readCookie,
|
|
312
313
|
resolveAdminModule,
|
|
313
314
|
rowFields,
|
|
315
|
+
secretsOf,
|
|
314
316
|
singularize,
|
|
315
317
|
sniffMimeType,
|
|
316
318
|
uniqueWithinOf,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bh as Label, F as Field, I as ID, o as AdminLocale, x as AdminSwitcher, ch as SsoProviderRef } from './config-
|
|
1
|
+
import { bh as Label, F as Field, I as ID, o as AdminLocale, x as AdminSwitcher, ch as SsoProviderRef } from './config-U1mRwD4t.js';
|
|
2
2
|
import { FieldCondition } from './conditions.js';
|
|
3
3
|
|
|
4
4
|
/** A Web Component from an admin module, with its `props` (plain JSON). */
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { e as ADMIN_ICONS, k as AdminComponent, n as AdminIcon, $ as AuthUser, ac as CliCommand, ad as CollectionAccess, af as CollectionConfig, C as Config, al as ConfigError, at as DashboardWidget, aD as EasyCMSError, aH as Endpoint, aI as EndpointRequest, aJ as ErrorCode, F as Field, aL as FieldAccess, a as FieldError, aQ as FieldTypeBase, aR as FieldTypeDefinition, aS as FieldTypeValidateContext, b2 as ForbiddenError, b4 as GlobalConfig, I as ID, be as JobConfig, bh as Label, bu as NavGroup, bv as NotFoundError, bx as OnRequest, by as OnRequestArgs, bB as PLUGIN_API_VERSION, bG as PayloadTooLargeError, bH as Plugin, bI as PluginInfo, bJ as PluginTypes, bM as QueryError, bR as RequestContext, co as TooManyRequestsError, cp as TypedPlugin, cq as UnauthorizedError, cy as ValidationError, cF as Where, cM as defineFieldType, cN as definePlugin } from './config-
|
|
2
|
-
export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-
|
|
1
|
+
export { e as ADMIN_ICONS, k as AdminComponent, n as AdminIcon, $ as AuthUser, ac as CliCommand, ad as CollectionAccess, af as CollectionConfig, C as Config, al as ConfigError, at as DashboardWidget, aD as EasyCMSError, aH as Endpoint, aI as EndpointRequest, aJ as ErrorCode, F as Field, aL as FieldAccess, a as FieldError, aQ as FieldTypeBase, aR as FieldTypeDefinition, aS as FieldTypeValidateContext, b2 as ForbiddenError, b4 as GlobalConfig, I as ID, be as JobConfig, bh as Label, bu as NavGroup, bv as NotFoundError, bx as OnRequest, by as OnRequestArgs, bB as PLUGIN_API_VERSION, bG as PayloadTooLargeError, bH as Plugin, bI as PluginInfo, bJ as PluginTypes, bM as QueryError, bR as RequestContext, co as TooManyRequestsError, cp as TypedPlugin, cq as UnauthorizedError, cy as ValidationError, cF as Where, cM as defineFieldType, cN as definePlugin } from './config-U1mRwD4t.js';
|
|
2
|
+
export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-DRV5HA4a.js';
|
|
3
3
|
import './conditions.js';
|