@dyrected/core 2.5.42 → 2.5.44
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-config-2vF6W56z.d.cts +1335 -0
- package/dist/app-config-2vF6W56z.d.ts +1335 -0
- package/dist/app-config-3XBnu9Xz.d.cts +1379 -0
- package/dist/app-config-3XBnu9Xz.d.ts +1379 -0
- package/dist/chunk-44O2LDPT.js +1513 -0
- package/dist/index.cjs +86 -95
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16 -10
- package/dist/server.cjs +495 -302
- package/dist/server.d.cts +34 -1
- package/dist/server.d.ts +34 -1
- package/dist/server.js +423 -223
- package/dist/where-sanitizer-7Q4JXMX6.js +57 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
runCollectionHooks,
|
|
14
14
|
saveWorkflowDraft,
|
|
15
15
|
transitionWorkflow
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-44O2LDPT.js";
|
|
17
17
|
|
|
18
|
-
// src/app.
|
|
18
|
+
// src/app.js
|
|
19
19
|
import { Hono } from "hono";
|
|
20
20
|
import { cors } from "hono/cors";
|
|
21
21
|
import { requestId } from "hono/request-id";
|
|
22
22
|
|
|
23
|
-
// src/services/defaults.service.
|
|
23
|
+
// src/services/defaults.service.js
|
|
24
24
|
var DefaultsService = class {
|
|
25
25
|
/**
|
|
26
26
|
* Recursively apply default values to a data object based on field definitions.
|
|
@@ -28,12 +28,14 @@ var DefaultsService = class {
|
|
|
28
28
|
static apply(fields, data = {}) {
|
|
29
29
|
const result = { ...data || {} };
|
|
30
30
|
fields.forEach((field) => {
|
|
31
|
-
if (field.type === "join")
|
|
31
|
+
if (field.type === "join")
|
|
32
|
+
return;
|
|
32
33
|
if (field.type === "row" && field.fields) {
|
|
33
34
|
Object.assign(result, this.apply(field.fields, data));
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
|
-
if (!field.name)
|
|
37
|
+
if (!field.name)
|
|
38
|
+
return;
|
|
37
39
|
let value = result[field.name];
|
|
38
40
|
if ((value === void 0 || value === null) && field.renameTo) {
|
|
39
41
|
const legacyValue = result[field.renameTo];
|
|
@@ -46,9 +48,12 @@ var DefaultsService = class {
|
|
|
46
48
|
if (field.defaultValue !== void 0) {
|
|
47
49
|
result[field.name] = field.defaultValue;
|
|
48
50
|
} else {
|
|
49
|
-
if (field.type === "boolean")
|
|
50
|
-
|
|
51
|
-
else if (field.type === "
|
|
51
|
+
if (field.type === "boolean")
|
|
52
|
+
result[field.name] = false;
|
|
53
|
+
else if (field.type === "array")
|
|
54
|
+
result[field.name] = [];
|
|
55
|
+
else if (field.type === "multiSelect")
|
|
56
|
+
result[field.name] = [];
|
|
52
57
|
else if (field.type === "object") {
|
|
53
58
|
result[field.name] = this.apply(field.fields || [], {});
|
|
54
59
|
}
|
|
@@ -63,7 +68,7 @@ var DefaultsService = class {
|
|
|
63
68
|
}
|
|
64
69
|
};
|
|
65
70
|
|
|
66
|
-
// src/services/population.service.
|
|
71
|
+
// src/services/population.service.js
|
|
67
72
|
var PopulationService = class {
|
|
68
73
|
db;
|
|
69
74
|
collections;
|
|
@@ -116,29 +121,31 @@ var PopulationService = class {
|
|
|
116
121
|
Object.assign(populatedDoc, rowPopulated);
|
|
117
122
|
continue;
|
|
118
123
|
}
|
|
119
|
-
if (!field.name)
|
|
124
|
+
if (!field.name)
|
|
125
|
+
continue;
|
|
120
126
|
const value = populatedDoc[field.name];
|
|
121
127
|
if (field.type === "relationship" && field.relationTo && value) {
|
|
122
128
|
const relatedCollection = this.collections.find((c) => c.slug === field.relationTo);
|
|
123
|
-
if (!relatedCollection)
|
|
129
|
+
if (!relatedCollection)
|
|
130
|
+
continue;
|
|
124
131
|
if (Array.isArray(value)) {
|
|
125
|
-
populatedDoc[field.name] = await Promise.all(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
})
|
|
141
|
-
);
|
|
132
|
+
populatedDoc[field.name] = await Promise.all(value.map(async (id) => {
|
|
133
|
+
if (!id)
|
|
134
|
+
return id;
|
|
135
|
+
let doc = id;
|
|
136
|
+
if (typeof id === "string") {
|
|
137
|
+
doc = await this.db.findOne({ collection: field.relationTo, id });
|
|
138
|
+
}
|
|
139
|
+
if (!doc || typeof doc !== "object")
|
|
140
|
+
return id;
|
|
141
|
+
const docWithDefaults = DefaultsService.apply(relatedCollection.fields, doc);
|
|
142
|
+
return this.populate({
|
|
143
|
+
data: docWithDefaults,
|
|
144
|
+
fields: relatedCollection.fields,
|
|
145
|
+
currentDepth: currentDepth + 1,
|
|
146
|
+
maxDepth
|
|
147
|
+
});
|
|
148
|
+
}));
|
|
142
149
|
} else if (value) {
|
|
143
150
|
let doc = value;
|
|
144
151
|
if (typeof value === "string") {
|
|
@@ -187,18 +194,17 @@ var PopulationService = class {
|
|
|
187
194
|
});
|
|
188
195
|
}
|
|
189
196
|
if (field.type === "blocks" && field.blocks && Array.isArray(value)) {
|
|
190
|
-
populatedDoc[field.name] = await Promise.all(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
);
|
|
197
|
+
populatedDoc[field.name] = await Promise.all(value.map(async (blockData) => {
|
|
198
|
+
const blockConfig = field.blocks.find((b) => b.slug === blockData.blockType);
|
|
199
|
+
if (!blockConfig)
|
|
200
|
+
return blockData;
|
|
201
|
+
return this.populate({
|
|
202
|
+
data: blockData,
|
|
203
|
+
fields: blockConfig.fields,
|
|
204
|
+
currentDepth,
|
|
205
|
+
maxDepth
|
|
206
|
+
});
|
|
207
|
+
}));
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
210
|
return populatedDoc;
|
|
@@ -207,7 +213,8 @@ var PopulationService = class {
|
|
|
207
213
|
* Helper to populate a PaginatedResult
|
|
208
214
|
*/
|
|
209
215
|
async populateResult(result, fields, maxDepth) {
|
|
210
|
-
if (maxDepth <= 0)
|
|
216
|
+
if (maxDepth <= 0)
|
|
217
|
+
return result;
|
|
211
218
|
const populatedDocs = await this.populate({
|
|
212
219
|
data: result.docs,
|
|
213
220
|
fields,
|
|
@@ -221,7 +228,7 @@ var PopulationService = class {
|
|
|
221
228
|
}
|
|
222
229
|
};
|
|
223
230
|
|
|
224
|
-
// src/services/audit.service.
|
|
231
|
+
// src/services/audit.service.js
|
|
225
232
|
var AuditService = class {
|
|
226
233
|
/**
|
|
227
234
|
* Writes a single entry to the __audit collection.
|
|
@@ -249,7 +256,7 @@ var AuditService = class {
|
|
|
249
256
|
}
|
|
250
257
|
};
|
|
251
258
|
|
|
252
|
-
// src/auth/password.
|
|
259
|
+
// src/auth/password.js
|
|
253
260
|
import { promisify } from "util";
|
|
254
261
|
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
255
262
|
var scryptAsync = promisify(scrypt);
|
|
@@ -262,23 +269,23 @@ async function hashPassword(plain) {
|
|
|
262
269
|
}
|
|
263
270
|
async function verifyPassword(plain, stored) {
|
|
264
271
|
const [salt, storedHash] = stored.split(":");
|
|
265
|
-
if (!salt || !storedHash)
|
|
272
|
+
if (!salt || !storedHash)
|
|
273
|
+
return false;
|
|
266
274
|
const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
|
|
267
275
|
const storedBuffer = Buffer.from(storedHash, "hex");
|
|
268
|
-
if (derivedKey.length !== storedBuffer.length)
|
|
276
|
+
if (derivedKey.length !== storedBuffer.length)
|
|
277
|
+
return false;
|
|
269
278
|
return timingSafeEqual(derivedKey, storedBuffer);
|
|
270
279
|
}
|
|
271
280
|
|
|
272
|
-
// src/utils/readonly-db.
|
|
281
|
+
// src/utils/readonly-db.js
|
|
273
282
|
var WRITE_METHODS = ["create", "update", "delete", "updateGlobal", "execute"];
|
|
274
283
|
function createReadonlyDb(db) {
|
|
275
284
|
return new Proxy(db, {
|
|
276
285
|
get(target, prop) {
|
|
277
286
|
if (WRITE_METHODS.includes(prop)) {
|
|
278
287
|
return () => {
|
|
279
|
-
throw new Error(
|
|
280
|
-
`[dyrected] Write operation "${String(prop)}" is not allowed in this hook phase. Use afterChange/afterDelete hooks for write operations.`
|
|
281
|
-
);
|
|
288
|
+
throw new Error(`[dyrected] Write operation "${String(prop)}" is not allowed in this hook phase. Use afterChange/afterDelete hooks for write operations.`);
|
|
282
289
|
};
|
|
283
290
|
}
|
|
284
291
|
return Reflect.get(target, prop);
|
|
@@ -286,11 +293,13 @@ function createReadonlyDb(db) {
|
|
|
286
293
|
});
|
|
287
294
|
}
|
|
288
295
|
|
|
289
|
-
// src/auth/jexl.
|
|
296
|
+
// src/auth/jexl.js
|
|
290
297
|
import jexl from "jexl";
|
|
291
298
|
async function evaluateAccess(expression, context) {
|
|
292
|
-
if (expression === void 0 || expression === null)
|
|
293
|
-
|
|
299
|
+
if (expression === void 0 || expression === null)
|
|
300
|
+
return false;
|
|
301
|
+
if (typeof expression === "boolean")
|
|
302
|
+
return expression;
|
|
294
303
|
try {
|
|
295
304
|
const result = await jexl.eval(expression, context);
|
|
296
305
|
return !!result;
|
|
@@ -300,16 +309,61 @@ async function evaluateAccess(expression, context) {
|
|
|
300
309
|
}
|
|
301
310
|
}
|
|
302
311
|
|
|
303
|
-
// src/controllers/collection.controller.
|
|
312
|
+
// src/controllers/collection.controller.js
|
|
304
313
|
var CollectionController = class {
|
|
305
314
|
collection;
|
|
306
315
|
constructor(collection) {
|
|
307
316
|
this.collection = collection;
|
|
308
317
|
}
|
|
318
|
+
getDelegatedProvider(c) {
|
|
319
|
+
const config = c.get("config");
|
|
320
|
+
const authCollectionSlug = config.adminAuth?.collectionSlug || "__admins";
|
|
321
|
+
if (this.collection.slug !== authCollectionSlug) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
return config.adminAuth?.providers?.find((p) => p.members) || null;
|
|
325
|
+
}
|
|
309
326
|
async find(c) {
|
|
310
327
|
const config = c.get("config");
|
|
311
328
|
const db = config.db;
|
|
312
|
-
if (!db)
|
|
329
|
+
if (!db)
|
|
330
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
331
|
+
const provider = this.getDelegatedProvider(c);
|
|
332
|
+
if (provider && provider.members?.list) {
|
|
333
|
+
const limit2 = Number(c.req.query("limit")) || 10;
|
|
334
|
+
const page2 = Number(c.req.query("page")) || 1;
|
|
335
|
+
const sort2 = c.req.query("sort") || void 0;
|
|
336
|
+
let where2 = void 0;
|
|
337
|
+
const whereRaw2 = c.req.query("where");
|
|
338
|
+
if (whereRaw2) {
|
|
339
|
+
try {
|
|
340
|
+
where2 = JSON.parse(decodeURIComponent(whereRaw2));
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const paginatedResult = await provider.members.list({ limit: limit2, page: page2, sort: sort2, where: where2, req: c.req });
|
|
345
|
+
const mappedDocs = [];
|
|
346
|
+
for (const m of paginatedResult.docs) {
|
|
347
|
+
let localId = m.id;
|
|
348
|
+
const localDoc = await db.find({
|
|
349
|
+
collection: this.collection.slug,
|
|
350
|
+
where: m.id ? { externalSubject: { equals: m.id } } : { email: { equals: m.email } },
|
|
351
|
+
limit: 1
|
|
352
|
+
});
|
|
353
|
+
if (localDoc.docs[0]) {
|
|
354
|
+
localId = localDoc.docs[0].id;
|
|
355
|
+
}
|
|
356
|
+
mappedDocs.push({
|
|
357
|
+
...m,
|
|
358
|
+
id: localId,
|
|
359
|
+
externalSubject: m.id
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return c.json({
|
|
363
|
+
...paginatedResult,
|
|
364
|
+
docs: mappedDocs
|
|
365
|
+
});
|
|
366
|
+
}
|
|
313
367
|
const readonlyDb = createReadonlyDb(db);
|
|
314
368
|
const limit = Number(c.req.query("limit")) || 10;
|
|
315
369
|
const page = Number(c.req.query("page")) || 1;
|
|
@@ -328,7 +382,7 @@ var CollectionController = class {
|
|
|
328
382
|
if (this.collection.admin?.filterable === false) {
|
|
329
383
|
where = void 0;
|
|
330
384
|
} else {
|
|
331
|
-
const { sanitizeWhereClause } = await import("./where-sanitizer-
|
|
385
|
+
const { sanitizeWhereClause } = await import("./where-sanitizer-7Q4JXMX6.js");
|
|
332
386
|
where = sanitizeWhereClause(where, this.collection.fields);
|
|
333
387
|
if (Object.keys(where).length === 0) {
|
|
334
388
|
where = void 0;
|
|
@@ -367,11 +421,13 @@ var CollectionController = class {
|
|
|
367
421
|
where
|
|
368
422
|
});
|
|
369
423
|
}
|
|
370
|
-
result.docs = result.docs.map((doc) => this.collection.workflow ? materializeWorkflowDocument(doc, this.collection.workflow, user) : doc).filter((doc) => doc !== null)
|
|
424
|
+
result.docs = result.docs.map((doc) => this.collection.workflow ? materializeWorkflowDocument(doc, this.collection.workflow, user) : doc).filter((doc) => doc !== null);
|
|
371
425
|
const processedDocs = [];
|
|
426
|
+
const readonlyDbForHooks = createReadonlyDb(db);
|
|
372
427
|
for (const doc of result.docs) {
|
|
428
|
+
const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
|
|
373
429
|
const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
374
|
-
doc,
|
|
430
|
+
doc: docWithDefaults,
|
|
375
431
|
req: c.req,
|
|
376
432
|
user,
|
|
377
433
|
db: readonlyDb
|
|
@@ -389,17 +445,43 @@ var CollectionController = class {
|
|
|
389
445
|
async findOne(c) {
|
|
390
446
|
const config = c.get("config");
|
|
391
447
|
const db = config.db;
|
|
392
|
-
if (!db)
|
|
448
|
+
if (!db)
|
|
449
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
450
|
+
const provider = this.getDelegatedProvider(c);
|
|
451
|
+
if (provider && (provider.members?.get || provider.members?.list)) {
|
|
452
|
+
const id2 = c.req.param("id");
|
|
453
|
+
if (!id2)
|
|
454
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
455
|
+
const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
|
|
456
|
+
const externalSubject = localDoc?.externalSubject || id2;
|
|
457
|
+
let member = null;
|
|
458
|
+
if (provider.members.get) {
|
|
459
|
+
member = await provider.members.get({ externalSubject, req: c.req });
|
|
460
|
+
} else if (provider.members.list) {
|
|
461
|
+
const listResult = await provider.members.list({ req: c.req });
|
|
462
|
+
member = listResult.docs.find((m) => m.id === externalSubject) || null;
|
|
463
|
+
}
|
|
464
|
+
if (!member)
|
|
465
|
+
return c.json({ message: "Not Found" }, 404);
|
|
466
|
+
return c.json({
|
|
467
|
+
...member,
|
|
468
|
+
id: localDoc ? localDoc.id : member.id,
|
|
469
|
+
externalSubject: member.id
|
|
470
|
+
});
|
|
471
|
+
}
|
|
393
472
|
const readonlyDb = createReadonlyDb(db);
|
|
394
473
|
const id = c.req.param("id");
|
|
395
474
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
|
|
396
475
|
const user = c.get("user");
|
|
397
|
-
if (!id)
|
|
476
|
+
if (!id)
|
|
477
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
398
478
|
let doc = await db.findOne({ collection: this.collection.slug, id });
|
|
399
|
-
if (!doc)
|
|
479
|
+
if (!doc)
|
|
480
|
+
return c.json({ message: "Not Found" }, 404);
|
|
400
481
|
if (this.collection.workflow) {
|
|
401
482
|
doc = materializeWorkflowDocument(doc, this.collection.workflow, user);
|
|
402
|
-
if (!doc)
|
|
483
|
+
if (!doc)
|
|
484
|
+
return c.json({ message: "Not Found" }, 404);
|
|
403
485
|
}
|
|
404
486
|
const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
|
|
405
487
|
const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
@@ -424,7 +506,22 @@ var CollectionController = class {
|
|
|
424
506
|
async create(c) {
|
|
425
507
|
const config = c.get("config");
|
|
426
508
|
const db = config.db;
|
|
427
|
-
if (!db)
|
|
509
|
+
if (!db)
|
|
510
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
511
|
+
const provider = this.getDelegatedProvider(c);
|
|
512
|
+
if (provider && provider.members?.create) {
|
|
513
|
+
const contentType2 = c.req.header("Content-Type") || "";
|
|
514
|
+
if (contentType2.toLowerCase().includes("multipart/form-data")) {
|
|
515
|
+
return this.upload(c);
|
|
516
|
+
}
|
|
517
|
+
const body2 = await c.req.json();
|
|
518
|
+
const member = await provider.members.create({ data: body2, req: c.req });
|
|
519
|
+
return c.json({
|
|
520
|
+
...member,
|
|
521
|
+
id: member.id,
|
|
522
|
+
externalSubject: member.id
|
|
523
|
+
}, 201);
|
|
524
|
+
}
|
|
428
525
|
const readonlyDb = createReadonlyDb(db);
|
|
429
526
|
const contentType = c.req.header("Content-Type") || "";
|
|
430
527
|
if (contentType.toLowerCase().includes("multipart/form-data")) {
|
|
@@ -485,13 +582,16 @@ var CollectionController = class {
|
|
|
485
582
|
async upload(c) {
|
|
486
583
|
const config = c.get("config");
|
|
487
584
|
const storage = config.storage;
|
|
488
|
-
if (!storage)
|
|
585
|
+
if (!storage)
|
|
586
|
+
return c.json({ message: "Storage not configured" }, 500);
|
|
489
587
|
const db = config.db;
|
|
490
|
-
if (!db)
|
|
588
|
+
if (!db)
|
|
589
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
491
590
|
const readonlyDb = createReadonlyDb(db);
|
|
492
591
|
const formData = await c.req.formData();
|
|
493
592
|
const file = formData.get("file");
|
|
494
|
-
if (!file)
|
|
593
|
+
if (!file)
|
|
594
|
+
return c.json({ message: "No file uploaded" }, 400);
|
|
495
595
|
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
496
596
|
const siteId = c.get("siteId");
|
|
497
597
|
const workspaceId = c.get("workspaceId");
|
|
@@ -550,10 +650,27 @@ var CollectionController = class {
|
|
|
550
650
|
async update(c) {
|
|
551
651
|
const config = c.get("config");
|
|
552
652
|
const db = config.db;
|
|
553
|
-
if (!db)
|
|
653
|
+
if (!db)
|
|
654
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
655
|
+
const provider = this.getDelegatedProvider(c);
|
|
656
|
+
if (provider && provider.members?.update) {
|
|
657
|
+
const id2 = c.req.param("id");
|
|
658
|
+
if (!id2)
|
|
659
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
660
|
+
const body2 = await c.req.json();
|
|
661
|
+
const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
|
|
662
|
+
const externalSubject = localDoc?.externalSubject || id2;
|
|
663
|
+
const member = await provider.members.update({ externalSubject, data: body2, req: c.req });
|
|
664
|
+
return c.json({
|
|
665
|
+
...member,
|
|
666
|
+
id: localDoc ? localDoc.id : member.id,
|
|
667
|
+
externalSubject: member.id
|
|
668
|
+
});
|
|
669
|
+
}
|
|
554
670
|
const readonlyDb = createReadonlyDb(db);
|
|
555
671
|
const id = c.req.param("id");
|
|
556
|
-
if (!id)
|
|
672
|
+
if (!id)
|
|
673
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
557
674
|
const body = await c.req.json();
|
|
558
675
|
const user = c.get("user");
|
|
559
676
|
let data = { ...body };
|
|
@@ -567,7 +684,8 @@ var CollectionController = class {
|
|
|
567
684
|
updatedBy: user?.sub ?? null
|
|
568
685
|
});
|
|
569
686
|
const originalDoc = await db.findOne({ collection: this.collection.slug, id });
|
|
570
|
-
if (!originalDoc)
|
|
687
|
+
if (!originalDoc)
|
|
688
|
+
return c.json({ message: "Not Found" }, 404);
|
|
571
689
|
let before = null;
|
|
572
690
|
if (this.collection.audit) {
|
|
573
691
|
before = originalDoc;
|
|
@@ -611,8 +729,10 @@ var CollectionController = class {
|
|
|
611
729
|
}
|
|
612
730
|
async transition(c) {
|
|
613
731
|
const config = c.get("config");
|
|
614
|
-
if (!config.db)
|
|
615
|
-
|
|
732
|
+
if (!config.db)
|
|
733
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
734
|
+
if (!this.collection.workflow)
|
|
735
|
+
return c.json({ message: "Workflows are not enabled for this collection" }, 404);
|
|
616
736
|
const id = c.req.param("id");
|
|
617
737
|
const transitionName = c.req.param("transition");
|
|
618
738
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -635,12 +755,16 @@ var CollectionController = class {
|
|
|
635
755
|
}
|
|
636
756
|
async workflowHistory(c) {
|
|
637
757
|
const config = c.get("config");
|
|
638
|
-
if (!config.db)
|
|
639
|
-
|
|
758
|
+
if (!config.db)
|
|
759
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
760
|
+
if (!this.collection.workflow)
|
|
761
|
+
return c.json({ message: "Workflows are not enabled for this collection" }, 404);
|
|
640
762
|
const documentId = c.req.param("id");
|
|
641
|
-
if (!documentId)
|
|
763
|
+
if (!documentId)
|
|
764
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
642
765
|
const document = await config.db.findOne({ collection: this.collection.slug, id: documentId });
|
|
643
|
-
if (!document)
|
|
766
|
+
if (!document)
|
|
767
|
+
return c.json({ message: "Not Found" }, 404);
|
|
644
768
|
const readAccess = this.collection.access?.read;
|
|
645
769
|
if (readAccess !== void 0 && readAccess !== null) {
|
|
646
770
|
const args = { user: c.get("user"), req: c.req, doc: document };
|
|
@@ -654,7 +778,8 @@ var CollectionController = class {
|
|
|
654
778
|
});
|
|
655
779
|
allowed = match.total > 0;
|
|
656
780
|
}
|
|
657
|
-
if (!allowed)
|
|
781
|
+
if (!allowed)
|
|
782
|
+
return c.json({ error: true, message: `Access denied: read on ${this.collection.slug}` }, 403);
|
|
658
783
|
}
|
|
659
784
|
const result = await config.db.find({
|
|
660
785
|
collection: WORKFLOW_HISTORY_COLLECTION,
|
|
@@ -678,14 +803,17 @@ var CollectionController = class {
|
|
|
678
803
|
async changePassword(c) {
|
|
679
804
|
const config = c.get("config");
|
|
680
805
|
const db = config.db;
|
|
681
|
-
if (!db)
|
|
806
|
+
if (!db)
|
|
807
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
682
808
|
if (!this.collection.auth) {
|
|
683
809
|
return c.json({ message: "This collection does not support authentication" }, 400);
|
|
684
810
|
}
|
|
685
811
|
const id = c.req.param("id");
|
|
686
|
-
if (!id)
|
|
812
|
+
if (!id)
|
|
813
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
687
814
|
const user = c.get("user");
|
|
688
|
-
if (!user)
|
|
815
|
+
if (!user)
|
|
816
|
+
return c.json({ message: "Authentication required" }, 401);
|
|
689
817
|
const body = await c.req.json().catch(() => null);
|
|
690
818
|
const { oldPassword, newPassword, confirmPassword } = body ?? {};
|
|
691
819
|
if (!newPassword) {
|
|
@@ -707,7 +835,8 @@ var CollectionController = class {
|
|
|
707
835
|
return c.json({ message: "Current password is required" }, 400);
|
|
708
836
|
}
|
|
709
837
|
const existing = await db.findOne({ collection: this.collection.slug, id });
|
|
710
|
-
if (!existing)
|
|
838
|
+
if (!existing)
|
|
839
|
+
return c.json({ message: "User not found" }, 404);
|
|
711
840
|
const valid = await verifyPassword(oldPassword, existing.password);
|
|
712
841
|
if (!valid) {
|
|
713
842
|
return c.json({ message: "Invalid current password" }, 400);
|
|
@@ -738,13 +867,26 @@ var CollectionController = class {
|
|
|
738
867
|
async delete(c) {
|
|
739
868
|
const config = c.get("config");
|
|
740
869
|
const db = config.db;
|
|
741
|
-
if (!db)
|
|
870
|
+
if (!db)
|
|
871
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
872
|
+
const provider = this.getDelegatedProvider(c);
|
|
873
|
+
if (provider && provider.members?.delete) {
|
|
874
|
+
const id2 = c.req.param("id");
|
|
875
|
+
if (!id2)
|
|
876
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
877
|
+
const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
|
|
878
|
+
const externalSubject = localDoc?.externalSubject || id2;
|
|
879
|
+
await provider.members.delete({ externalSubject, req: c.req });
|
|
880
|
+
return c.json({ message: "Deleted" });
|
|
881
|
+
}
|
|
742
882
|
const readonlyDb = createReadonlyDb(db);
|
|
743
883
|
const id = c.req.param("id");
|
|
744
|
-
if (!id)
|
|
884
|
+
if (!id)
|
|
885
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
745
886
|
const user = c.get("user");
|
|
746
887
|
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
747
|
-
if (!doc)
|
|
888
|
+
if (!doc)
|
|
889
|
+
return c.json({ message: "Not Found" }, 404);
|
|
748
890
|
let before = null;
|
|
749
891
|
if (this.collection.audit) {
|
|
750
892
|
before = doc;
|
|
@@ -779,7 +921,8 @@ var CollectionController = class {
|
|
|
779
921
|
async deleteMany(c) {
|
|
780
922
|
const config = c.get("config");
|
|
781
923
|
const db = config.db;
|
|
782
|
-
if (!db)
|
|
924
|
+
if (!db)
|
|
925
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
783
926
|
const readonlyDb = createReadonlyDb(db);
|
|
784
927
|
const user = c.get("user");
|
|
785
928
|
let ids = [];
|
|
@@ -794,7 +937,8 @@ var CollectionController = class {
|
|
|
794
937
|
const raw = c.req.queries("ids") ?? c.req.queries("ids[]") ?? [];
|
|
795
938
|
ids = raw.filter(Boolean);
|
|
796
939
|
}
|
|
797
|
-
if (!ids.length)
|
|
940
|
+
if (!ids.length)
|
|
941
|
+
return c.json({ message: "No IDs provided" }, 400);
|
|
798
942
|
const deleted = [];
|
|
799
943
|
const failed = [];
|
|
800
944
|
for (const id of ids) {
|
|
@@ -847,7 +991,8 @@ var CollectionController = class {
|
|
|
847
991
|
async seed(c) {
|
|
848
992
|
const config = c.get("config");
|
|
849
993
|
const db = config.db;
|
|
850
|
-
if (!db)
|
|
994
|
+
if (!db)
|
|
995
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
851
996
|
const body = await c.req.json();
|
|
852
997
|
const initialData = body.data;
|
|
853
998
|
if (!initialData || !Array.isArray(initialData)) {
|
|
@@ -867,7 +1012,7 @@ var CollectionController = class {
|
|
|
867
1012
|
}
|
|
868
1013
|
};
|
|
869
1014
|
|
|
870
|
-
// src/controllers/global.controller.
|
|
1015
|
+
// src/controllers/global.controller.js
|
|
871
1016
|
var GlobalController = class {
|
|
872
1017
|
global;
|
|
873
1018
|
constructor(global) {
|
|
@@ -876,7 +1021,8 @@ var GlobalController = class {
|
|
|
876
1021
|
async get(c) {
|
|
877
1022
|
const config = c.get("config");
|
|
878
1023
|
const db = config.db;
|
|
879
|
-
if (!db)
|
|
1024
|
+
if (!db)
|
|
1025
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
880
1026
|
const readonlyDb = createReadonlyDb(db);
|
|
881
1027
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
|
|
882
1028
|
const user = c.get("user");
|
|
@@ -920,7 +1066,8 @@ var GlobalController = class {
|
|
|
920
1066
|
async update(c) {
|
|
921
1067
|
const config = c.get("config");
|
|
922
1068
|
const db = config.db;
|
|
923
|
-
if (!db)
|
|
1069
|
+
if (!db)
|
|
1070
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
924
1071
|
const readonlyDb = createReadonlyDb(db);
|
|
925
1072
|
const body = await c.req.json();
|
|
926
1073
|
const user = c.get("user");
|
|
@@ -955,7 +1102,8 @@ var GlobalController = class {
|
|
|
955
1102
|
async seed(c) {
|
|
956
1103
|
const config = c.get("config");
|
|
957
1104
|
const db = config.db;
|
|
958
|
-
if (!db)
|
|
1105
|
+
if (!db)
|
|
1106
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
959
1107
|
const body = await c.req.json();
|
|
960
1108
|
const initialData = body.data;
|
|
961
1109
|
if (!initialData) {
|
|
@@ -971,20 +1119,23 @@ var GlobalController = class {
|
|
|
971
1119
|
}
|
|
972
1120
|
};
|
|
973
1121
|
function isFunctionallyEmpty(obj) {
|
|
974
|
-
if (obj === null || obj === void 0 || obj === "")
|
|
1122
|
+
if (obj === null || obj === void 0 || obj === "")
|
|
1123
|
+
return true;
|
|
975
1124
|
if (Array.isArray(obj)) {
|
|
976
|
-
if (obj.length === 0)
|
|
1125
|
+
if (obj.length === 0)
|
|
1126
|
+
return true;
|
|
977
1127
|
return obj.every(isFunctionallyEmpty);
|
|
978
1128
|
}
|
|
979
1129
|
if (typeof obj === "object") {
|
|
980
1130
|
const keys = Object.keys(obj);
|
|
981
|
-
if (keys.length === 0)
|
|
1131
|
+
if (keys.length === 0)
|
|
1132
|
+
return true;
|
|
982
1133
|
return keys.every((key) => isFunctionallyEmpty(obj[key]));
|
|
983
1134
|
}
|
|
984
1135
|
return false;
|
|
985
1136
|
}
|
|
986
1137
|
|
|
987
|
-
// src/controllers/media.controller.
|
|
1138
|
+
// src/controllers/media.controller.js
|
|
988
1139
|
var MediaController = class {
|
|
989
1140
|
collection;
|
|
990
1141
|
constructor(collection = "media") {
|
|
@@ -1064,7 +1215,8 @@ var MediaController = class {
|
|
|
1064
1215
|
}
|
|
1065
1216
|
}
|
|
1066
1217
|
const db = config.db;
|
|
1067
|
-
if (!db)
|
|
1218
|
+
if (!db)
|
|
1219
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1068
1220
|
const doc = await db.create({
|
|
1069
1221
|
collection: this.collection,
|
|
1070
1222
|
data: finalFileData
|
|
@@ -1073,7 +1225,8 @@ var MediaController = class {
|
|
|
1073
1225
|
}
|
|
1074
1226
|
async find(c) {
|
|
1075
1227
|
const db = c.get("config").db;
|
|
1076
|
-
if (!db)
|
|
1228
|
+
if (!db)
|
|
1229
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1077
1230
|
const limit = Number(c.req.query("limit")) || 10;
|
|
1078
1231
|
const page = Number(c.req.query("page")) || 1;
|
|
1079
1232
|
const result = await db.find({
|
|
@@ -1087,11 +1240,14 @@ var MediaController = class {
|
|
|
1087
1240
|
const config = c.get("config");
|
|
1088
1241
|
const storage = config.storage;
|
|
1089
1242
|
const db = config.db;
|
|
1090
|
-
if (!db)
|
|
1243
|
+
if (!db)
|
|
1244
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1091
1245
|
const id = c.req.param("id");
|
|
1092
|
-
if (!id)
|
|
1246
|
+
if (!id)
|
|
1247
|
+
return c.json({ message: "Missing ID" }, 400);
|
|
1093
1248
|
const doc = await db.findOne({ collection: this.collection, id });
|
|
1094
|
-
if (!doc)
|
|
1249
|
+
if (!doc)
|
|
1250
|
+
return c.json({ message: "Not Found" }, 404);
|
|
1095
1251
|
if (storage) {
|
|
1096
1252
|
await storage.delete({ filename: doc.filename });
|
|
1097
1253
|
if (doc.sizes) {
|
|
@@ -1112,26 +1268,26 @@ var MediaController = class {
|
|
|
1112
1268
|
return c.json({ message: "Storage not configured for serving" }, 404);
|
|
1113
1269
|
}
|
|
1114
1270
|
const filename = c.req.param("filename");
|
|
1115
|
-
if (!filename)
|
|
1271
|
+
if (!filename)
|
|
1272
|
+
return c.json({ message: "Missing filename" }, 400);
|
|
1116
1273
|
let res = await storage.resolve({ filename });
|
|
1117
1274
|
if (!res && !filename.includes("/")) {
|
|
1118
1275
|
res = await storage.resolve({ filename: `default/${filename}` });
|
|
1119
1276
|
}
|
|
1120
|
-
if (!res)
|
|
1277
|
+
if (!res)
|
|
1278
|
+
return c.json({ message: "Not Found" }, 404);
|
|
1121
1279
|
c.header("Content-Type", res.mimeType);
|
|
1122
1280
|
return c.body(res.buffer);
|
|
1123
1281
|
}
|
|
1124
1282
|
};
|
|
1125
1283
|
|
|
1126
|
-
// src/auth/token.
|
|
1284
|
+
// src/auth/token.js
|
|
1127
1285
|
import { SignJWT, jwtVerify, decodeJwt } from "jose";
|
|
1128
1286
|
import { TextEncoder } from "util";
|
|
1129
1287
|
function getSecret() {
|
|
1130
1288
|
const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET;
|
|
1131
1289
|
if (!secret) {
|
|
1132
|
-
throw new Error(
|
|
1133
|
-
"[dyrected/core] DYRECTED_JWT_SECRET is not set. Add it to your environment variables to enable auth collections."
|
|
1134
|
-
);
|
|
1290
|
+
throw new Error("[dyrected/core] DYRECTED_JWT_SECRET is not set. Add it to your environment variables to enable auth collections.");
|
|
1135
1291
|
}
|
|
1136
1292
|
return new TextEncoder().encode(secret);
|
|
1137
1293
|
}
|
|
@@ -1144,7 +1300,7 @@ async function verifyCollectionToken(token) {
|
|
|
1144
1300
|
return payload;
|
|
1145
1301
|
}
|
|
1146
1302
|
|
|
1147
|
-
// src/services/email-template.
|
|
1303
|
+
// src/services/email-template.js
|
|
1148
1304
|
var emailTokens = {
|
|
1149
1305
|
colors: {
|
|
1150
1306
|
canvas: "#f6f7f2",
|
|
@@ -1208,11 +1364,9 @@ function detailBox(content, monospace = false) {
|
|
|
1208
1364
|
}
|
|
1209
1365
|
function ctaButton(label, href) {
|
|
1210
1366
|
const safeHref = safeHttpUrl(href);
|
|
1211
|
-
if (!safeHref)
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
"padding:8px 0 24px"
|
|
1215
|
-
), "width:auto");
|
|
1367
|
+
if (!safeHref)
|
|
1368
|
+
return "";
|
|
1369
|
+
return table(row(`<a href="${safeHref}" style="display:inline-block;padding:13px 22px;border-radius:${emailTokens.radius.control};background:${emailTokens.colors.accent};font-family:${emailTokens.font};font-size:14px;line-height:1.2;font-weight:700;color:${emailTokens.colors.text};text-decoration:none">${escapeHtml(label)}</a>`, "padding:8px 0 24px"), "width:auto");
|
|
1216
1370
|
}
|
|
1217
1371
|
function alertBox(content) {
|
|
1218
1372
|
return table(row(escapeHtml(content), `padding:13px 16px;font-family:${emailTokens.font};font-size:13px;line-height:1.5;color:${emailTokens.colors.dangerText}`), `width:100%;background:${emailTokens.colors.dangerSurface};border:1px solid ${emailTokens.colors.dangerBorder};border-radius:${emailTokens.radius.control}`);
|
|
@@ -1223,23 +1377,19 @@ function layout({ preheader, title, content, footer }) {
|
|
|
1223
1377
|
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"></head>
|
|
1224
1378
|
<body style="margin:0;padding:0;background:${emailTokens.colors.canvas}">
|
|
1225
1379
|
<div style="display:none;max-height:0;overflow:hidden;opacity:0;color:transparent">${escapeHtml(preheader)}</div>
|
|
1226
|
-
${table(row(
|
|
1227
|
-
table(
|
|
1228
|
-
row(" ", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel("Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(content, "padding:0 32px 32px") + row(`${divider()}${paragraph(footer, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "0")}`, "padding:0 32px 28px"),
|
|
1229
|
-
`width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`
|
|
1230
|
-
),
|
|
1231
|
-
"padding:32px 12px"
|
|
1232
|
-
), `width:100%;background:${emailTokens.colors.canvas}`)}
|
|
1380
|
+
${table(row(table(row(" ", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel("Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(content, "padding:0 32px 32px") + row(`${divider()}${paragraph(footer, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "0")}`, "padding:0 32px 28px"), `width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`), "padding:32px 12px"), `width:100%;background:${emailTokens.colors.canvas}`)}
|
|
1233
1381
|
</body>
|
|
1234
1382
|
</html>`;
|
|
1235
1383
|
}
|
|
1236
1384
|
|
|
1237
|
-
// src/services/email.service.
|
|
1385
|
+
// src/services/email.service.js
|
|
1238
1386
|
var _devSend = null;
|
|
1239
1387
|
var _devSendPromise = null;
|
|
1240
1388
|
async function getDevSend() {
|
|
1241
|
-
if (_devSend)
|
|
1242
|
-
|
|
1389
|
+
if (_devSend)
|
|
1390
|
+
return _devSend;
|
|
1391
|
+
if (_devSendPromise)
|
|
1392
|
+
return _devSendPromise;
|
|
1243
1393
|
_devSendPromise = (async () => {
|
|
1244
1394
|
try {
|
|
1245
1395
|
const nodemailer = await import("nodemailer");
|
|
@@ -1323,7 +1473,7 @@ function buildPasswordChangedEmail(config, args) {
|
|
|
1323
1473
|
};
|
|
1324
1474
|
}
|
|
1325
1475
|
|
|
1326
|
-
// src/controllers/auth.controller.
|
|
1476
|
+
// src/controllers/auth.controller.js
|
|
1327
1477
|
var AuthController = class {
|
|
1328
1478
|
collection;
|
|
1329
1479
|
constructor(collection) {
|
|
@@ -1335,7 +1485,8 @@ var AuthController = class {
|
|
|
1335
1485
|
// ---------------------------------------------------------------------------
|
|
1336
1486
|
async init(c) {
|
|
1337
1487
|
const db = c.get("config").db;
|
|
1338
|
-
if (!db)
|
|
1488
|
+
if (!db)
|
|
1489
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1339
1490
|
const result = await db.find({
|
|
1340
1491
|
collection: this.collection.slug,
|
|
1341
1492
|
limit: 1
|
|
@@ -1351,7 +1502,8 @@ var AuthController = class {
|
|
|
1351
1502
|
async registerFirstUser(c) {
|
|
1352
1503
|
const config = c.get("config");
|
|
1353
1504
|
const db = config.db;
|
|
1354
|
-
if (!db)
|
|
1505
|
+
if (!db)
|
|
1506
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1355
1507
|
const check = await db.find({
|
|
1356
1508
|
collection: this.collection.slug,
|
|
1357
1509
|
limit: 1
|
|
@@ -1379,9 +1531,7 @@ var AuthController = class {
|
|
|
1379
1531
|
collection: this.collection.slug
|
|
1380
1532
|
});
|
|
1381
1533
|
const { subject, html } = buildWelcomeEmail(config, { email: body.email });
|
|
1382
|
-
sendEmail(config, { to: body.email, subject, html }).catch(
|
|
1383
|
-
(err) => console.error("[dyrected/core] Failed to send welcome email:", err)
|
|
1384
|
-
);
|
|
1534
|
+
sendEmail(config, { to: body.email, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send welcome email:", err));
|
|
1385
1535
|
const { password: _, ...safeUser } = user;
|
|
1386
1536
|
return c.json({ token, user: safeUser });
|
|
1387
1537
|
}
|
|
@@ -1390,7 +1540,8 @@ var AuthController = class {
|
|
|
1390
1540
|
// ---------------------------------------------------------------------------
|
|
1391
1541
|
async login(c) {
|
|
1392
1542
|
const db = c.get("config").db;
|
|
1393
|
-
if (!db)
|
|
1543
|
+
if (!db)
|
|
1544
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1394
1545
|
const body = await c.req.json().catch(() => null);
|
|
1395
1546
|
if (!body?.email || !body?.password) {
|
|
1396
1547
|
return c.json({ error: true, message: "email and password are required." }, 400);
|
|
@@ -1429,7 +1580,8 @@ var AuthController = class {
|
|
|
1429
1580
|
// ---------------------------------------------------------------------------
|
|
1430
1581
|
async me(c) {
|
|
1431
1582
|
const db = c.get("config").db;
|
|
1432
|
-
if (!db)
|
|
1583
|
+
if (!db)
|
|
1584
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1433
1585
|
const requestUser = c.get("user");
|
|
1434
1586
|
if (!requestUser) {
|
|
1435
1587
|
return c.json({ error: true, message: "Authentication required." }, 401);
|
|
@@ -1464,7 +1616,8 @@ var AuthController = class {
|
|
|
1464
1616
|
async forgotPassword(c) {
|
|
1465
1617
|
const config = c.get("config");
|
|
1466
1618
|
const db = config.db;
|
|
1467
|
-
if (!db)
|
|
1619
|
+
if (!db)
|
|
1620
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1468
1621
|
const body = await c.req.json().catch(() => null);
|
|
1469
1622
|
if (!body?.email) {
|
|
1470
1623
|
return c.json({ error: true, message: "email is required." }, 400);
|
|
@@ -1476,10 +1629,7 @@ var AuthController = class {
|
|
|
1476
1629
|
});
|
|
1477
1630
|
const user = result.docs[0];
|
|
1478
1631
|
if (user) {
|
|
1479
|
-
const resetToken = await signCollectionToken(
|
|
1480
|
-
{ sub: user.id, email: user.email, collection: this.collection.slug, purpose: "reset" },
|
|
1481
|
-
"1h"
|
|
1482
|
-
);
|
|
1632
|
+
const resetToken = await signCollectionToken({ sub: user.id, email: user.email, collection: this.collection.slug, purpose: "reset" }, "1h");
|
|
1483
1633
|
const resetUrl = body?.resetUrl;
|
|
1484
1634
|
const url = resetUrl ? `${resetUrl}${resetUrl.includes("?") ? "&" : "?"}token=${encodeURIComponent(resetToken)}` : void 0;
|
|
1485
1635
|
try {
|
|
@@ -1502,7 +1652,8 @@ var AuthController = class {
|
|
|
1502
1652
|
async resetPassword(c) {
|
|
1503
1653
|
const config = c.get("config");
|
|
1504
1654
|
const db = config.db;
|
|
1505
|
-
if (!db)
|
|
1655
|
+
if (!db)
|
|
1656
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1506
1657
|
const body = await c.req.json().catch(() => null);
|
|
1507
1658
|
if (!body?.token || !body?.password) {
|
|
1508
1659
|
return c.json({ error: true, message: "token and password are required." }, 400);
|
|
@@ -1523,9 +1674,7 @@ var AuthController = class {
|
|
|
1523
1674
|
data: { password: hashedPassword }
|
|
1524
1675
|
});
|
|
1525
1676
|
const { subject, html } = buildPasswordChangedEmail(config, { email: payload.email });
|
|
1526
|
-
sendEmail(config, { to: payload.email, subject, html }).catch(
|
|
1527
|
-
(err) => console.error("[dyrected/core] Failed to send password-changed email:", err)
|
|
1528
|
-
);
|
|
1677
|
+
sendEmail(config, { to: payload.email, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send password-changed email:", err));
|
|
1529
1678
|
return c.json({ success: true, message: "Password has been reset. You can now log in." });
|
|
1530
1679
|
}
|
|
1531
1680
|
// ---------------------------------------------------------------------------
|
|
@@ -1535,7 +1684,8 @@ var AuthController = class {
|
|
|
1535
1684
|
async invite(c) {
|
|
1536
1685
|
const config = c.get("config");
|
|
1537
1686
|
const db = config.db;
|
|
1538
|
-
if (!db)
|
|
1687
|
+
if (!db)
|
|
1688
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1539
1689
|
const requestUser = c.get("user");
|
|
1540
1690
|
if (!requestUser) {
|
|
1541
1691
|
return c.json({ error: true, message: "Authentication required." }, 401);
|
|
@@ -1552,10 +1702,7 @@ var AuthController = class {
|
|
|
1552
1702
|
if (existing.total > 0) {
|
|
1553
1703
|
return c.json({ error: true, message: "An account with that email already exists." }, 409);
|
|
1554
1704
|
}
|
|
1555
|
-
const inviteToken = await signCollectionToken(
|
|
1556
|
-
{ sub: body.email, email: body.email, collection: this.collection.slug, purpose: "invite" },
|
|
1557
|
-
"7d"
|
|
1558
|
-
);
|
|
1705
|
+
const inviteToken = await signCollectionToken({ sub: body.email, email: body.email, collection: this.collection.slug, purpose: "invite" }, "7d");
|
|
1559
1706
|
try {
|
|
1560
1707
|
const { subject, html } = buildInviteEmail(config, {
|
|
1561
1708
|
token: inviteToken,
|
|
@@ -1575,7 +1722,8 @@ var AuthController = class {
|
|
|
1575
1722
|
async acceptInvite(c) {
|
|
1576
1723
|
const config = c.get("config");
|
|
1577
1724
|
const db = config.db;
|
|
1578
|
-
if (!db)
|
|
1725
|
+
if (!db)
|
|
1726
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
1579
1727
|
const body = await c.req.json().catch(() => null);
|
|
1580
1728
|
if (!body?.token || !body?.password) {
|
|
1581
1729
|
return c.json({ error: true, message: "token and password are required." }, 400);
|
|
@@ -1610,28 +1758,21 @@ var AuthController = class {
|
|
|
1610
1758
|
collection: this.collection.slug
|
|
1611
1759
|
});
|
|
1612
1760
|
const { subject, html } = buildWelcomeEmail(config, { email: inviteeEmail });
|
|
1613
|
-
sendEmail(config, { to: inviteeEmail, subject, html }).catch(
|
|
1614
|
-
(err) => console.error("[dyrected/core] Failed to send welcome email:", err)
|
|
1615
|
-
);
|
|
1761
|
+
sendEmail(config, { to: inviteeEmail, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send welcome email:", err));
|
|
1616
1762
|
const { password: _, ...safeUser } = user;
|
|
1617
1763
|
return c.json({ token: sessionToken, user: safeUser }, 201);
|
|
1618
1764
|
}
|
|
1619
1765
|
};
|
|
1620
1766
|
|
|
1621
|
-
// src/controllers/admin-auth.controller.
|
|
1767
|
+
// src/controllers/admin-auth.controller.js
|
|
1622
1768
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1623
1769
|
import { TextEncoder as TextEncoder2 } from "util";
|
|
1624
|
-
import {
|
|
1625
|
-
SignJWT as SignJWT2,
|
|
1626
|
-
createRemoteJWKSet,
|
|
1627
|
-
jwtVerify as jwtVerify2,
|
|
1628
|
-
decodeJwt as decodeJwt2
|
|
1629
|
-
} from "jose";
|
|
1770
|
+
import { SignJWT as SignJWT2, createRemoteJWKSet, jwtVerify as jwtVerify2, decodeJwt as decodeJwt2 } from "jose";
|
|
1630
1771
|
var AdminAuthController = class {
|
|
1772
|
+
config;
|
|
1631
1773
|
constructor(config) {
|
|
1632
1774
|
this.config = config;
|
|
1633
1775
|
}
|
|
1634
|
-
config;
|
|
1635
1776
|
providers(c) {
|
|
1636
1777
|
return c.json(getPublicAdminAuthConfig(this.config.adminAuth));
|
|
1637
1778
|
}
|
|
@@ -1644,20 +1785,22 @@ var AdminAuthController = class {
|
|
|
1644
1785
|
const siteId = this.getSiteId(c);
|
|
1645
1786
|
const returnTo = this.normalizeReturnTo(c.req.query("returnTo"), c);
|
|
1646
1787
|
if (provider.type === "oidc") {
|
|
1647
|
-
const redirectUrl = await this.buildOidcStartUrl(
|
|
1648
|
-
provider,
|
|
1649
|
-
returnTo,
|
|
1650
|
-
siteId,
|
|
1651
|
-
new URL(c.req.url).origin
|
|
1652
|
-
);
|
|
1788
|
+
const redirectUrl = await this.buildOidcStartUrl(provider, returnTo, siteId, new URL(c.req.url).origin);
|
|
1653
1789
|
return c.redirect(redirectUrl, 302);
|
|
1654
1790
|
}
|
|
1655
1791
|
if (!provider.startUrl) {
|
|
1656
1792
|
return c.json({ error: true, message: "This provider does not expose a start URL." }, 400);
|
|
1657
1793
|
}
|
|
1658
|
-
const url = new URL(
|
|
1794
|
+
const url = this.buildCustomProviderStartUrl(provider.startUrl, new URL(c.req.url).origin);
|
|
1795
|
+
if (!url) {
|
|
1796
|
+
return c.json({
|
|
1797
|
+
error: true,
|
|
1798
|
+
message: `Admin auth provider "${provider.id}" has an invalid start URL.`
|
|
1799
|
+
}, 500);
|
|
1800
|
+
}
|
|
1659
1801
|
url.searchParams.set("returnTo", returnTo);
|
|
1660
|
-
if (siteId)
|
|
1802
|
+
if (siteId)
|
|
1803
|
+
url.searchParams.set("siteId", siteId);
|
|
1661
1804
|
url.searchParams.set("provider", provider.id);
|
|
1662
1805
|
return c.redirect(url.toString(), 302);
|
|
1663
1806
|
}
|
|
@@ -1704,7 +1847,8 @@ var AdminAuthController = class {
|
|
|
1704
1847
|
return c.json({ success: true, message: "Logged out. Discard your token." });
|
|
1705
1848
|
}
|
|
1706
1849
|
getProvider(config, id) {
|
|
1707
|
-
if (config.adminAuth?.mode !== "external")
|
|
1850
|
+
if (config.adminAuth?.mode !== "external")
|
|
1851
|
+
return void 0;
|
|
1708
1852
|
return config.adminAuth.providers.find((provider) => provider.id === id);
|
|
1709
1853
|
}
|
|
1710
1854
|
async completeProviderAuth(requestConfig, provider, c) {
|
|
@@ -1716,7 +1860,8 @@ var AdminAuthController = class {
|
|
|
1716
1860
|
throw new Error("Admin auth collection is not configured.");
|
|
1717
1861
|
}
|
|
1718
1862
|
const db = c.get("config").db;
|
|
1719
|
-
if (!db)
|
|
1863
|
+
if (!db)
|
|
1864
|
+
throw new Error("Database not configured.");
|
|
1720
1865
|
let user = await this.findUserByExternalIdentity(db, adminCollection.slug, provider.id, identity.sub) ?? (identity.email ? (await db.find({
|
|
1721
1866
|
collection: adminCollection.slug,
|
|
1722
1867
|
where: { email: identity.email },
|
|
@@ -1776,7 +1921,8 @@ var AdminAuthController = class {
|
|
|
1776
1921
|
},
|
|
1777
1922
|
user
|
|
1778
1923
|
});
|
|
1779
|
-
if (resolved)
|
|
1924
|
+
if (resolved)
|
|
1925
|
+
return resolved;
|
|
1780
1926
|
return { allowed: true, roles: identity.roles, data: {} };
|
|
1781
1927
|
}
|
|
1782
1928
|
async buildUserData(identity, providerId, roles, extraData) {
|
|
@@ -1807,7 +1953,8 @@ var AdminAuthController = class {
|
|
|
1807
1953
|
}
|
|
1808
1954
|
async getRequestConfig(c) {
|
|
1809
1955
|
const siteId = this.getSiteId(c);
|
|
1810
|
-
if (!siteId || !this.config.onSchemaFetch)
|
|
1956
|
+
if (!siteId || !this.config.onSchemaFetch)
|
|
1957
|
+
return this.config;
|
|
1811
1958
|
const dynamic = await this.config.onSchemaFetch(siteId);
|
|
1812
1959
|
return {
|
|
1813
1960
|
...this.config,
|
|
@@ -1838,7 +1985,8 @@ var AdminAuthController = class {
|
|
|
1838
1985
|
async resolveOidcIdentity(provider, c) {
|
|
1839
1986
|
const code = c.req.query("code");
|
|
1840
1987
|
const state = c.req.query("state");
|
|
1841
|
-
if (!code || !state)
|
|
1988
|
+
if (!code || !state)
|
|
1989
|
+
throw new Error("Missing OIDC callback parameters.");
|
|
1842
1990
|
const parsedState = await this.verifyState(state, provider.id);
|
|
1843
1991
|
const discovery = await this.getOidcDiscovery(provider);
|
|
1844
1992
|
const redirectUri = provider.redirectUri || this.defaultCallbackPath(provider.id, new URL(c.req.url).origin);
|
|
@@ -1873,7 +2021,8 @@ var AdminAuthController = class {
|
|
|
1873
2021
|
const userInfo = await fetch(provider.userInfoEndpoint || discovery.userinfo_endpoint, {
|
|
1874
2022
|
headers: { Authorization: `Bearer ${tokenBody.access_token}` }
|
|
1875
2023
|
});
|
|
1876
|
-
if (!userInfo.ok)
|
|
2024
|
+
if (!userInfo.ok)
|
|
2025
|
+
throw new Error("OIDC userinfo request failed.");
|
|
1877
2026
|
claims = await userInfo.json();
|
|
1878
2027
|
} else {
|
|
1879
2028
|
throw new Error("OIDC provider did not return usable identity claims.");
|
|
@@ -1913,15 +2062,13 @@ var AdminAuthController = class {
|
|
|
1913
2062
|
if (provider.audience) {
|
|
1914
2063
|
const aud = claims.aud;
|
|
1915
2064
|
const matches = Array.isArray(aud) ? aud.includes(provider.audience) : aud === provider.audience;
|
|
1916
|
-
if (!matches)
|
|
2065
|
+
if (!matches)
|
|
2066
|
+
throw new Error("External auth audience mismatch.");
|
|
1917
2067
|
}
|
|
1918
2068
|
}
|
|
1919
2069
|
return {
|
|
1920
2070
|
identity: this.mapClaims(claims, provider.claimMapping),
|
|
1921
|
-
returnTo: this.normalizeReturnTo(
|
|
1922
|
-
body?.returnTo || c.req.query("returnTo"),
|
|
1923
|
-
c
|
|
1924
|
-
)
|
|
2071
|
+
returnTo: this.normalizeReturnTo(body?.returnTo || c.req.query("returnTo"), c)
|
|
1925
2072
|
};
|
|
1926
2073
|
}
|
|
1927
2074
|
mapClaims(claims, mapping) {
|
|
@@ -1951,24 +2098,38 @@ var AdminAuthController = class {
|
|
|
1951
2098
|
return typeof value === "string" ? value : void 0;
|
|
1952
2099
|
}
|
|
1953
2100
|
readStringArrayClaim(value) {
|
|
1954
|
-
if (!value)
|
|
2101
|
+
if (!value)
|
|
2102
|
+
return void 0;
|
|
1955
2103
|
if (Array.isArray(value)) {
|
|
1956
2104
|
return value.filter((entry) => typeof entry === "string");
|
|
1957
2105
|
}
|
|
1958
|
-
if (typeof value === "string")
|
|
2106
|
+
if (typeof value === "string")
|
|
2107
|
+
return [value];
|
|
1959
2108
|
return void 0;
|
|
1960
2109
|
}
|
|
1961
2110
|
async getOidcDiscovery(provider) {
|
|
1962
|
-
const discoveryUrl = new URL(
|
|
1963
|
-
provider.issuer.replace(/\/$/, "") + "/.well-known/openid-configuration"
|
|
1964
|
-
);
|
|
2111
|
+
const discoveryUrl = new URL(provider.issuer.replace(/\/$/, "") + "/.well-known/openid-configuration");
|
|
1965
2112
|
const response = await fetch(discoveryUrl.toString());
|
|
1966
|
-
if (!response.ok)
|
|
2113
|
+
if (!response.ok)
|
|
2114
|
+
throw new Error("Failed to load OIDC discovery document.");
|
|
1967
2115
|
return response.json();
|
|
1968
2116
|
}
|
|
1969
2117
|
defaultCallbackPath(providerId, origin = "") {
|
|
1970
2118
|
return `${origin}/api/admin/auth/${providerId}/callback`;
|
|
1971
2119
|
}
|
|
2120
|
+
buildCustomProviderStartUrl(startUrl, origin) {
|
|
2121
|
+
const value = startUrl.trim();
|
|
2122
|
+
if (!value || /^(undefined|null)(\/|$)/i.test(value))
|
|
2123
|
+
return null;
|
|
2124
|
+
try {
|
|
2125
|
+
const url = new URL(value, origin);
|
|
2126
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
2127
|
+
return null;
|
|
2128
|
+
return url;
|
|
2129
|
+
} catch {
|
|
2130
|
+
return null;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
1972
2133
|
async signState(payload) {
|
|
1973
2134
|
return new SignJWT2(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("15m").sign(this.getStateSecret());
|
|
1974
2135
|
}
|
|
@@ -1988,13 +2149,14 @@ var AdminAuthController = class {
|
|
|
1988
2149
|
return new TextEncoder2().encode(secret);
|
|
1989
2150
|
}
|
|
1990
2151
|
normalizeReturnTo(returnTo, c) {
|
|
1991
|
-
if (returnTo)
|
|
2152
|
+
if (returnTo)
|
|
2153
|
+
return returnTo;
|
|
1992
2154
|
const url = new URL(c.req.url);
|
|
1993
2155
|
return `${url.origin}/admin`;
|
|
1994
2156
|
}
|
|
1995
2157
|
};
|
|
1996
2158
|
|
|
1997
|
-
// src/controllers/preview.controller.
|
|
2159
|
+
// src/controllers/preview.controller.js
|
|
1998
2160
|
import { SignJWT as SignJWT3, jwtVerify as jwtVerify3 } from "jose";
|
|
1999
2161
|
import { TextEncoder as TextEncoder3 } from "util";
|
|
2000
2162
|
var PreviewController = class {
|
|
@@ -2037,7 +2199,7 @@ var PreviewController = class {
|
|
|
2037
2199
|
}
|
|
2038
2200
|
};
|
|
2039
2201
|
|
|
2040
|
-
// src/middleware/auth.
|
|
2202
|
+
// src/middleware/auth.js
|
|
2041
2203
|
function requireAuth() {
|
|
2042
2204
|
return async (c, next) => {
|
|
2043
2205
|
const authHeader = c.req.header("Authorization");
|
|
@@ -2069,7 +2231,7 @@ function optionalAuth() {
|
|
|
2069
2231
|
};
|
|
2070
2232
|
}
|
|
2071
2233
|
|
|
2072
|
-
// src/utils/swagger.
|
|
2234
|
+
// src/utils/swagger.js
|
|
2073
2235
|
function getSwaggerHtml(specUrl = "/api/openapi.json") {
|
|
2074
2236
|
return `
|
|
2075
2237
|
<!DOCTYPE html>
|
|
@@ -2118,7 +2280,7 @@ function getSwaggerHtml(specUrl = "/api/openapi.json") {
|
|
|
2118
2280
|
`;
|
|
2119
2281
|
}
|
|
2120
2282
|
|
|
2121
|
-
// src/router.
|
|
2283
|
+
// src/router.js
|
|
2122
2284
|
function accessGate(target, action) {
|
|
2123
2285
|
return async (c, next) => {
|
|
2124
2286
|
const user = c.get("user");
|
|
@@ -2135,7 +2297,8 @@ function accessGate(target, action) {
|
|
|
2135
2297
|
};
|
|
2136
2298
|
}
|
|
2137
2299
|
async function checkAccess(access, accessArgs) {
|
|
2138
|
-
if (access === void 0 || access === null)
|
|
2300
|
+
if (access === void 0 || access === null)
|
|
2301
|
+
return true;
|
|
2139
2302
|
if (typeof access === "function") {
|
|
2140
2303
|
try {
|
|
2141
2304
|
const result = await access(accessArgs);
|
|
@@ -2151,7 +2314,8 @@ async function checkAccess(access, accessArgs) {
|
|
|
2151
2314
|
return true;
|
|
2152
2315
|
}
|
|
2153
2316
|
function serializeFieldForApi(f) {
|
|
2154
|
-
if (!f)
|
|
2317
|
+
if (!f)
|
|
2318
|
+
return f;
|
|
2155
2319
|
const serialized = { ...f };
|
|
2156
2320
|
if (serialized.admin?.hooks) {
|
|
2157
2321
|
const hooks = { ...serialized.admin.hooks };
|
|
@@ -2184,8 +2348,10 @@ function registerRoutes(app, config) {
|
|
|
2184
2348
|
let globals = [...config.globals];
|
|
2185
2349
|
if (siteId && config.onSchemaFetch) {
|
|
2186
2350
|
const dynamic = await config.onSchemaFetch(siteId);
|
|
2187
|
-
if (dynamic.collections)
|
|
2188
|
-
|
|
2351
|
+
if (dynamic.collections)
|
|
2352
|
+
collections = [...collections, ...dynamic.collections];
|
|
2353
|
+
if (dynamic.globals)
|
|
2354
|
+
globals = [...globals, ...dynamic.globals];
|
|
2189
2355
|
if (dynamic.admin) {
|
|
2190
2356
|
config.admin = { ...config.admin, ...dynamic.admin };
|
|
2191
2357
|
}
|
|
@@ -2196,8 +2362,10 @@ function registerRoutes(app, config) {
|
|
|
2196
2362
|
const user = c.get("user");
|
|
2197
2363
|
const accessArgs = { user, req: c.req, doc: null };
|
|
2198
2364
|
const serializeAccess = async (access) => {
|
|
2199
|
-
if (typeof access === "string")
|
|
2200
|
-
|
|
2365
|
+
if (typeof access === "string")
|
|
2366
|
+
return access;
|
|
2367
|
+
if (typeof access === "boolean")
|
|
2368
|
+
return access;
|
|
2201
2369
|
return checkAccess(access, accessArgs);
|
|
2202
2370
|
};
|
|
2203
2371
|
const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
@@ -2290,7 +2458,8 @@ function registerRoutes(app, config) {
|
|
|
2290
2458
|
let collections = [...config.collections];
|
|
2291
2459
|
if (siteId && config.onSchemaFetch) {
|
|
2292
2460
|
const dynamic = await config.onSchemaFetch(siteId);
|
|
2293
|
-
if (dynamic.collections)
|
|
2461
|
+
if (dynamic.collections)
|
|
2462
|
+
collections = [...collections, ...dynamic.collections];
|
|
2294
2463
|
}
|
|
2295
2464
|
const user = c.get("user");
|
|
2296
2465
|
let collection = collections.find((col) => col.slug === colSlug);
|
|
@@ -2309,7 +2478,8 @@ function registerRoutes(app, config) {
|
|
|
2309
2478
|
let globals = [...config.globals];
|
|
2310
2479
|
if (siteId && config.onSchemaFetch) {
|
|
2311
2480
|
const dynamic = await config.onSchemaFetch(siteId);
|
|
2312
|
-
if (dynamic.globals)
|
|
2481
|
+
if (dynamic.globals)
|
|
2482
|
+
globals = [...globals, ...dynamic.globals];
|
|
2313
2483
|
}
|
|
2314
2484
|
const glb = globals.find((g) => g.slug === colSlug);
|
|
2315
2485
|
if (!glb) {
|
|
@@ -2367,9 +2537,12 @@ function registerRoutes(app, config) {
|
|
|
2367
2537
|
const user = c.get("user");
|
|
2368
2538
|
const key = c.req.param("key");
|
|
2369
2539
|
const scope = c.req.query("scope");
|
|
2370
|
-
if (!db)
|
|
2371
|
-
|
|
2372
|
-
if (!
|
|
2540
|
+
if (!db)
|
|
2541
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
2542
|
+
if (!user?.collection || !user.sub)
|
|
2543
|
+
return c.json({ error: true, message: "Authentication required." }, 401);
|
|
2544
|
+
if (!key)
|
|
2545
|
+
return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
2373
2546
|
const getGlobalPreference = async () => {
|
|
2374
2547
|
const globalDoc = await db.findOne({ collection: "__global_preferences", id: key });
|
|
2375
2548
|
return globalDoc ? globalDoc.value : null;
|
|
@@ -2379,7 +2552,8 @@ function registerRoutes(app, config) {
|
|
|
2379
2552
|
return c.json({ key, value: globalValue2 });
|
|
2380
2553
|
}
|
|
2381
2554
|
const doc = await db.findOne({ collection: user.collection, id: user.sub });
|
|
2382
|
-
if (!doc)
|
|
2555
|
+
if (!doc)
|
|
2556
|
+
return c.json({ error: true, message: "User not found." }, 404);
|
|
2383
2557
|
const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? doc.__preferences : {};
|
|
2384
2558
|
if (key in preferences) {
|
|
2385
2559
|
return c.json({ key, value: preferences[key] ?? null });
|
|
@@ -2392,9 +2566,12 @@ function registerRoutes(app, config) {
|
|
|
2392
2566
|
const user = c.get("user");
|
|
2393
2567
|
const key = c.req.param("key");
|
|
2394
2568
|
const scope = c.req.query("scope");
|
|
2395
|
-
if (!db)
|
|
2396
|
-
|
|
2397
|
-
if (!
|
|
2569
|
+
if (!db)
|
|
2570
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
2571
|
+
if (!user?.collection || !user.sub)
|
|
2572
|
+
return c.json({ error: true, message: "Authentication required." }, 401);
|
|
2573
|
+
if (!key)
|
|
2574
|
+
return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
2398
2575
|
const body = await c.req.json().catch(() => ({}));
|
|
2399
2576
|
if (scope === "global") {
|
|
2400
2577
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
@@ -2417,7 +2594,8 @@ function registerRoutes(app, config) {
|
|
|
2417
2594
|
return c.json({ key, value: body.value });
|
|
2418
2595
|
}
|
|
2419
2596
|
const doc = await db.findOne({ collection: user.collection, id: user.sub });
|
|
2420
|
-
if (!doc)
|
|
2597
|
+
if (!doc)
|
|
2598
|
+
return c.json({ error: true, message: "User not found." }, 404);
|
|
2421
2599
|
const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? doc.__preferences : {};
|
|
2422
2600
|
const nextPreferences = { ...preferences, [key]: body.value };
|
|
2423
2601
|
await db.update({
|
|
@@ -2432,9 +2610,12 @@ function registerRoutes(app, config) {
|
|
|
2432
2610
|
const user = c.get("user");
|
|
2433
2611
|
const key = c.req.param("key");
|
|
2434
2612
|
const scope = c.req.query("scope");
|
|
2435
|
-
if (!db)
|
|
2436
|
-
|
|
2437
|
-
if (!
|
|
2613
|
+
if (!db)
|
|
2614
|
+
return c.json({ message: "Database not configured" }, 500);
|
|
2615
|
+
if (!user?.collection || !user.sub)
|
|
2616
|
+
return c.json({ error: true, message: "Authentication required." }, 401);
|
|
2617
|
+
if (!key)
|
|
2618
|
+
return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
2438
2619
|
if (scope === "global") {
|
|
2439
2620
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
2440
2621
|
if (!isAdminUser) {
|
|
@@ -2444,7 +2625,8 @@ function registerRoutes(app, config) {
|
|
|
2444
2625
|
return c.json({ success: true });
|
|
2445
2626
|
}
|
|
2446
2627
|
const doc = await db.findOne({ collection: user.collection, id: user.sub });
|
|
2447
|
-
if (!doc)
|
|
2628
|
+
if (!doc)
|
|
2629
|
+
return c.json({ error: true, message: "User not found." }, 404);
|
|
2448
2630
|
const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? { ...doc.__preferences } : {};
|
|
2449
2631
|
delete preferences[key];
|
|
2450
2632
|
await db.update({
|
|
@@ -2480,7 +2662,8 @@ function registerRoutes(app, config) {
|
|
|
2480
2662
|
app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
|
|
2481
2663
|
app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
|
|
2482
2664
|
for (const collection of config.collections) {
|
|
2483
|
-
if (!collection.auth)
|
|
2665
|
+
if (!collection.auth)
|
|
2666
|
+
continue;
|
|
2484
2667
|
const path = `/api/collections/${collection.slug}`;
|
|
2485
2668
|
const authController = new AuthController(collection);
|
|
2486
2669
|
app.post(`${path}/login`, (c) => authController.login(c));
|
|
@@ -2582,25 +2765,39 @@ function registerRoutes(app, config) {
|
|
|
2582
2765
|
if (collection.auth && id) {
|
|
2583
2766
|
const authController = new AuthController(collection);
|
|
2584
2767
|
const method2 = c.req.method;
|
|
2585
|
-
if (method2 === "POST" && id === "login")
|
|
2586
|
-
|
|
2587
|
-
if (method2 === "
|
|
2588
|
-
|
|
2589
|
-
if (method2 === "
|
|
2590
|
-
|
|
2768
|
+
if (method2 === "POST" && id === "login")
|
|
2769
|
+
return authController.login(c);
|
|
2770
|
+
if (method2 === "POST" && id === "logout")
|
|
2771
|
+
return authController.logout(c);
|
|
2772
|
+
if (method2 === "GET" && id === "me")
|
|
2773
|
+
return authController.me(c);
|
|
2774
|
+
if (method2 === "POST" && id === "refresh-token")
|
|
2775
|
+
return authController.refreshToken(c);
|
|
2776
|
+
if (method2 === "POST" && id === "forgot-password")
|
|
2777
|
+
return authController.forgotPassword(c);
|
|
2778
|
+
if (method2 === "POST" && id === "reset-password")
|
|
2779
|
+
return authController.resetPassword(c);
|
|
2591
2780
|
}
|
|
2592
2781
|
const controller = new CollectionController(collection);
|
|
2593
2782
|
const method = c.req.method;
|
|
2594
2783
|
if (id) {
|
|
2595
|
-
if (method === "GET")
|
|
2596
|
-
|
|
2597
|
-
if (method === "
|
|
2598
|
-
|
|
2599
|
-
if (method === "
|
|
2600
|
-
|
|
2784
|
+
if (method === "GET")
|
|
2785
|
+
return controller.findOne(c);
|
|
2786
|
+
if (method === "PATCH")
|
|
2787
|
+
return controller.update(c);
|
|
2788
|
+
if (method === "DELETE" && id === "delete-many")
|
|
2789
|
+
return controller.deleteMany(c);
|
|
2790
|
+
if (method === "DELETE")
|
|
2791
|
+
return controller.delete(c);
|
|
2792
|
+
if (method === "POST" && id === "media")
|
|
2793
|
+
return controller.create(c);
|
|
2794
|
+
if (method === "POST" && id === "seed")
|
|
2795
|
+
return controller.seed(c);
|
|
2601
2796
|
} else {
|
|
2602
|
-
if (method === "GET")
|
|
2603
|
-
|
|
2797
|
+
if (method === "GET")
|
|
2798
|
+
return controller.find(c);
|
|
2799
|
+
if (method === "POST")
|
|
2800
|
+
return controller.create(c);
|
|
2604
2801
|
}
|
|
2605
2802
|
}
|
|
2606
2803
|
}
|
|
@@ -2621,10 +2818,13 @@ function registerRoutes(app, config) {
|
|
|
2621
2818
|
const controller = new GlobalController(global);
|
|
2622
2819
|
const method = c.req.method;
|
|
2623
2820
|
if (id) {
|
|
2624
|
-
if (method === "POST" && id === "seed")
|
|
2821
|
+
if (method === "POST" && id === "seed")
|
|
2822
|
+
return controller.seed(c);
|
|
2625
2823
|
} else {
|
|
2626
|
-
if (method === "GET")
|
|
2627
|
-
|
|
2824
|
+
if (method === "GET")
|
|
2825
|
+
return controller.get(c);
|
|
2826
|
+
if (method === "PATCH")
|
|
2827
|
+
return controller.update(c);
|
|
2628
2828
|
}
|
|
2629
2829
|
}
|
|
2630
2830
|
}
|
|
@@ -2632,7 +2832,7 @@ function registerRoutes(app, config) {
|
|
|
2632
2832
|
});
|
|
2633
2833
|
}
|
|
2634
2834
|
|
|
2635
|
-
// src/app.
|
|
2835
|
+
// src/app.js
|
|
2636
2836
|
async function createDyrectedApp(rawConfig) {
|
|
2637
2837
|
const config = normalizeConfig(rawConfig);
|
|
2638
2838
|
const app = new Hono();
|