@actuate-media/cms-core 0.39.0 → 0.40.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 +349 -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 +162 -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 +837 -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 +91 -0
  46. package/dist/auth/user-admin.d.ts.map +1 -0
  47. package/dist/auth/user-admin.js +250 -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
@@ -3,8 +3,10 @@ export type { SessionPayload, SessionOptions } from './session.js';
3
3
  export { hashPassword, verifyPassword, validatePasswordPolicy, checkPasswordBreach, } from './password.js';
4
4
  export { generateResetToken, hashToken, createPasswordReset, executePasswordReset, } from './reset.js';
5
5
  export type { ResetToken } from './reset.js';
6
- export { createUserInvite, normalizeInviteRole, VALID_INVITE_ROLES } from './invite.js';
7
- export type { InviteRole, CreateInviteInput, CreateInviteResult } from './invite.js';
6
+ export { createInvite, listInvites, resendInvite, cancelInvite, updateInviteRole, extendInvite, expireStaleInvites, markInviteAcceptedForEmail, INVITE_EXPIRY_MS, VALID_EXPIRY_DAYS, } from './invites.js';
7
+ export type { CreateInviteInput, InviteResult, SerializedInvite, InviteConfig, ListInvitesParams, } from './invites.js';
8
+ export { ASSIGNABLE_ROLES, DISPLAY_ROLES, toDisplayRole, normalizeAssignableRole, enumRolesForDisplay, isOwnerRole, roleRank, permissionsForRole, } from './roles.js';
9
+ export type { UserRoleEnum, DisplayRole, Permission } from './roles.js';
8
10
  export { generateTOTPSecret, verifyTOTP, generateBackupCodes, generateTOTPUri } from './totp.js';
9
11
  export { initiateOAuth, handleCallback, linkAccount } from './oauth.js';
10
12
  export { generateCodeVerifier, generateCodeChallenge, getAuthorizationUrl, exchangeCodeForTokens, getUserProfile, handleOAuthCallback, } from './oauth.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC1F,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACvF,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEpF,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhG,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,mBAAmB,GACpB,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC1F,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,GAClB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,mBAAmB,EACnB,WAAW,EACX,QAAQ,EACR,kBAAkB,GACnB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhG,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,mBAAmB,GACpB,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA"}
@@ -1,7 +1,8 @@
1
1
  export { createSession, verifySession, revokeSession, refreshSession } from './session.js';
2
2
  export { hashPassword, verifyPassword, validatePasswordPolicy, checkPasswordBreach, } from './password.js';
3
3
  export { generateResetToken, hashToken, createPasswordReset, executePasswordReset, } from './reset.js';
4
- export { createUserInvite, normalizeInviteRole, VALID_INVITE_ROLES } from './invite.js';
4
+ export { createInvite, listInvites, resendInvite, cancelInvite, updateInviteRole, extendInvite, expireStaleInvites, markInviteAcceptedForEmail, INVITE_EXPIRY_MS, VALID_EXPIRY_DAYS, } from './invites.js';
5
+ export { ASSIGNABLE_ROLES, DISPLAY_ROLES, toDisplayRole, normalizeAssignableRole, enumRolesForDisplay, isOwnerRole, roleRank, permissionsForRole, } from './roles.js';
5
6
  export { generateTOTPSecret, verifyTOTP, generateBackupCodes, generateTOTPUri } from './totp.js';
6
7
  export { initiateOAuth, handleCallback, linkAccount } from './oauth.js';
7
8
  export { generateCodeVerifier, generateCodeChallenge, getAuthorizationUrl, exchangeCodeForTokens, getUserProfile, handleOAuthCallback, } from './oauth.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAG1F,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAGvF,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhG,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,mBAAmB,GACpB,MAAM,YAAY,CAAA;AASnB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAG1F,OAAO,EACL,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,YAAY,CAAA;AAGnB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAQrB,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,mBAAmB,EACnB,WAAW,EACX,QAAQ,EACR,kBAAkB,GACnB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhG,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,mBAAmB,GACpB,MAAM,YAAY,CAAA;AASnB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA"}
@@ -0,0 +1,71 @@
1
+ import { type UserRoleEnum } from './roles.js';
2
+ /** Invitations are valid for a week by default — a person has to act. */
3
+ export declare const INVITE_EXPIRY_MS: number;
4
+ export declare const VALID_EXPIRY_DAYS: readonly [7, 14, 30];
5
+ export interface InviteConfig {
6
+ siteUrl: string;
7
+ /** Admin mount path; the accept link is `<siteUrl><adminPath>/reset-password`. */
8
+ adminPath?: string;
9
+ invitedByName?: string;
10
+ invitedByUserId?: string;
11
+ siteName?: string;
12
+ }
13
+ export interface CreateInviteInput {
14
+ email: string;
15
+ role: string;
16
+ message?: string;
17
+ /** Days until expiry; one of {@link VALID_EXPIRY_DAYS}. Defaults to 7. */
18
+ expiresInDays?: number;
19
+ }
20
+ export interface InviteResult {
21
+ success: boolean;
22
+ error?: string;
23
+ /** Distinguish "already invited" so the UI can offer Resend instead. */
24
+ code?: 'DUPLICATE_USER' | 'PENDING_INVITE' | 'INVALID_EMAIL' | 'INVALID_ROLE' | 'NOT_FOUND';
25
+ /** The accept link. Returned only when email was NOT sent, so an admin can share it. */
26
+ inviteUrl?: string;
27
+ emailed?: boolean;
28
+ invite?: SerializedInvite;
29
+ }
30
+ export interface SerializedInvite {
31
+ id: string;
32
+ email: string;
33
+ role: UserRoleEnum;
34
+ roleName: string;
35
+ status: string;
36
+ invitedByUserId: string | null;
37
+ expiresAt: string;
38
+ createdAt: string;
39
+ expired: boolean;
40
+ }
41
+ /**
42
+ * Create an invitation. Provisions a placeholder user, persists a PENDING
43
+ * invite + single-use reset token, and emails the accept link.
44
+ */
45
+ export declare function createInvite(db: any, input: CreateInviteInput, config: InviteConfig): Promise<InviteResult>;
46
+ export interface ListInvitesParams {
47
+ status?: string;
48
+ search?: string;
49
+ }
50
+ /**
51
+ * List invites, lazily flipping any past-expiry PENDING rows to EXPIRED so the
52
+ * status is always honest without a background job.
53
+ */
54
+ export declare function listInvites(db: any, params?: ListInvitesParams): Promise<SerializedInvite[]>;
55
+ /** Flip PENDING invites whose expiry has passed to EXPIRED. Returns the count. */
56
+ export declare function expireStaleInvites(db: any): Promise<number>;
57
+ /** Resend (or revive an expired) invite: fresh token, reset expiry, re-email. */
58
+ export declare function resendInvite(db: any, inviteId: string, config: InviteConfig): Promise<InviteResult>;
59
+ /** Cancel an invite and deactivate its placeholder user so the link is dead. */
60
+ export declare function cancelInvite(db: any, inviteId: string): Promise<InviteResult>;
61
+ /** Change the role on a pending invite (and its placeholder user). */
62
+ export declare function updateInviteRole(db: any, inviteId: string, role: string): Promise<InviteResult>;
63
+ /** Extend a pending invite's expiry and re-sync its reset token expiry. */
64
+ export declare function extendInvite(db: any, inviteId: string, days: number): Promise<InviteResult>;
65
+ /**
66
+ * Mark the pending invite for an email as ACCEPTED. Called when a user sets
67
+ * their password through the reset/accept flow. Safe to call for non-invited
68
+ * users — it simply updates zero rows.
69
+ */
70
+ export declare function markInviteAcceptedForEmail(db: any, email: string, userId: string): Promise<boolean>;
71
+ //# sourceMappingURL=invites.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invites.d.ts","sourceRoot":"","sources":["../../src/auth/invites.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Invite lifecycle service (backed by the `actuate_invites` table).
3
+ *
4
+ * The `Invite` row is the authoritative record of an invitation — it drives the
5
+ * Users admin UI (pending list, resend/cancel/extend/change-role) and seat
6
+ * accounting. To keep a working, secure acceptance flow without a separate
7
+ * invitee page, creating an invite also provisions a placeholder user with an
8
+ * unusable password (the proven Batch-2 mechanism) and issues a single-use,
9
+ * hashed password-reset token. The invitee sets their password via the existing
10
+ * `/reset-password` page; completing that flips the invite to ACCEPTED (see
11
+ * `markInviteAcceptedForEmail`, called from `executePasswordReset`).
12
+ *
13
+ * Members are de-duplicated against pending invites by email so a not-yet-
14
+ * accepted invite is shown only as a pending row, never double-counted.
15
+ */
16
+ import { randomBytes } from 'node:crypto';
17
+ import { hashPassword } from './password.js';
18
+ import { generateResetToken } from './reset.js';
19
+ import { userInvitationEmailTemplate } from './invite-email.js';
20
+ import { resolveEmailSender } from '../email/sender.js';
21
+ import { normalizeAssignableRole, toDisplayRole } from './roles.js';
22
+ /** Invitations are valid for a week by default — a person has to act. */
23
+ export const INVITE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
24
+ export const VALID_EXPIRY_DAYS = [7, 14, 30];
25
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
26
+ function expiryDays(input) {
27
+ const v = input ?? 7;
28
+ return VALID_EXPIRY_DAYS.includes(v) ? v : 7;
29
+ }
30
+ function buildAcceptUrl(rawToken, config) {
31
+ const base = config.siteUrl.replace(/\/$/, '');
32
+ const adminPath = (config.adminPath ?? '/admin').replace(/\/$/, '');
33
+ return `${base}${adminPath}/reset-password?token=${rawToken}`;
34
+ }
35
+ function serializeInvite(row) {
36
+ return {
37
+ id: row.id,
38
+ email: row.email,
39
+ role: row.role,
40
+ roleName: toDisplayRole(row.role),
41
+ status: row.status,
42
+ invitedByUserId: row.invitedByUserId ?? null,
43
+ expiresAt: row.expiresAt instanceof Date ? row.expiresAt.toISOString() : row.expiresAt,
44
+ createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : row.createdAt,
45
+ expired: new Date(row.expiresAt).getTime() < Date.now(),
46
+ };
47
+ }
48
+ async function sendInviteEmail(email, name, role, inviteUrl, config) {
49
+ const sender = resolveEmailSender();
50
+ if (!sender)
51
+ return false;
52
+ const tpl = userInvitationEmailTemplate({
53
+ inviteUrl,
54
+ userName: name,
55
+ role,
56
+ invitedByName: config.invitedByName,
57
+ siteName: config.siteName,
58
+ });
59
+ try {
60
+ await sender.send({ to: email, subject: tpl.subject, html: tpl.html, text: tpl.text });
61
+ return true;
62
+ }
63
+ catch {
64
+ // Best-effort: the invite + token are persisted, so the admin can share the
65
+ // returned link manually. Don't fail the whole invite on a send error.
66
+ return false;
67
+ }
68
+ }
69
+ /**
70
+ * Create an invitation. Provisions a placeholder user, persists a PENDING
71
+ * invite + single-use reset token, and emails the accept link.
72
+ */
73
+ export async function createInvite(db, input, config) {
74
+ const email = input.email.toLowerCase().trim();
75
+ if (!EMAIL_RE.test(email)) {
76
+ return { success: false, error: 'Enter a valid email address.', code: 'INVALID_EMAIL' };
77
+ }
78
+ const role = normalizeAssignableRole(input.role);
79
+ if (!role) {
80
+ return { success: false, error: 'Select a valid role.', code: 'INVALID_ROLE' };
81
+ }
82
+ // Already a pending invite? Offer resend rather than a duplicate.
83
+ const pending = await db.invite.findFirst({
84
+ where: { email: { equals: email, mode: 'insensitive' }, status: 'PENDING' },
85
+ });
86
+ if (pending) {
87
+ return {
88
+ success: false,
89
+ error: 'This person already has a pending invitation. Resend it instead.',
90
+ code: 'PENDING_INVITE',
91
+ invite: serializeInvite(pending),
92
+ };
93
+ }
94
+ // An existing, already-accepted user can't be re-invited.
95
+ const existingUser = await db.user.findFirst({
96
+ where: { email: { equals: email, mode: 'insensitive' } },
97
+ });
98
+ if (existingUser) {
99
+ return {
100
+ success: false,
101
+ error: 'A user with this email already exists.',
102
+ code: 'DUPLICATE_USER',
103
+ };
104
+ }
105
+ const name = email.split('@')[0] || email;
106
+ const passwordHash = await hashPassword(randomBytes(32).toString('hex'));
107
+ const expiresAt = new Date(Date.now() + expiryDays(input.expiresInDays) * 24 * 60 * 60 * 1000);
108
+ // Placeholder user — unusable password, not yet approved/accepted.
109
+ const user = await db.user.create({
110
+ data: {
111
+ email,
112
+ name,
113
+ role,
114
+ passwordHash,
115
+ isActive: true,
116
+ isApproved: false,
117
+ emailVerified: false,
118
+ invitedById: config.invitedByUserId ?? null,
119
+ },
120
+ });
121
+ const { raw, hash } = generateResetToken();
122
+ await db.passwordResetToken.create({
123
+ data: { userId: user.id, tokenHash: hash, expiresAt },
124
+ });
125
+ const invite = await db.invite.create({
126
+ data: {
127
+ email,
128
+ role,
129
+ status: 'PENDING',
130
+ tokenHash: hash,
131
+ message: input.message?.trim() || null,
132
+ invitedByUserId: config.invitedByUserId ?? null,
133
+ expiresAt,
134
+ },
135
+ });
136
+ const inviteUrl = buildAcceptUrl(raw, config);
137
+ const emailed = await sendInviteEmail(email, name, role, inviteUrl, config);
138
+ return {
139
+ success: true,
140
+ emailed,
141
+ inviteUrl: emailed ? undefined : inviteUrl,
142
+ invite: serializeInvite(invite),
143
+ };
144
+ }
145
+ /**
146
+ * List invites, lazily flipping any past-expiry PENDING rows to EXPIRED so the
147
+ * status is always honest without a background job.
148
+ */
149
+ export async function listInvites(db, params = {}) {
150
+ await expireStaleInvites(db);
151
+ const where = {};
152
+ if (params.status && params.status !== 'all')
153
+ where.status = params.status.toUpperCase();
154
+ if (params.search)
155
+ where.email = { contains: params.search.trim(), mode: 'insensitive' };
156
+ const rows = await db.invite.findMany({ where, orderBy: { createdAt: 'desc' } });
157
+ return rows.map(serializeInvite);
158
+ }
159
+ /** Flip PENDING invites whose expiry has passed to EXPIRED. Returns the count. */
160
+ export async function expireStaleInvites(db) {
161
+ const res = await db.invite.updateMany({
162
+ where: { status: 'PENDING', expiresAt: { lt: new Date() } },
163
+ data: { status: 'EXPIRED' },
164
+ });
165
+ return res.count ?? 0;
166
+ }
167
+ /** Resend (or revive an expired) invite: fresh token, reset expiry, re-email. */
168
+ export async function resendInvite(db, inviteId, config) {
169
+ const invite = await db.invite.findUnique({ where: { id: inviteId } });
170
+ if (!invite)
171
+ return { success: false, error: 'Invite not found.', code: 'NOT_FOUND' };
172
+ if (invite.status === 'ACCEPTED') {
173
+ return { success: false, error: 'This invitation was already accepted.' };
174
+ }
175
+ if (invite.status === 'CANCELLED') {
176
+ return { success: false, error: 'This invitation was cancelled.' };
177
+ }
178
+ const user = await db.user.findFirst({
179
+ where: { email: { equals: invite.email, mode: 'insensitive' } },
180
+ });
181
+ const expiresAt = new Date(Date.now() + INVITE_EXPIRY_MS);
182
+ const { raw, hash } = generateResetToken();
183
+ if (user) {
184
+ await db.passwordResetToken.updateMany({
185
+ where: { userId: user.id, usedAt: null },
186
+ data: { usedAt: new Date() },
187
+ });
188
+ await db.passwordResetToken.create({ data: { userId: user.id, tokenHash: hash, expiresAt } });
189
+ }
190
+ const updated = await db.invite.update({
191
+ where: { id: inviteId },
192
+ data: { status: 'PENDING', tokenHash: hash, expiresAt },
193
+ });
194
+ const inviteUrl = buildAcceptUrl(raw, config);
195
+ const emailed = await sendInviteEmail(invite.email, user?.name ?? invite.email, invite.role, inviteUrl, config);
196
+ return {
197
+ success: true,
198
+ emailed,
199
+ inviteUrl: emailed ? undefined : inviteUrl,
200
+ invite: serializeInvite(updated),
201
+ };
202
+ }
203
+ /** Cancel an invite and deactivate its placeholder user so the link is dead. */
204
+ export async function cancelInvite(db, inviteId) {
205
+ const invite = await db.invite.findUnique({ where: { id: inviteId } });
206
+ if (!invite)
207
+ return { success: false, error: 'Invite not found.', code: 'NOT_FOUND' };
208
+ if (invite.status === 'ACCEPTED') {
209
+ return { success: false, error: 'This invitation was already accepted.' };
210
+ }
211
+ const user = await db.user.findFirst({
212
+ where: { email: { equals: invite.email, mode: 'insensitive' }, acceptedInviteAt: null },
213
+ });
214
+ if (user) {
215
+ await db.passwordResetToken.updateMany({
216
+ where: { userId: user.id, usedAt: null },
217
+ data: { usedAt: new Date() },
218
+ });
219
+ await db.user.update({ where: { id: user.id }, data: { isActive: false } });
220
+ }
221
+ const updated = await db.invite.update({
222
+ where: { id: inviteId },
223
+ data: { status: 'CANCELLED', cancelledAt: new Date() },
224
+ });
225
+ return { success: true, invite: serializeInvite(updated) };
226
+ }
227
+ /** Change the role on a pending invite (and its placeholder user). */
228
+ export async function updateInviteRole(db, inviteId, role) {
229
+ const normalized = normalizeAssignableRole(role);
230
+ if (!normalized)
231
+ return { success: false, error: 'Select a valid role.', code: 'INVALID_ROLE' };
232
+ const invite = await db.invite.findUnique({ where: { id: inviteId } });
233
+ if (!invite)
234
+ return { success: false, error: 'Invite not found.', code: 'NOT_FOUND' };
235
+ if (invite.status !== 'PENDING') {
236
+ return { success: false, error: 'Only pending invitations can be changed.' };
237
+ }
238
+ await db.user.updateMany({
239
+ where: { email: { equals: invite.email, mode: 'insensitive' }, acceptedInviteAt: null },
240
+ data: { role: normalized },
241
+ });
242
+ const updated = await db.invite.update({ where: { id: inviteId }, data: { role: normalized } });
243
+ return { success: true, invite: serializeInvite(updated) };
244
+ }
245
+ /** Extend a pending invite's expiry and re-sync its reset token expiry. */
246
+ export async function extendInvite(db, inviteId, days) {
247
+ const invite = await db.invite.findUnique({ where: { id: inviteId } });
248
+ if (!invite)
249
+ return { success: false, error: 'Invite not found.', code: 'NOT_FOUND' };
250
+ if (invite.status !== 'PENDING') {
251
+ return { success: false, error: 'Only pending invitations can be extended.' };
252
+ }
253
+ const expiresAt = new Date(Date.now() + expiryDays(days) * 24 * 60 * 60 * 1000);
254
+ const user = await db.user.findFirst({
255
+ where: { email: { equals: invite.email, mode: 'insensitive' }, acceptedInviteAt: null },
256
+ });
257
+ if (user) {
258
+ await db.passwordResetToken.updateMany({
259
+ where: { userId: user.id, usedAt: null },
260
+ data: { expiresAt },
261
+ });
262
+ }
263
+ const updated = await db.invite.update({ where: { id: inviteId }, data: { expiresAt } });
264
+ return { success: true, invite: serializeInvite(updated) };
265
+ }
266
+ /**
267
+ * Mark the pending invite for an email as ACCEPTED. Called when a user sets
268
+ * their password through the reset/accept flow. Safe to call for non-invited
269
+ * users — it simply updates zero rows.
270
+ */
271
+ export async function markInviteAcceptedForEmail(db, email, userId) {
272
+ const normalized = email.toLowerCase().trim();
273
+ const res = await db.invite.updateMany({
274
+ where: { email: { equals: normalized, mode: 'insensitive' }, status: 'PENDING' },
275
+ data: { status: 'ACCEPTED', acceptedAt: new Date(), acceptedUserId: userId },
276
+ });
277
+ return (res.count ?? 0) > 0;
278
+ }
279
+ //# sourceMappingURL=invites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invites.js","sourceRoot":"","sources":["../../src/auth/invites.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/auth/reset.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb;AAID,4EAA4E;AAC5E,wBAAgB,kBAAkB,IAAI,UAAU,CAI/C;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,GAAG,EACP,KAAK,EAAE,MAAM,EACb,MAAM,EAAE;IACN,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE;YACN,IAAI,EAAE,CAAC,IAAI,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,OAAO,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;SAC5F,CAAA;KACF,CAAA;CACF,GACA,OAAO,CAAC,IAAI,CAAC,CA4Cf;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,GAAG,EACP,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAuD/C"}
1
+ {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/auth/reset.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb;AAID,4EAA4E;AAC5E,wBAAgB,kBAAkB,IAAI,UAAU,CAI/C;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,GAAG,EACP,KAAK,EAAE,MAAM,EACb,MAAM,EAAE;IACN,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE;YACN,IAAI,EAAE,CAAC,IAAI,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,OAAO,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;SAC5F,CAAA;KACF,CAAA;CACF,GACA,OAAO,CAAC,IAAI,CAAC,CA4Cf;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,GAAG,EACP,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAiF/C"}
@@ -97,11 +97,33 @@ export async function executePasswordReset(db, rawToken, newPassword) {
97
97
  return { success: false, error: policy.errors[0] ?? 'Password does not meet requirements.' };
98
98
  }
99
99
  const passwordHash = await hashPassword(newPassword);
100
+ // If this account has a pending invite, completing the reset *is* the
101
+ // acceptance: mark the invite ACCEPTED and stamp the user as accepted +
102
+ // approved. Done in the same transaction so password + acceptance commit
103
+ // atomically. A normal (non-invite) reset matches no invite and is unaffected.
104
+ let pendingInvite = null;
105
+ try {
106
+ pendingInvite =
107
+ (await db.invite?.findFirst?.({
108
+ where: { email: { equals: resetToken.user.email, mode: 'insensitive' }, status: 'PENDING' },
109
+ })) ?? null;
110
+ }
111
+ catch {
112
+ pendingInvite = null;
113
+ }
114
+ const userData = { passwordHash };
115
+ const ops = [];
116
+ if (pendingInvite) {
117
+ userData.acceptedInviteAt = new Date();
118
+ userData.isApproved = true;
119
+ userData.emailVerified = true;
120
+ ops.push(db.invite.update({
121
+ where: { id: pendingInvite.id },
122
+ data: { status: 'ACCEPTED', acceptedAt: new Date(), acceptedUserId: resetToken.userId },
123
+ }));
124
+ }
100
125
  await db.$transaction([
101
- db.user.update({
102
- where: { id: resetToken.userId },
103
- data: { passwordHash },
104
- }),
126
+ db.user.update({ where: { id: resetToken.userId }, data: userData }),
105
127
  db.passwordResetToken.update({
106
128
  where: { id: resetToken.id },
107
129
  data: { usedAt: new Date() },
@@ -110,6 +132,7 @@ export async function executePasswordReset(db, rawToken, newPassword) {
110
132
  where: { userId: resetToken.userId, revokedAt: null },
111
133
  data: { revokedAt: new Date() },
112
134
  }),
135
+ ...ops,
113
136
  ]);
114
137
  return { success: true };
115
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/auth/reset.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/auth/reset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Role model for the Users / access-control subsystem.
3
+ *
4
+ * The database enum (`UserRole`) carries six values for backward compatibility:
5
+ * the four product-facing roles `OWNER`, `ADMIN`, `EDITOR`, `VIEWER` plus the
6
+ * two legacy values `AUTHOR` and `CLIENT` that predate this subsystem. The admin
7
+ * UI only ever speaks in the four product roles, so this module is the single
8
+ * source of truth for:
9
+ *
10
+ * - mapping any stored role to a product-facing display label,
11
+ * - mapping a display-label filter back to the enum values it covers,
12
+ * - which roles may be *assigned* to new/updated users,
13
+ * - a privilege rank used by protection checks (last-owner, self-demotion),
14
+ * - the permission groups each role grants.
15
+ *
16
+ * Everything here is pure and dependency-free so it can be unit-tested in
17
+ * isolation and imported from the lightweight `@actuate-media/cms-core/roles`
18
+ * subpath without pulling the full barrel.
19
+ */
20
+ export type UserRoleEnum = 'OWNER' | 'ADMIN' | 'EDITOR' | 'AUTHOR' | 'CLIENT' | 'VIEWER';
21
+ export type DisplayRole = 'Owner' | 'Admin' | 'Editor' | 'Viewer';
22
+ /** Roles offered in the invite modal and role dropdowns, in privilege order. */
23
+ export declare const ASSIGNABLE_ROLES: readonly UserRoleEnum[];
24
+ /** All product-facing display roles, used for the filter pills. */
25
+ export declare const DISPLAY_ROLES: readonly DisplayRole[];
26
+ /**
27
+ * Privilege rank — higher is more privileged. Used to enforce "you cannot act on
28
+ * someone more privileged than you" and last-owner / self-demotion protections.
29
+ * Legacy roles rank with their display equivalent.
30
+ */
31
+ export declare const ROLE_RANK: Record<UserRoleEnum, number>;
32
+ /** Map any stored role (incl. legacy) to its product-facing display label. */
33
+ export declare function toDisplayRole(role: string | null | undefined): DisplayRole;
34
+ /** Validate + normalize a role coming from the UI for assignment. */
35
+ export declare function normalizeAssignableRole(role: string | null | undefined): UserRoleEnum | null;
36
+ /**
37
+ * The enum values a display-role filter covers. Returns `null` for "all" or an
38
+ * unrecognized label so callers can skip filtering.
39
+ */
40
+ export declare function enumRolesForDisplay(display: string | null | undefined): UserRoleEnum[] | null;
41
+ export declare function isOwnerRole(role: string | null | undefined): boolean;
42
+ /** Rank of a role (0 for unknown). */
43
+ export declare function roleRank(role: string | null | undefined): number;
44
+ /** Every permission scope recognised by the system. */
45
+ export declare const ALL_PERMISSIONS: readonly ["dashboard.view", "content.view", "content.create", "content.update", "content.publish", "content.delete", "pages.manage", "posts.manage", "media.manage", "forms.manage", "seo.manage", "redirects.manage", "scripts.manage", "users.view", "users.invite", "users.update", "users.update_role", "users.remove", "users.suspend", "users.reset_password", "users.reset_mfa", "sessions.view", "sessions.revoke", "api_keys.manage", "settings.manage", "security.manage", "audit.view", "billing.manage", "workspace.delete", "workspace.owner_actions"];
46
+ export type Permission = (typeof ALL_PERMISSIONS)[number];
47
+ /** Effective permissions granted by a role. */
48
+ export declare function permissionsForRole(role: string | null | undefined): Permission[];
49
+ /** Whether a role grants a specific permission. */
50
+ export declare function roleHasPermission(role: string | null | undefined, permission: Permission): boolean;
51
+ //# sourceMappingURL=roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../src/auth/roles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACxF,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEjE,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,EAAE,SAAS,YAAY,EAA2C,CAAA;AAE/F,mEAAmE;AACnE,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAA2C,CAAA;AAkB3F;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAOlD,CAAA;AAQD,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,CAG1E;AAED,qEAAqE;AACrE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAG5F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,EAAE,GAAG,IAAI,CAM7F;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEpE;AAED,sCAAsC;AACtC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGhE;AAMD,uDAAuD;AACvD,eAAO,MAAM,eAAe,qiBA+BlB,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAqBzD,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,UAAU,EAAE,CAehF;AAED,mDAAmD;AACnD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,UAAU,EAAE,UAAU,GACrB,OAAO,CAET"}