@actuate-media/cms-core 0.39.0 → 0.41.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 (64) hide show
  1. package/dist/__tests__/api/users-detail.test.d.ts +2 -0
  2. package/dist/__tests__/api/users-detail.test.d.ts.map +1 -0
  3. package/dist/__tests__/api/users-detail.test.js +401 -0
  4. package/dist/__tests__/api/users-detail.test.js.map +1 -0
  5. package/dist/__tests__/api/users-invite.test.js +80 -26
  6. package/dist/__tests__/api/users-invite.test.js.map +1 -1
  7. package/dist/__tests__/auth/invites.test.d.ts +2 -0
  8. package/dist/__tests__/auth/invites.test.d.ts.map +1 -0
  9. package/dist/__tests__/auth/invites.test.js +181 -0
  10. package/dist/__tests__/auth/invites.test.js.map +1 -0
  11. package/dist/__tests__/auth/roles.test.d.ts +2 -0
  12. package/dist/__tests__/auth/roles.test.d.ts.map +1 -0
  13. package/dist/__tests__/auth/roles.test.js +60 -0
  14. package/dist/__tests__/auth/roles.test.js.map +1 -0
  15. package/dist/__tests__/auth/team.test.d.ts +2 -0
  16. package/dist/__tests__/auth/team.test.d.ts.map +1 -0
  17. package/dist/__tests__/auth/team.test.js +139 -0
  18. package/dist/__tests__/auth/team.test.js.map +1 -0
  19. package/dist/__tests__/auth/user-admin.test.d.ts +2 -0
  20. package/dist/__tests__/auth/user-admin.test.d.ts.map +1 -0
  21. package/dist/__tests__/auth/user-admin.test.js +198 -0
  22. package/dist/__tests__/auth/user-admin.test.js.map +1 -0
  23. package/dist/api/handlers.d.ts.map +1 -1
  24. package/dist/api/handlers.js +883 -16
  25. package/dist/api/handlers.js.map +1 -1
  26. package/dist/auth/index.d.ts +4 -2
  27. package/dist/auth/index.d.ts.map +1 -1
  28. package/dist/auth/index.js +2 -1
  29. package/dist/auth/index.js.map +1 -1
  30. package/dist/auth/invites.d.ts +71 -0
  31. package/dist/auth/invites.d.ts.map +1 -0
  32. package/dist/auth/invites.js +279 -0
  33. package/dist/auth/invites.js.map +1 -0
  34. package/dist/auth/reset.d.ts.map +1 -1
  35. package/dist/auth/reset.js +27 -4
  36. package/dist/auth/reset.js.map +1 -1
  37. package/dist/auth/roles.d.ts +51 -0
  38. package/dist/auth/roles.d.ts.map +1 -0
  39. package/dist/auth/roles.js +159 -0
  40. package/dist/auth/roles.js.map +1 -0
  41. package/dist/auth/team.d.ts +86 -0
  42. package/dist/auth/team.d.ts.map +1 -0
  43. package/dist/auth/team.js +270 -0
  44. package/dist/auth/team.js.map +1 -0
  45. package/dist/auth/user-admin.d.ts +118 -0
  46. package/dist/auth/user-admin.d.ts.map +1 -0
  47. package/dist/auth/user-admin.js +292 -0
  48. package/dist/auth/user-admin.js.map +1 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +2 -0
  52. package/dist/index.js.map +1 -1
  53. package/package.json +6 -1
  54. package/prisma/migrations/0011_user_roles_owner_viewer/migration.sql +6 -0
  55. package/prisma/migrations/0012_invites_and_user_lifecycle/migration.sql +40 -0
  56. package/prisma/schema.prisma +43 -0
  57. package/dist/__tests__/auth/invite.test.d.ts +0 -2
  58. package/dist/__tests__/auth/invite.test.d.ts.map +0 -1
  59. package/dist/__tests__/auth/invite.test.js +0 -151
  60. package/dist/__tests__/auth/invite.test.js.map +0 -1
  61. package/dist/auth/invite.d.ts +0 -37
  62. package/dist/auth/invite.d.ts.map +0 -1
  63. package/dist/auth/invite.js +0 -104
  64. package/dist/auth/invite.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=users-detail.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users-detail.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/api/users-detail.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,401 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import { handleActuateAPI } from '../../api/index.js';
3
+ import { createSession } from '../../auth/session.js';
4
+ import { generateToken as generateCsrfToken } from '../../security/csrf.js';
5
+ import { initDB } from '../../db.js';
6
+ import { setEmailSender } from '../../email/sender.js';
7
+ import { setActuateConfig } from '../../config/runtime.js';
8
+ /**
9
+ * Integration tests for the User detail drawer + security action routes:
10
+ * GET /users/:id, sessions list/revoke/revoke-all, permissions, suspend,
11
+ * reactivate, lock/unlock, force/send password reset, require-mfa, mfa-reset.
12
+ *
13
+ * Focus: response contracts + the access-control guards (admin-only, Owner-only
14
+ * on Owners, last-Owner protection, self-action blocks, reauth on MFA reset).
15
+ */
16
+ const SECRET = 'test-secret-for-users-detail-routes-1234567890abc';
17
+ function createMockDB(userRows, sessionRows = []) {
18
+ const users = userRows.map((r) => ({ isActive: true, passwordHash: 'h', ...r }));
19
+ const sessions = [...sessionRows];
20
+ const audit = [];
21
+ const resetTokens = [];
22
+ return {
23
+ _users: users,
24
+ _sessions: sessions,
25
+ _audit: audit,
26
+ _resetTokens: resetTokens,
27
+ user: {
28
+ findMany: async () => users,
29
+ findUnique: async ({ where }) => users.find((u) => u.id === where.id) ?? null,
30
+ findFirst: async ({ where }) => {
31
+ if (where?.email?.equals) {
32
+ const t = String(where.email.equals).toLowerCase();
33
+ return users.find((u) => u.email.toLowerCase() === t) ?? null;
34
+ }
35
+ return null;
36
+ },
37
+ count: async ({ where } = {}) => users.filter((u) => (where?.role ? u.role === where.role : true) &&
38
+ (where?.isActive !== undefined ? (u.isActive ?? true) === where.isActive : true) &&
39
+ (where?.id?.not ? u.id !== where.id.not : true)).length,
40
+ update: async ({ where, data }) => {
41
+ const u = users.find((x) => x.id === where.id);
42
+ Object.assign(u, data);
43
+ return u;
44
+ },
45
+ },
46
+ session: {
47
+ findMany: async ({ where } = {}) => sessions.filter((s) => (where?.userId ? s.userId === where.userId : true)),
48
+ findUnique: async ({ where }) => {
49
+ const found = sessions.find((s) => s.id === where.id);
50
+ if (found)
51
+ return found;
52
+ // Auth session lookup performed by extractSession — the JWT carries a
53
+ // `session-<userId>` id that has no corresponding row in these tests.
54
+ // Treat it as a valid, non-revoked session so auth succeeds.
55
+ if (String(where.id).startsWith('session-')) {
56
+ return { id: where.id, revokedAt: null, expiresAt: new Date(Date.now() + 1e8) };
57
+ }
58
+ return null;
59
+ },
60
+ update: async ({ where, data }) => {
61
+ const s = sessions.find((x) => x.id === where.id);
62
+ Object.assign(s, data);
63
+ return s;
64
+ },
65
+ updateMany: async ({ where, data }) => {
66
+ let n = 0;
67
+ for (const s of sessions) {
68
+ const matchUser = where?.userId ? s.userId === where.userId : true;
69
+ const matchRevoked = where?.revokedAt === null ? s.revokedAt === null : true;
70
+ const matchNot = where?.id?.not ? s.id !== where.id.not : true;
71
+ if (matchUser && matchRevoked && matchNot) {
72
+ Object.assign(s, data);
73
+ n++;
74
+ }
75
+ }
76
+ return { count: n };
77
+ },
78
+ },
79
+ auditLog: {
80
+ findMany: async () => audit,
81
+ create: async ({ data }) => {
82
+ audit.push(data);
83
+ return data;
84
+ },
85
+ },
86
+ oAuthAccount: { findMany: async () => [] },
87
+ document: {
88
+ count: async () => 0,
89
+ updateMany: async ({ where }) => {
90
+ // Reassign any docs "owned" by the from-user in this lightweight model.
91
+ const n = where?.createdById === 'u-owns' ? 2 : 0;
92
+ return { count: n };
93
+ },
94
+ },
95
+ media: { count: async () => 0, updateMany: async () => ({ count: 0 }) },
96
+ contentTemplate: { count: async () => 0, updateMany: async () => ({ count: 0 }) },
97
+ passwordResetToken: {
98
+ updateMany: async () => ({ count: 0 }),
99
+ create: async ({ data }) => {
100
+ resetTokens.push(data);
101
+ return data;
102
+ },
103
+ },
104
+ };
105
+ }
106
+ async function headersFor(userId, role) {
107
+ const token = await createSession({ userId, role, sessionId: `session-${userId}` }, { secret: SECRET });
108
+ const csrf = await generateCsrfToken();
109
+ return {
110
+ cookie: `actuate_session=${token}; actuate_csrf=${csrf}`,
111
+ 'x-csrf-token': csrf,
112
+ 'content-type': 'application/json',
113
+ };
114
+ }
115
+ const BASE = 'https://cms.example.com/api/cms';
116
+ describe('User detail + security action routes', () => {
117
+ const original = {
118
+ CMS_SECRET: process.env.CMS_SECRET,
119
+ CMS_SESSION_SECRET: process.env.CMS_SESSION_SECRET,
120
+ NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
121
+ };
122
+ beforeEach(() => {
123
+ process.env.CMS_SECRET = SECRET;
124
+ delete process.env.CMS_SESSION_SECRET;
125
+ process.env.NEXT_PUBLIC_SITE_URL = 'https://cms.example.com';
126
+ setEmailSender(null);
127
+ // Pin a clean runtime config so a `platform.email` adapter leaked onto the
128
+ // global config by a previously-run test file can't make the admin
129
+ // password-reset flow await a foreign (possibly hanging) sender instead of
130
+ // the one registered via `setEmailSender`.
131
+ setActuateConfig({ admin: { path: '/admin' } });
132
+ });
133
+ afterEach(() => {
134
+ setEmailSender(null);
135
+ for (const [k, v] of Object.entries(original)) {
136
+ if (v === undefined)
137
+ delete process.env[k];
138
+ else
139
+ process.env[k] = v;
140
+ }
141
+ });
142
+ function makeHandler(users, sessions = []) {
143
+ const db = createMockDB(users, sessions);
144
+ initDB(db);
145
+ return { handler: handleActuateAPI({ prismaClient: db }), db };
146
+ }
147
+ const admin = () => ({
148
+ id: 'admin-1',
149
+ email: 'admin@a.com',
150
+ name: 'Admin',
151
+ role: 'ADMIN',
152
+ });
153
+ const owner = () => ({
154
+ id: 'owner-1',
155
+ email: 'owner@a.com',
156
+ name: 'Owner',
157
+ role: 'OWNER',
158
+ });
159
+ function req(path, headers, method = 'GET', body) {
160
+ return new Request(`${BASE}${path}`, {
161
+ method,
162
+ headers,
163
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
164
+ });
165
+ }
166
+ it('GET /users/:id returns the detail shape (admin-only)', async () => {
167
+ const { handler } = makeHandler([
168
+ admin(),
169
+ { id: 'u2', email: 'p@a.com', name: 'Priya', role: 'EDITOR' },
170
+ ]);
171
+ const res = await handler(req('/users/u2', await headersFor('admin-1', 'ADMIN')));
172
+ expect(res.status).toBe(200);
173
+ const body = (await res.json());
174
+ expect(body.data.id).toBe('u2');
175
+ expect(body.data.roleName).toBe('Editor');
176
+ expect(body.data.status).toBe('active');
177
+ });
178
+ it('rejects a non-admin (editor) with 403', async () => {
179
+ const { handler } = makeHandler([{ id: 'ed-1', email: 'e@a.com', name: 'Ed', role: 'EDITOR' }]);
180
+ const res = await handler(req('/users/ed-1', await headersFor('ed-1', 'EDITOR')));
181
+ expect(res.status).toBe(403);
182
+ });
183
+ it('GET /users/:id/sessions lists sessions with current marker', async () => {
184
+ const { handler } = makeHandler([admin()], [
185
+ {
186
+ id: 'session-admin-1',
187
+ userId: 'admin-1',
188
+ userAgent: 'Chrome',
189
+ expiresAt: new Date(Date.now() + 86400000),
190
+ createdAt: new Date(),
191
+ revokedAt: null,
192
+ },
193
+ ]);
194
+ const res = await handler(req('/users/admin-1/sessions', await headersFor('admin-1', 'ADMIN')));
195
+ expect(res.status).toBe(200);
196
+ const body = (await res.json());
197
+ expect(body.data[0].current).toBe(true);
198
+ expect(body.data[0].active).toBe(true);
199
+ });
200
+ it('DELETE /users/:id/sessions/:sessionId revokes one session', async () => {
201
+ const { handler, db } = makeHandler([admin(), { id: 'u2', email: 'p@a.com', name: 'Priya', role: 'EDITOR' }], [
202
+ {
203
+ id: 'sx',
204
+ userId: 'u2',
205
+ expiresAt: new Date(Date.now() + 86400000),
206
+ createdAt: new Date(),
207
+ revokedAt: null,
208
+ },
209
+ ]);
210
+ const res = await handler(req('/users/u2/sessions/sx', await headersFor('admin-1', 'ADMIN'), 'DELETE'));
211
+ expect(res.status).toBe(200);
212
+ expect(db._sessions[0].revokedAt).toBeInstanceOf(Date);
213
+ });
214
+ it('POST /users/:id/sessions/revoke-all revokes every active session for another user', async () => {
215
+ const { handler, db } = makeHandler([admin(), { id: 'u2', email: 'p@a.com', name: 'Priya', role: 'EDITOR' }], [
216
+ {
217
+ id: 's1',
218
+ userId: 'u2',
219
+ expiresAt: new Date(Date.now() + 1e8),
220
+ createdAt: new Date(),
221
+ revokedAt: null,
222
+ },
223
+ {
224
+ id: 's2',
225
+ userId: 'u2',
226
+ expiresAt: new Date(Date.now() + 1e8),
227
+ createdAt: new Date(),
228
+ revokedAt: null,
229
+ },
230
+ ]);
231
+ const res = await handler(req('/users/u2/sessions/revoke-all', await headersFor('admin-1', 'ADMIN'), 'POST'));
232
+ expect(res.status).toBe(200);
233
+ expect(db._sessions.every((s) => s.revokedAt instanceof Date)).toBe(true);
234
+ });
235
+ it('GET /users/:id/permissions returns derived permissions', async () => {
236
+ const { handler } = makeHandler([
237
+ admin(),
238
+ { id: 'u2', email: 'v@a.com', name: 'V', role: 'VIEWER' },
239
+ ]);
240
+ const res = await handler(req('/users/u2/permissions', await headersFor('admin-1', 'ADMIN')));
241
+ const body = (await res.json());
242
+ expect(body.data.roleName).toBe('Viewer');
243
+ expect(body.data.permissions).toEqual(['dashboard.view', 'content.view']);
244
+ });
245
+ it('POST /users/:id/suspend suspends a user and revokes sessions', async () => {
246
+ const { handler, db } = makeHandler([admin(), { id: 'u2', email: 'p@a.com', name: 'Priya', role: 'EDITOR' }], [
247
+ {
248
+ id: 's1',
249
+ userId: 'u2',
250
+ expiresAt: new Date(Date.now() + 1e8),
251
+ createdAt: new Date(),
252
+ revokedAt: null,
253
+ },
254
+ ]);
255
+ const res = await handler(req('/users/u2/suspend', await headersFor('admin-1', 'ADMIN'), 'POST'));
256
+ expect(res.status).toBe(200);
257
+ expect(db._users.find((u) => u.id === 'u2').suspendedAt).toBeInstanceOf(Date);
258
+ expect(db._sessions[0].revokedAt).toBeInstanceOf(Date);
259
+ });
260
+ it('blocks suspending your own account', async () => {
261
+ const { handler } = makeHandler([admin()]);
262
+ const res = await handler(req('/users/admin-1/suspend', await headersFor('admin-1', 'ADMIN'), 'POST'));
263
+ expect(res.status).toBe(400);
264
+ });
265
+ it('protects the last Owner from suspension', async () => {
266
+ const { handler } = makeHandler([owner(), admin()]);
267
+ const res = await handler(req('/users/owner-1/suspend', await headersFor('owner-1', 'OWNER'), 'POST'));
268
+ // owner-1 is acting on themselves here → blocked as self-action first
269
+ expect(res.status).toBe(400);
270
+ });
271
+ it('forbids a non-Owner admin from acting on an Owner', async () => {
272
+ const { handler } = makeHandler([admin(), owner()]);
273
+ const res = await handler(req('/users/owner-1/suspend', await headersFor('admin-1', 'ADMIN'), 'POST'));
274
+ expect(res.status).toBe(403);
275
+ });
276
+ it('blocks suspending the last Owner (acted on by another Owner)', async () => {
277
+ const { handler } = makeHandler([
278
+ owner(),
279
+ { id: 'owner-2', email: 'o2@a.com', name: 'O2', role: 'OWNER', isActive: false },
280
+ ]);
281
+ const res = await handler(req('/users/owner-1/suspend', await headersFor('owner-2', 'OWNER'), 'POST'));
282
+ // owner-2 is inactive but still the actor; owner-1 is the only active Owner.
283
+ expect(res.status).toBe(409);
284
+ });
285
+ it('reactivate clears suspension', async () => {
286
+ const { handler, db } = makeHandler([
287
+ admin(),
288
+ { id: 'u2', email: 'p@a.com', name: 'Priya', role: 'EDITOR', suspendedAt: new Date() },
289
+ ]);
290
+ const res = await handler(req('/users/u2/reactivate', await headersFor('admin-1', 'ADMIN'), 'POST'));
291
+ expect(res.status).toBe(200);
292
+ expect(db._users.find((u) => u.id === 'u2').suspendedAt).toBeNull();
293
+ });
294
+ it('force-password-reset sets the flag', async () => {
295
+ const { handler, db } = makeHandler([
296
+ admin(),
297
+ { id: 'u2', email: 'p@a.com', name: 'P', role: 'EDITOR' },
298
+ ]);
299
+ const res = await handler(req('/users/u2/force-password-reset', await headersFor('admin-1', 'ADMIN'), 'POST', {
300
+ enabled: true,
301
+ }));
302
+ expect(res.status).toBe(200);
303
+ expect(db._users.find((u) => u.id === 'u2').forcePasswordReset).toBe(true);
304
+ });
305
+ it('require-mfa sets the flag', async () => {
306
+ const { handler, db } = makeHandler([
307
+ admin(),
308
+ { id: 'u2', email: 'p@a.com', name: 'P', role: 'EDITOR' },
309
+ ]);
310
+ const res = await handler(req('/users/u2/require-mfa', await headersFor('admin-1', 'ADMIN'), 'POST', {
311
+ required: true,
312
+ }));
313
+ expect(res.status).toBe(200);
314
+ expect(db._users.find((u) => u.id === 'u2').mfaRequired).toBe(true);
315
+ });
316
+ it('mfa-reset requires reauthentication (401) when no password header is sent', async () => {
317
+ const { handler } = makeHandler([
318
+ admin(),
319
+ { id: 'u2', email: 'p@a.com', name: 'P', role: 'EDITOR', totpEnabled: true },
320
+ ]);
321
+ const res = await handler(req('/users/u2/mfa-reset', await headersFor('admin-1', 'ADMIN'), 'POST'));
322
+ expect(res.status).toBe(401);
323
+ const body = (await res.json());
324
+ expect(body.code ?? body.error).toBeTruthy();
325
+ });
326
+ it('password-reset is rejected for SSO-only accounts (409)', async () => {
327
+ const { handler } = makeHandler([
328
+ admin(),
329
+ { id: 'u2', email: 'sso@a.com', name: 'SSO', role: 'EDITOR', passwordHash: null },
330
+ ]);
331
+ const res = await handler(req('/users/u2/password-reset', await headersFor('admin-1', 'ADMIN'), 'POST'));
332
+ expect(res.status).toBe(409);
333
+ });
334
+ it('password-reset sends a reset email for a normal user when email is configured', async () => {
335
+ const sent = [];
336
+ setEmailSender({ send: async (o) => void sent.push({ to: o.to }) });
337
+ const { handler } = makeHandler([
338
+ admin(),
339
+ { id: 'u2', email: 'p@a.com', name: 'P', role: 'EDITOR', passwordHash: 'h' },
340
+ ]);
341
+ const res = await handler(req('/users/u2/password-reset', await headersFor('admin-1', 'ADMIN'), 'POST'));
342
+ expect(res.status).toBe(200);
343
+ expect(sent).toEqual([{ to: 'p@a.com' }]);
344
+ });
345
+ it('transfer-content reassigns ownership to an active recipient', async () => {
346
+ const { handler } = makeHandler([
347
+ admin(),
348
+ { id: 'u-owns', email: 'owns@a.com', name: 'Owns', role: 'EDITOR' },
349
+ { id: 'u-recv', email: 'recv@a.com', name: 'Recv', role: 'EDITOR' },
350
+ ]);
351
+ const res = await handler(req('/users/u-owns/transfer-content', await headersFor('admin-1', 'ADMIN'), 'POST', {
352
+ toUserId: 'u-recv',
353
+ }));
354
+ expect(res.status).toBe(200);
355
+ const body = (await res.json());
356
+ expect(body.data.documents).toBe(2);
357
+ expect(body.data.total).toBe(2);
358
+ });
359
+ it('transfer-content rejects a missing recipient (400)', async () => {
360
+ const { handler } = makeHandler([
361
+ admin(),
362
+ { id: 'u-owns', email: 'o@a.com', name: 'O', role: 'EDITOR' },
363
+ ]);
364
+ const res = await handler(req('/users/u-owns/transfer-content', await headersFor('admin-1', 'ADMIN'), 'POST', {}));
365
+ expect(res.status).toBe(400);
366
+ });
367
+ it('transfer-content rejects transferring to a suspended user (409)', async () => {
368
+ const { handler } = makeHandler([
369
+ admin(),
370
+ { id: 'u-owns', email: 'o@a.com', name: 'O', role: 'EDITOR' },
371
+ { id: 'u-susp', email: 's@a.com', name: 'S', role: 'EDITOR', suspendedAt: new Date() },
372
+ ]);
373
+ const res = await handler(req('/users/u-owns/transfer-content', await headersFor('admin-1', 'ADMIN'), 'POST', {
374
+ toUserId: 'u-susp',
375
+ }));
376
+ expect(res.status).toBe(409);
377
+ });
378
+ it('transfer-content rejects transferring to the same user (400)', async () => {
379
+ const { handler } = makeHandler([
380
+ admin(),
381
+ { id: 'u-owns', email: 'o@a.com', name: 'O', role: 'EDITOR' },
382
+ ]);
383
+ const res = await handler(req('/users/u-owns/transfer-content', await headersFor('admin-1', 'ADMIN'), 'POST', {
384
+ toUserId: 'u-owns',
385
+ }));
386
+ expect(res.status).toBe(400);
387
+ });
388
+ it('lock then unlock toggles the locked state', async () => {
389
+ const { handler, db } = makeHandler([
390
+ admin(),
391
+ { id: 'u2', email: 'p@a.com', name: 'P', role: 'EDITOR' },
392
+ ]);
393
+ const lock = await handler(req('/users/u2/lock', await headersFor('admin-1', 'ADMIN'), 'POST'));
394
+ expect(lock.status).toBe(200);
395
+ expect(db._users.find((u) => u.id === 'u2').lockedAt).toBeInstanceOf(Date);
396
+ const unlock = await handler(req('/users/u2/unlock', await headersFor('admin-1', 'ADMIN'), 'POST'));
397
+ expect(unlock.status).toBe(200);
398
+ expect(db._users.find((u) => u.id === 'u2').lockedAt).toBeNull();
399
+ });
400
+ });
401
+ //# sourceMappingURL=users-detail.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users-detail.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/users-detail.test.ts"],"names":[],"mappings":"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"}
@@ -7,28 +7,77 @@ import { setEmailSender } from '../../email/sender.js';
7
7
  /**
8
8
  * Integration tests for `POST /api/cms/users/invite`.
9
9
  *
10
- * The endpoint provisions a user with an unusable password and a single-use
11
- * reset token, then returns a set-password link (and/or emails it). It is
12
- * admin-only the only multi-user provisioning path in the product.
10
+ * The endpoint records a first-class Invite (hashed single-use token) and
11
+ * provisions a placeholder user, then returns a set-password link (and/or
12
+ * emails it). It is admin-only, seat-aware, and Owner-gated for the Owner role.
13
13
  */
14
14
  const SECRET = 'test-secret-for-users-invite-routes-1234567890abc';
15
15
  function createMockDB(rows) {
16
- const users = [...rows];
16
+ const users = rows.map((r) => ({ isActive: true, ...r }));
17
17
  const tokens = [];
18
+ const invites = [];
19
+ const invite = {
20
+ findMany: async ({ where } = {}) => {
21
+ let list = invites;
22
+ if (where?.status)
23
+ list = list.filter((i) => i.status === where.status);
24
+ return list;
25
+ },
26
+ findFirst: async ({ where }) => {
27
+ if (where?.email?.equals) {
28
+ const target = String(where.email.equals).toLowerCase();
29
+ return (invites.find((i) => i.email.toLowerCase() === target && (!where.status || i.status === where.status)) ?? null);
30
+ }
31
+ return null;
32
+ },
33
+ findUnique: async ({ where }) => invites.find((i) => i.id === where.id) ?? null,
34
+ count: async ({ where } = {}) => {
35
+ let list = invites;
36
+ if (where?.status)
37
+ list = list.filter((i) => i.status === where.status);
38
+ return list.length;
39
+ },
40
+ create: async ({ data }) => {
41
+ const row = {
42
+ id: `inv_${invites.length + 1}`,
43
+ createdAt: new Date(),
44
+ ...data,
45
+ };
46
+ invites.push(row);
47
+ return row;
48
+ },
49
+ update: async ({ where, data }) => {
50
+ const row = invites.find((i) => i.id === where.id);
51
+ Object.assign(row, data);
52
+ return row;
53
+ },
54
+ updateMany: async ({ where, data }) => {
55
+ let n = 0;
56
+ for (const i of invites) {
57
+ if (!where?.status || i.status === where.status) {
58
+ Object.assign(i, data);
59
+ n++;
60
+ }
61
+ }
62
+ return { count: n };
63
+ },
64
+ };
18
65
  return {
19
66
  _users: users,
20
67
  _tokens: tokens,
68
+ _invites: invites,
21
69
  session: { findUnique: async () => ({ revokedAt: null }) },
22
70
  user: {
23
- // hasModel() probes for findMany; the invite route never calls it.
24
71
  findMany: async () => users,
25
72
  findUnique: async ({ where }) => users.find((u) => u.id === where.id) ?? null,
73
+ count: async ({ where } = {}) => users.filter((u) => (where?.role ? u.role === where.role : true) &&
74
+ (where?.isActive !== undefined ? (u.isActive ?? true) === where.isActive : true)).length,
26
75
  findFirst: async ({ where }) => {
27
76
  const target = String(where.email.equals).toLowerCase();
28
77
  return users.find((u) => u.email.toLowerCase() === target) ?? null;
29
78
  },
30
79
  create: async ({ data }) => {
31
- const u = { id: `u_${users.length + 1}`, ...data };
80
+ const u = { id: `u_${users.length + 1}`, isActive: true, ...data };
32
81
  users.push(u);
33
82
  return u;
34
83
  },
@@ -40,6 +89,7 @@ function createMockDB(rows) {
40
89
  return t;
41
90
  },
42
91
  },
92
+ invite,
43
93
  };
44
94
  }
45
95
  async function roleHeaders(userId, role) {
@@ -91,37 +141,33 @@ describe('POST /api/cms/users/invite', () => {
91
141
  const res = await handler(new Request('https://cms.example.com/api/cms/users/invite', {
92
142
  method: 'POST',
93
143
  headers: { 'content-type': 'application/json' },
94
- body: JSON.stringify({ email: 'a@example.com', role: 'author' }),
144
+ body: JSON.stringify({ email: 'a@example.com', role: 'editor' }),
95
145
  }));
96
- // CSRF runs before auth, so an anonymous POST without a token can return
97
- // either — both prove the write was blocked.
98
146
  expect([401, 403]).toContain(res.status);
99
147
  });
100
148
  it('rejects non-admin roles with 403', async () => {
101
149
  const { handler, db } = makeHandler([
102
150
  { id: 'editor-1', email: 'ed@example.com', name: 'Ed', role: 'EDITOR' },
103
151
  ]);
104
- const res = await handler(inviteRequest(await editorHeaders('editor-1'), { email: 'a@example.com', role: 'author' }));
152
+ const res = await handler(inviteRequest(await editorHeaders('editor-1'), { email: 'a@example.com', role: 'editor' }));
105
153
  expect(res.status).toBe(403);
106
- expect(db._users).toHaveLength(1);
154
+ expect(db._invites).toHaveLength(0);
107
155
  });
108
- it('creates the user + token and returns a shareable link when email is unconfigured', async () => {
156
+ it('records an invite + token and returns a shareable link when email is unconfigured', async () => {
109
157
  const { handler, db } = makeHandler([
110
158
  { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
111
159
  ]);
112
- const res = await handler(inviteRequest(await adminHeaders('admin-1'), {
113
- email: 'new@example.com',
114
- name: 'New Person',
115
- role: 'editor',
116
- }));
160
+ const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'new@example.com', role: 'editor' }));
117
161
  expect(res.status).toBe(200);
118
162
  const body = (await res.json());
119
163
  expect(body.data.success).toBe(true);
120
164
  expect(body.data.emailed).toBe(false);
121
- expect(body.data.user.role).toBe('EDITOR');
165
+ expect(body.data.invite.role).toBe('EDITOR');
122
166
  expect(body.data.inviteUrl).toContain('/admin/reset-password?token=');
123
167
  expect(db._tokens).toHaveLength(1);
124
- // The created user is the second row (after the admin fixture).
168
+ expect(db._invites).toHaveLength(1);
169
+ // The hashed token is stored — never the raw token.
170
+ expect(db._invites[0].tokenHash).not.toContain(body.data.inviteUrl.split('token=')[1]);
125
171
  expect(db._users.map((u) => u.email)).toContain('new@example.com');
126
172
  });
127
173
  it('emails the invite (and hides the link) when a sender is registered', async () => {
@@ -134,33 +180,41 @@ describe('POST /api/cms/users/invite', () => {
134
180
  const { handler } = makeHandler([
135
181
  { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
136
182
  ]);
137
- const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'new@example.com', role: 'author' }));
183
+ const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'new@example.com', role: 'viewer' }));
138
184
  const body = (await res.json());
139
185
  expect(body.data.emailed).toBe(true);
140
186
  expect(body.data.inviteUrl).toBeUndefined();
141
187
  expect(sent).toEqual([{ to: 'new@example.com' }]);
142
188
  });
143
- it('rejects a duplicate email with 400', async () => {
189
+ it('rejects a duplicate existing user with 400', async () => {
144
190
  const { handler, db } = makeHandler([
145
191
  { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
146
- { id: 'u-2', email: 'taken@example.com', name: 'Taken', role: 'AUTHOR' },
192
+ { id: 'u-2', email: 'taken@example.com', name: 'Taken', role: 'EDITOR' },
147
193
  ]);
148
- const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'Taken@Example.com', role: 'author' }));
194
+ const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'Taken@Example.com', role: 'editor' }));
149
195
  expect(res.status).toBe(400);
150
- expect(db._users).toHaveLength(2);
196
+ expect(db._invites).toHaveLength(0);
151
197
  });
152
198
  it('rejects an invalid role with 400', async () => {
153
199
  const { handler } = makeHandler([
154
200
  { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
155
201
  ]);
156
- const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'a@example.com', role: 'viewer' }));
202
+ const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'a@example.com', role: 'superuser' }));
157
203
  expect(res.status).toBe(400);
158
204
  });
205
+ it('forbids a non-Owner from inviting an Owner (403)', async () => {
206
+ const { handler, db } = makeHandler([
207
+ { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
208
+ ]);
209
+ const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'owner@example.com', role: 'owner' }));
210
+ expect(res.status).toBe(403);
211
+ expect(db._invites).toHaveLength(0);
212
+ });
159
213
  it('requires an email', async () => {
160
214
  const { handler } = makeHandler([
161
215
  { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
162
216
  ]);
163
- const res = await handler(inviteRequest(await adminHeaders('admin-1'), { role: 'author' }));
217
+ const res = await handler(inviteRequest(await adminHeaders('admin-1'), { role: 'editor' }));
164
218
  expect(res.status).toBe(400);
165
219
  });
166
220
  });