@actuate-media/cms-core 0.38.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 (91) hide show
  1. package/dist/__tests__/api/ai-status.test.d.ts +2 -0
  2. package/dist/__tests__/api/ai-status.test.d.ts.map +1 -0
  3. package/dist/__tests__/api/ai-status.test.js +110 -0
  4. package/dist/__tests__/api/ai-status.test.js.map +1 -0
  5. package/dist/__tests__/api/users-detail.test.d.ts +2 -0
  6. package/dist/__tests__/api/users-detail.test.d.ts.map +1 -0
  7. package/dist/__tests__/api/users-detail.test.js +349 -0
  8. package/dist/__tests__/api/users-detail.test.js.map +1 -0
  9. package/dist/__tests__/api/users-invite.test.d.ts +2 -0
  10. package/dist/__tests__/api/users-invite.test.d.ts.map +1 -0
  11. package/dist/__tests__/api/users-invite.test.js +221 -0
  12. package/dist/__tests__/api/users-invite.test.js.map +1 -0
  13. package/dist/__tests__/auth/invites.test.d.ts +2 -0
  14. package/dist/__tests__/auth/invites.test.d.ts.map +1 -0
  15. package/dist/__tests__/auth/invites.test.js +181 -0
  16. package/dist/__tests__/auth/invites.test.js.map +1 -0
  17. package/dist/__tests__/auth/reset.test.js +52 -1
  18. package/dist/__tests__/auth/reset.test.js.map +1 -1
  19. package/dist/__tests__/auth/roles.test.d.ts +2 -0
  20. package/dist/__tests__/auth/roles.test.d.ts.map +1 -0
  21. package/dist/__tests__/auth/roles.test.js +60 -0
  22. package/dist/__tests__/auth/roles.test.js.map +1 -0
  23. package/dist/__tests__/auth/team.test.d.ts +2 -0
  24. package/dist/__tests__/auth/team.test.d.ts.map +1 -0
  25. package/dist/__tests__/auth/team.test.js +139 -0
  26. package/dist/__tests__/auth/team.test.js.map +1 -0
  27. package/dist/__tests__/auth/user-admin.test.d.ts +2 -0
  28. package/dist/__tests__/auth/user-admin.test.d.ts.map +1 -0
  29. package/dist/__tests__/auth/user-admin.test.js +162 -0
  30. package/dist/__tests__/auth/user-admin.test.js.map +1 -0
  31. package/dist/__tests__/email/sender.test.d.ts +2 -0
  32. package/dist/__tests__/email/sender.test.d.ts.map +1 -0
  33. package/dist/__tests__/email/sender.test.js +33 -0
  34. package/dist/__tests__/email/sender.test.js.map +1 -0
  35. package/dist/__tests__/middleware.test.js +30 -0
  36. package/dist/__tests__/middleware.test.js.map +1 -1
  37. package/dist/__tests__/setup/index.test.js +37 -2
  38. package/dist/__tests__/setup/index.test.js.map +1 -1
  39. package/dist/api/handlers.d.ts.map +1 -1
  40. package/dist/api/handlers.js +1116 -194
  41. package/dist/api/handlers.js.map +1 -1
  42. package/dist/auth/index.d.ts +4 -0
  43. package/dist/auth/index.d.ts.map +1 -1
  44. package/dist/auth/index.js +2 -0
  45. package/dist/auth/index.js.map +1 -1
  46. package/dist/auth/invite-email.d.ts +12 -0
  47. package/dist/auth/invite-email.d.ts.map +1 -0
  48. package/dist/auth/invite-email.js +57 -0
  49. package/dist/auth/invite-email.js.map +1 -0
  50. package/dist/auth/invites.d.ts +71 -0
  51. package/dist/auth/invites.d.ts.map +1 -0
  52. package/dist/auth/invites.js +279 -0
  53. package/dist/auth/invites.js.map +1 -0
  54. package/dist/auth/reset.d.ts +6 -0
  55. package/dist/auth/reset.d.ts.map +1 -1
  56. package/dist/auth/reset.js +38 -7
  57. package/dist/auth/reset.js.map +1 -1
  58. package/dist/auth/roles.d.ts +51 -0
  59. package/dist/auth/roles.d.ts.map +1 -0
  60. package/dist/auth/roles.js +159 -0
  61. package/dist/auth/roles.js.map +1 -0
  62. package/dist/auth/team.d.ts +86 -0
  63. package/dist/auth/team.d.ts.map +1 -0
  64. package/dist/auth/team.js +270 -0
  65. package/dist/auth/team.js.map +1 -0
  66. package/dist/auth/user-admin.d.ts +91 -0
  67. package/dist/auth/user-admin.d.ts.map +1 -0
  68. package/dist/auth/user-admin.js +250 -0
  69. package/dist/auth/user-admin.js.map +1 -0
  70. package/dist/email/sender.d.ts +35 -0
  71. package/dist/email/sender.d.ts.map +1 -0
  72. package/dist/email/sender.js +25 -0
  73. package/dist/email/sender.js.map +1 -0
  74. package/dist/forms/notify.d.ts.map +1 -1
  75. package/dist/forms/notify.js +3 -2
  76. package/dist/forms/notify.js.map +1 -1
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +4 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/middleware.d.ts.map +1 -1
  82. package/dist/middleware.js +60 -1
  83. package/dist/middleware.js.map +1 -1
  84. package/dist/setup/index.d.ts +14 -0
  85. package/dist/setup/index.d.ts.map +1 -1
  86. package/dist/setup/index.js +29 -3
  87. package/dist/setup/index.js.map +1 -1
  88. package/package.json +11 -1
  89. package/prisma/migrations/0011_user_roles_owner_viewer/migration.sql +6 -0
  90. package/prisma/migrations/0012_invites_and_user_lifecycle/migration.sql +40 -0
  91. package/prisma/schema.prisma +43 -0
@@ -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"}
@@ -0,0 +1,159 @@
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
+ /** Roles offered in the invite modal and role dropdowns, in privilege order. */
21
+ export const ASSIGNABLE_ROLES = ['OWNER', 'ADMIN', 'EDITOR', 'VIEWER'];
22
+ /** All product-facing display roles, used for the filter pills. */
23
+ export const DISPLAY_ROLES = ['Owner', 'Admin', 'Editor', 'Viewer'];
24
+ const ENUM_TO_DISPLAY = {
25
+ OWNER: 'Owner',
26
+ ADMIN: 'Admin',
27
+ EDITOR: 'Editor',
28
+ AUTHOR: 'Editor',
29
+ CLIENT: 'Viewer',
30
+ VIEWER: 'Viewer',
31
+ };
32
+ const DISPLAY_TO_ENUMS = {
33
+ Owner: ['OWNER'],
34
+ Admin: ['ADMIN'],
35
+ Editor: ['EDITOR', 'AUTHOR'],
36
+ Viewer: ['VIEWER', 'CLIENT'],
37
+ };
38
+ /**
39
+ * Privilege rank — higher is more privileged. Used to enforce "you cannot act on
40
+ * someone more privileged than you" and last-owner / self-demotion protections.
41
+ * Legacy roles rank with their display equivalent.
42
+ */
43
+ export const ROLE_RANK = {
44
+ OWNER: 100,
45
+ ADMIN: 80,
46
+ EDITOR: 40,
47
+ AUTHOR: 40,
48
+ VIEWER: 10,
49
+ CLIENT: 10,
50
+ };
51
+ function normalizeEnum(role) {
52
+ if (!role)
53
+ return null;
54
+ const upper = role.trim().toUpperCase();
55
+ return upper in ENUM_TO_DISPLAY ? upper : null;
56
+ }
57
+ /** Map any stored role (incl. legacy) to its product-facing display label. */
58
+ export function toDisplayRole(role) {
59
+ const e = normalizeEnum(role);
60
+ return e ? ENUM_TO_DISPLAY[e] : 'Viewer';
61
+ }
62
+ /** Validate + normalize a role coming from the UI for assignment. */
63
+ export function normalizeAssignableRole(role) {
64
+ const e = normalizeEnum(role);
65
+ return e && ASSIGNABLE_ROLES.includes(e) ? e : null;
66
+ }
67
+ /**
68
+ * The enum values a display-role filter covers. Returns `null` for "all" or an
69
+ * unrecognized label so callers can skip filtering.
70
+ */
71
+ export function enumRolesForDisplay(display) {
72
+ if (!display || display.toLowerCase() === 'all')
73
+ return null;
74
+ const key = DISPLAY_ROLES.find((r) => r.toLowerCase() === display.trim().toLowerCase());
75
+ return key ? DISPLAY_TO_ENUMS[key] : null;
76
+ }
77
+ export function isOwnerRole(role) {
78
+ return normalizeEnum(role) === 'OWNER';
79
+ }
80
+ /** Rank of a role (0 for unknown). */
81
+ export function roleRank(role) {
82
+ const e = normalizeEnum(role);
83
+ return e ? ROLE_RANK[e] : 0;
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Permission groups
87
+ // ---------------------------------------------------------------------------
88
+ /** Every permission scope recognised by the system. */
89
+ export const ALL_PERMISSIONS = [
90
+ 'dashboard.view',
91
+ 'content.view',
92
+ 'content.create',
93
+ 'content.update',
94
+ 'content.publish',
95
+ 'content.delete',
96
+ 'pages.manage',
97
+ 'posts.manage',
98
+ 'media.manage',
99
+ 'forms.manage',
100
+ 'seo.manage',
101
+ 'redirects.manage',
102
+ 'scripts.manage',
103
+ 'users.view',
104
+ 'users.invite',
105
+ 'users.update',
106
+ 'users.update_role',
107
+ 'users.remove',
108
+ 'users.suspend',
109
+ 'users.reset_password',
110
+ 'users.reset_mfa',
111
+ 'sessions.view',
112
+ 'sessions.revoke',
113
+ 'api_keys.manage',
114
+ 'settings.manage',
115
+ 'security.manage',
116
+ 'audit.view',
117
+ 'billing.manage',
118
+ 'workspace.delete',
119
+ 'workspace.owner_actions',
120
+ ];
121
+ const CONTENT_PERMISSIONS = [
122
+ 'dashboard.view',
123
+ 'content.view',
124
+ 'content.create',
125
+ 'content.update',
126
+ 'content.publish',
127
+ 'content.delete',
128
+ 'pages.manage',
129
+ 'posts.manage',
130
+ 'media.manage',
131
+ 'forms.manage',
132
+ 'seo.manage',
133
+ 'redirects.manage',
134
+ ];
135
+ // Admin gets everything except the Owner-only billing/workspace/owner actions.
136
+ const OWNER_ONLY = ['billing.manage', 'workspace.delete', 'workspace.owner_actions'];
137
+ const ADMIN_PERMISSIONS = ALL_PERMISSIONS.filter((p) => !OWNER_ONLY.includes(p));
138
+ /** Effective permissions granted by a role. */
139
+ export function permissionsForRole(role) {
140
+ switch (normalizeEnum(role)) {
141
+ case 'OWNER':
142
+ return [...ALL_PERMISSIONS];
143
+ case 'ADMIN':
144
+ return ADMIN_PERMISSIONS;
145
+ case 'EDITOR':
146
+ case 'AUTHOR':
147
+ return [...CONTENT_PERMISSIONS];
148
+ case 'VIEWER':
149
+ case 'CLIENT':
150
+ return ['dashboard.view', 'content.view'];
151
+ default:
152
+ return [];
153
+ }
154
+ }
155
+ /** Whether a role grants a specific permission. */
156
+ export function roleHasPermission(role, permission) {
157
+ return permissionsForRole(role).includes(permission);
158
+ }
159
+ //# sourceMappingURL=roles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.js","sourceRoot":"","sources":["../../src/auth/roles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Team / access-control read models for the Users admin tab.
3
+ *
4
+ * Pure data shaping over the Prisma client (no config or request coupling) so it
5
+ * unit-tests cleanly. The API layer derives `workspaceDomain` / `seatLimit` from
6
+ * config and passes them in.
7
+ *
8
+ * - `listTeamMembers` — active/suspended/locked members, de-duplicated against
9
+ * pending invites (a placeholder user for a pending
10
+ * invite is shown only as the invite row).
11
+ * - `getUserStats` — the four summary cards (members, pending, admins, seats).
12
+ * - `computeAccessReview` — risk recommendations from real signals.
13
+ */
14
+ import { type DisplayRole, type UserRoleEnum } from './roles.js';
15
+ export type MemberStatus = 'active' | 'suspended' | 'locked' | 'deactivated';
16
+ export interface TeamMember {
17
+ id: string;
18
+ name: string;
19
+ email: string;
20
+ initials: string;
21
+ role: UserRoleEnum;
22
+ roleName: DisplayRole;
23
+ status: MemberStatus;
24
+ mfaEnabled: boolean;
25
+ mfaRequired: boolean;
26
+ externalDomain: boolean;
27
+ authProvider: string | null;
28
+ lastLoginAt: string | null;
29
+ lastActiveAt: string | null;
30
+ acceptedInviteAt: string | null;
31
+ createdAt: string;
32
+ isCurrentUser: boolean;
33
+ }
34
+ export interface TeamOptions {
35
+ /** Registrable workspace domain, e.g. "actuatemedia.com". */
36
+ workspaceDomain?: string | null;
37
+ /** Seat cap; null/undefined = unlimited. */
38
+ seatLimit?: number | null;
39
+ /** Used to mark the current row + protect self-actions. */
40
+ currentUserId?: string;
41
+ /** Access-review recommendation ids the workspace has dismissed. */
42
+ dismissedIds?: string[];
43
+ }
44
+ export interface MemberFilter {
45
+ search?: string;
46
+ /** Display-role filter ("Owner"/"Admin"/"Editor"/"Viewer"/"all"). */
47
+ role?: string;
48
+ status?: string;
49
+ }
50
+ export declare function listTeamMembers(db: any, filter?: MemberFilter, opts?: TeamOptions): Promise<TeamMember[]>;
51
+ export interface UserStats {
52
+ members: number;
53
+ pendingInvites: number;
54
+ admins: number;
55
+ seatsUsed: number;
56
+ seatLimit: number | null;
57
+ seatsAvailable: number | null;
58
+ }
59
+ export declare function getUserStats(db: any, opts?: TeamOptions): Promise<UserStats>;
60
+ export type Severity = 'low' | 'medium' | 'high' | 'critical';
61
+ export type RecommendationType = 'inactive_user' | 'external_domain' | 'admin_without_mfa' | 'owner_without_mfa' | 'stale_invite' | 'never_signed_in';
62
+ export interface AccessReviewRecommendation {
63
+ id: string;
64
+ userId: string | null;
65
+ inviteId?: string | null;
66
+ severity: Severity;
67
+ type: RecommendationType;
68
+ title: string;
69
+ description: string;
70
+ recommendedAction: string;
71
+ status: 'open';
72
+ }
73
+ /**
74
+ * Build one primary recommendation per member (highest-risk signal), plus a
75
+ * recommendation per stale pending invite. Dismissed ids are filtered out and
76
+ * the result is sorted highest-severity first.
77
+ */
78
+ export declare function buildRecommendations(members: TeamMember[], pendingInvites: Array<{
79
+ id: string;
80
+ email: string;
81
+ createdAt: string;
82
+ }>, dismissed: Set<string>): AccessReviewRecommendation[];
83
+ export declare function computeAccessReview(db: any, opts?: TeamOptions): Promise<AccessReviewRecommendation[]>;
84
+ /** Derive the registrable workspace domain from a site URL, if any. */
85
+ export declare function workspaceDomainFromUrl(siteUrl?: string | null): string | null;
86
+ //# sourceMappingURL=team.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.d.ts","sourceRoot":"","sources":["../../src/auth/team.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Team / access-control read models for the Users admin tab.
3
+ *
4
+ * Pure data shaping over the Prisma client (no config or request coupling) so it
5
+ * unit-tests cleanly. The API layer derives `workspaceDomain` / `seatLimit` from
6
+ * config and passes them in.
7
+ *
8
+ * - `listTeamMembers` — active/suspended/locked members, de-duplicated against
9
+ * pending invites (a placeholder user for a pending
10
+ * invite is shown only as the invite row).
11
+ * - `getUserStats` — the four summary cards (members, pending, admins, seats).
12
+ * - `computeAccessReview` — risk recommendations from real signals.
13
+ */
14
+ import { toDisplayRole } from './roles.js';
15
+ const INACTIVE_DAYS = 90;
16
+ const STALE_INVITE_DAYS = 7;
17
+ const DAY_MS = 24 * 60 * 60 * 1000;
18
+ function initialsFor(name, email) {
19
+ const source = name?.trim() || email;
20
+ const parts = source.split(/\s+/).filter(Boolean);
21
+ if (parts.length >= 2)
22
+ return (parts[0][0] + parts[1][0]).toUpperCase();
23
+ const single = parts[0] ?? email;
24
+ return single.slice(0, 2).toUpperCase();
25
+ }
26
+ function emailDomain(email) {
27
+ return email.split('@')[1]?.toLowerCase() ?? '';
28
+ }
29
+ function iso(d) {
30
+ if (!d)
31
+ return null;
32
+ return d instanceof Date ? d.toISOString() : d;
33
+ }
34
+ function memberStatus(user) {
35
+ if (!user.isActive)
36
+ return 'deactivated';
37
+ if (user.lockedAt)
38
+ return 'locked';
39
+ if (user.suspendedAt)
40
+ return 'suspended';
41
+ return 'active';
42
+ }
43
+ function toMember(user, opts) {
44
+ const domain = emailDomain(user.email);
45
+ return {
46
+ id: user.id,
47
+ name: user.name,
48
+ email: user.email,
49
+ initials: initialsFor(user.name, user.email),
50
+ role: user.role,
51
+ roleName: toDisplayRole(user.role),
52
+ status: memberStatus(user),
53
+ mfaEnabled: !!user.totpEnabled,
54
+ mfaRequired: !!user.mfaRequired,
55
+ externalDomain: !!opts.workspaceDomain && !!domain && domain !== opts.workspaceDomain,
56
+ authProvider: user.authProvider ?? null,
57
+ lastLoginAt: iso(user.lastLoginAt),
58
+ lastActiveAt: iso(user.lastActiveAt ?? user.lastLoginAt),
59
+ acceptedInviteAt: iso(user.acceptedInviteAt),
60
+ createdAt: iso(user.createdAt) ?? new Date().toISOString(),
61
+ isCurrentUser: !!opts.currentUserId && user.id === opts.currentUserId,
62
+ };
63
+ }
64
+ /** Emails with a still-PENDING invite — excluded from the member list. */
65
+ async function pendingInviteEmails(db) {
66
+ if (!db.invite?.findMany)
67
+ return new Set();
68
+ const rows = await db.invite.findMany({
69
+ where: { status: 'PENDING' },
70
+ select: { email: true },
71
+ });
72
+ return new Set(rows.map((r) => r.email.toLowerCase()));
73
+ }
74
+ export async function listTeamMembers(db, filter = {}, opts = {}) {
75
+ const [users, pending] = await Promise.all([
76
+ db.user.findMany({ orderBy: { createdAt: 'asc' } }),
77
+ pendingInviteEmails(db),
78
+ ]);
79
+ let members = users
80
+ .filter((u) => !pending.has(u.email.toLowerCase()))
81
+ .map((u) => toMember(u, opts));
82
+ if (filter.role && filter.role.toLowerCase() !== 'all') {
83
+ const want = filter.role.toLowerCase();
84
+ members = members.filter((m) => m.roleName.toLowerCase() === want);
85
+ }
86
+ if (filter.status && filter.status.toLowerCase() !== 'all') {
87
+ const want = filter.status.toLowerCase();
88
+ members = members.filter((m) => m.status === want);
89
+ }
90
+ if (filter.search) {
91
+ const q = filter.search.trim().toLowerCase();
92
+ members = members.filter((m) => m.name.toLowerCase().includes(q) ||
93
+ m.email.toLowerCase().includes(q) ||
94
+ m.roleName.toLowerCase().includes(q) ||
95
+ m.status.includes(q) ||
96
+ emailDomain(m.email).includes(q));
97
+ }
98
+ return members;
99
+ }
100
+ export async function getUserStats(db, opts = {}) {
101
+ const members = await listTeamMembers(db, {}, opts);
102
+ const active = members.filter((m) => m.status === 'active');
103
+ const admins = active.filter((m) => m.roleName === 'Owner' || m.roleName === 'Admin').length;
104
+ let pendingInvites = 0;
105
+ if (db.invite?.count) {
106
+ pendingInvites = await db.invite.count({
107
+ where: { status: 'PENDING', expiresAt: { gt: new Date() } },
108
+ });
109
+ }
110
+ // Active members + pending invites reserve seats. Suspended/locked/deactivated
111
+ // do not count.
112
+ const seatsUsed = active.length + pendingInvites;
113
+ const seatLimit = opts.seatLimit ?? null;
114
+ const seatsAvailable = seatLimit == null ? null : Math.max(0, seatLimit - seatsUsed);
115
+ return {
116
+ members: active.length,
117
+ pendingInvites,
118
+ admins,
119
+ seatsUsed,
120
+ seatLimit,
121
+ seatsAvailable,
122
+ };
123
+ }
124
+ const SEVERITY_RANK = { critical: 4, high: 3, medium: 2, low: 1 };
125
+ function daysSince(d) {
126
+ if (!d)
127
+ return null;
128
+ return Math.floor((Date.now() - new Date(d).getTime()) / DAY_MS);
129
+ }
130
+ /**
131
+ * Build one primary recommendation per member (highest-risk signal), plus a
132
+ * recommendation per stale pending invite. Dismissed ids are filtered out and
133
+ * the result is sorted highest-severity first.
134
+ */
135
+ export function buildRecommendations(members, pendingInvites, dismissed) {
136
+ const recs = [];
137
+ for (const m of members) {
138
+ if (m.status !== 'active')
139
+ continue;
140
+ const inactiveDays = daysSince(m.lastLoginAt);
141
+ const inactive = inactiveDays != null && inactiveDays >= INACTIVE_DAYS;
142
+ const neverSignedIn = m.lastLoginAt == null && m.acceptedInviteAt != null;
143
+ const isOwner = m.roleName === 'Owner';
144
+ const isAdmin = m.roleName === 'Admin';
145
+ let rec = null;
146
+ if (isOwner && !m.mfaEnabled) {
147
+ rec = {
148
+ id: `owner_without_mfa:${m.id}`,
149
+ userId: m.id,
150
+ severity: 'critical',
151
+ type: 'owner_without_mfa',
152
+ title: 'Owner without MFA',
153
+ description: `${m.name} (${m.email}) is an Owner but has not enabled multi-factor authentication.`,
154
+ recommendedAction: 'Require MFA',
155
+ status: 'open',
156
+ };
157
+ }
158
+ else if (isAdmin && !m.mfaEnabled) {
159
+ rec = {
160
+ id: `admin_without_mfa:${m.id}`,
161
+ userId: m.id,
162
+ severity: 'high',
163
+ type: 'admin_without_mfa',
164
+ title: 'Admin without MFA',
165
+ description: `${m.name} (${m.email}) has admin access but has not enabled multi-factor authentication.`,
166
+ recommendedAction: 'Require MFA',
167
+ status: 'open',
168
+ };
169
+ }
170
+ else if (inactive && m.externalDomain) {
171
+ rec = {
172
+ id: `inactive_user:${m.id}`,
173
+ userId: m.id,
174
+ severity: 'high',
175
+ type: 'inactive_user',
176
+ title: 'Access review',
177
+ description: `${m.name} (${m.email}) hasn't signed in for ${inactiveDays} days and holds an external address. Consider revoking access to reduce risk.`,
178
+ recommendedAction: 'Revoke access',
179
+ status: 'open',
180
+ };
181
+ }
182
+ else if (inactive) {
183
+ rec = {
184
+ id: `inactive_user:${m.id}`,
185
+ userId: m.id,
186
+ severity: 'medium',
187
+ type: 'inactive_user',
188
+ title: 'Inactive user',
189
+ description: `${m.name} (${m.email}) hasn't signed in for ${inactiveDays} days.`,
190
+ recommendedAction: 'Revoke access',
191
+ status: 'open',
192
+ };
193
+ }
194
+ else if (m.externalDomain) {
195
+ rec = {
196
+ id: `external_domain:${m.id}`,
197
+ userId: m.id,
198
+ severity: m.roleName === 'Viewer' ? 'low' : 'medium',
199
+ type: 'external_domain',
200
+ title: 'External domain',
201
+ description: `${m.name} (${m.email}) signs in with an external email domain.`,
202
+ recommendedAction: 'Review access',
203
+ status: 'open',
204
+ };
205
+ }
206
+ else if (neverSignedIn) {
207
+ rec = {
208
+ id: `never_signed_in:${m.id}`,
209
+ userId: m.id,
210
+ severity: 'medium',
211
+ type: 'never_signed_in',
212
+ title: 'Never signed in',
213
+ description: `${m.name} (${m.email}) has an account but has never signed in.`,
214
+ recommendedAction: 'Send reminder',
215
+ status: 'open',
216
+ };
217
+ }
218
+ if (rec && !dismissed.has(rec.id))
219
+ recs.push(rec);
220
+ }
221
+ for (const inv of pendingInvites) {
222
+ const age = daysSince(inv.createdAt);
223
+ if (age != null && age >= STALE_INVITE_DAYS) {
224
+ const id = `stale_invite:${inv.id}`;
225
+ if (!dismissed.has(id)) {
226
+ recs.push({
227
+ id,
228
+ userId: null,
229
+ inviteId: inv.id,
230
+ severity: 'medium',
231
+ type: 'stale_invite',
232
+ title: 'Stale invitation',
233
+ description: `The invitation to ${inv.email} has been pending for ${age} days.`,
234
+ recommendedAction: 'Resend or cancel',
235
+ status: 'open',
236
+ });
237
+ }
238
+ }
239
+ }
240
+ return recs.sort((a, b) => SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity]);
241
+ }
242
+ export async function computeAccessReview(db, opts = {}) {
243
+ const members = await listTeamMembers(db, {}, opts);
244
+ let pending = [];
245
+ if (db.invite?.findMany) {
246
+ const rows = await db.invite.findMany({
247
+ where: { status: 'PENDING' },
248
+ select: { id: true, email: true, createdAt: true },
249
+ });
250
+ pending = rows.map((r) => ({
251
+ id: r.id,
252
+ email: r.email,
253
+ createdAt: iso(r.createdAt) ?? new Date().toISOString(),
254
+ }));
255
+ }
256
+ return buildRecommendations(members, pending, new Set(opts.dismissedIds ?? []));
257
+ }
258
+ /** Derive the registrable workspace domain from a site URL, if any. */
259
+ export function workspaceDomainFromUrl(siteUrl) {
260
+ if (!siteUrl)
261
+ return null;
262
+ try {
263
+ const host = new URL(siteUrl).hostname.toLowerCase();
264
+ return host.replace(/^www\./, '');
265
+ }
266
+ catch {
267
+ return null;
268
+ }
269
+ }
270
+ //# sourceMappingURL=team.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.js","sourceRoot":"","sources":["../../src/auth/team.ts"],"names":[],"mappings":"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"}