domma-cms 0.23.0 → 0.25.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.
Files changed (47) hide show
  1. package/CLAUDE.md +14 -0
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +4 -4
  4. package/admin/js/lib/crud-tutorial.js +1 -1
  5. package/admin/js/lib/project-context.js +1 -1
  6. package/admin/js/templates/api-endpoint-editor.html +120 -0
  7. package/admin/js/templates/api-endpoints.html +13 -0
  8. package/admin/js/templates/api-tokens.html +13 -0
  9. package/admin/js/templates/effects.html +752 -752
  10. package/admin/js/templates/form-submissions.html +30 -30
  11. package/admin/js/templates/forms.html +17 -17
  12. package/admin/js/templates/my-profile.html +17 -17
  13. package/admin/js/templates/role-editor.html +70 -70
  14. package/admin/js/templates/roles.html +10 -10
  15. package/admin/js/views/api-endpoint-editor.js +1 -0
  16. package/admin/js/views/api-endpoints.js +7 -0
  17. package/admin/js/views/api-tokens.js +8 -0
  18. package/admin/js/views/collection-editor.js +4 -4
  19. package/admin/js/views/index.js +1 -1
  20. package/admin/js/views/project-detail.js +1 -1
  21. package/admin/js/views/roles.js +1 -1
  22. package/bin/lib/config-merge.js +44 -44
  23. package/bin/update.js +547 -547
  24. package/config/menus/admin-sidebar.json +13 -1
  25. package/package.json +1 -1
  26. package/server/middleware/auth.js +253 -253
  27. package/server/routes/api/api-endpoints.js +96 -0
  28. package/server/routes/api/api-tokens.js +83 -0
  29. package/server/routes/api/auth.js +309 -309
  30. package/server/routes/api/collections.js +114 -17
  31. package/server/routes/api/endpoints-public.js +88 -0
  32. package/server/routes/api/navigation.js +42 -42
  33. package/server/routes/api/settings.js +141 -141
  34. package/server/routes/public.js +202 -202
  35. package/server/server.js +16 -1
  36. package/server/services/apiEndpoints.js +402 -0
  37. package/server/services/apiTokens.js +273 -0
  38. package/server/services/email.js +167 -167
  39. package/server/services/permissionRegistry.js +26 -0
  40. package/server/services/presetCollections.js +54 -0
  41. package/server/services/projects.js +18 -2
  42. package/server/services/roles.js +16 -0
  43. package/server/services/scaffolder.js +54 -1
  44. package/server/services/sidebar-migration.js +45 -0
  45. package/server/services/userProfiles.js +199 -199
  46. package/server/services/users.js +302 -302
  47. package/config/connections.json.bak +0 -9
@@ -0,0 +1,83 @@
1
+ /**
2
+ * API Tokens API
3
+ *
4
+ * GET /api/api-tokens — list tokens visible to the caller (sanitised; never the hash)
5
+ * POST /api/api-tokens — create; response carries the plaintext ONCE
6
+ * PUT /api/api-tokens/:id — rename, enable/disable, edit scopes/expiry
7
+ * DELETE /api/api-tokens/:id — revoke (immediate)
8
+ *
9
+ * Tokens are project-scoped artefacts: non-super-admin users with a
10
+ * `projects: []` access scope only see/manage tokens in their projects.
11
+ * Auth middlewares are accepted as DI options so tests can supply no-ops.
12
+ */
13
+ import {
14
+ authenticate as defaultAuthenticate,
15
+ requirePermission as defaultRequirePermission
16
+ } from '../../middleware/auth.js';
17
+ import {
18
+ createToken,
19
+ getTokenSanitised,
20
+ listTokensSanitised,
21
+ revokeToken,
22
+ updateToken
23
+ } from '../../services/apiTokens.js';
24
+ import {canSeeArtefact} from '../../services/projects.js';
25
+
26
+ /**
27
+ * Register the api-tokens routes.
28
+ *
29
+ * @param {import('fastify').FastifyInstance} fastify
30
+ * @param {{authenticate?: Function, requirePermission?: Function}} [opts]
31
+ * @returns {Promise<void>}
32
+ */
33
+ export async function apiTokensRoutes(fastify, opts = {}) {
34
+ const authenticate = opts.authenticate || defaultAuthenticate;
35
+ const requirePermission = opts.requirePermission || defaultRequirePermission;
36
+
37
+ const canRead = {preHandler: [authenticate, requirePermission('api-tokens', 'read')]};
38
+ const canCreate = {preHandler: [authenticate, requirePermission('api-tokens', 'create')]};
39
+ const canUpdate = {preHandler: [authenticate, requirePermission('api-tokens', 'update')]};
40
+ const canDelete = {preHandler: [authenticate, requirePermission('api-tokens', 'delete')]};
41
+
42
+ fastify.get('/api-tokens', canRead, async (request) => {
43
+ return listTokensSanitised(request.user);
44
+ });
45
+
46
+ fastify.post('/api-tokens', canCreate, async (request, reply) => {
47
+ const {name, project, scopes, expiresAt} = request.body || {};
48
+ if (project && !canSeeArtefact(request.user, {meta: {project}})) {
49
+ return reply.status(403).send({error: 'Access denied for this project'});
50
+ }
51
+ try {
52
+ const creator = request.user?.name || request.user?.email || null;
53
+ const {entry, plaintext} = await createToken({name, project, scopes, expiresAt, createdBy: creator});
54
+ // The plaintext appears here and nowhere else — only the hash is stored.
55
+ return reply.status(201).send({token: entry, plaintext});
56
+ } catch (err) {
57
+ return reply.status(400).send({error: err.message});
58
+ }
59
+ });
60
+
61
+ fastify.put('/api-tokens/:id', canUpdate, async (request, reply) => {
62
+ const existing = await getTokenSanitised(request.params.id);
63
+ if (!existing) return reply.status(404).send({error: 'Token not found'});
64
+ if (!canSeeArtefact(request.user, {meta: {project: existing.project}})) {
65
+ return reply.status(403).send({error: 'Access denied for this project'});
66
+ }
67
+ try {
68
+ return await updateToken(request.params.id, request.body || {});
69
+ } catch (err) {
70
+ return reply.status(400).send({error: err.message});
71
+ }
72
+ });
73
+
74
+ fastify.delete('/api-tokens/:id', canDelete, async (request, reply) => {
75
+ const existing = await getTokenSanitised(request.params.id);
76
+ if (!existing) return reply.status(404).send({error: 'Token not found'});
77
+ if (!canSeeArtefact(request.user, {meta: {project: existing.project}})) {
78
+ return reply.status(403).send({error: 'Access denied for this project'});
79
+ }
80
+ await revokeToken(request.params.id);
81
+ return {success: true};
82
+ });
83
+ }