@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.
- package/dist/__tests__/api/users-detail.test.d.ts +2 -0
- package/dist/__tests__/api/users-detail.test.d.ts.map +1 -0
- package/dist/__tests__/api/users-detail.test.js +401 -0
- package/dist/__tests__/api/users-detail.test.js.map +1 -0
- package/dist/__tests__/api/users-invite.test.js +80 -26
- package/dist/__tests__/api/users-invite.test.js.map +1 -1
- package/dist/__tests__/auth/invites.test.d.ts +2 -0
- package/dist/__tests__/auth/invites.test.d.ts.map +1 -0
- package/dist/__tests__/auth/invites.test.js +181 -0
- package/dist/__tests__/auth/invites.test.js.map +1 -0
- package/dist/__tests__/auth/roles.test.d.ts +2 -0
- package/dist/__tests__/auth/roles.test.d.ts.map +1 -0
- package/dist/__tests__/auth/roles.test.js +60 -0
- package/dist/__tests__/auth/roles.test.js.map +1 -0
- package/dist/__tests__/auth/team.test.d.ts +2 -0
- package/dist/__tests__/auth/team.test.d.ts.map +1 -0
- package/dist/__tests__/auth/team.test.js +139 -0
- package/dist/__tests__/auth/team.test.js.map +1 -0
- package/dist/__tests__/auth/user-admin.test.d.ts +2 -0
- package/dist/__tests__/auth/user-admin.test.d.ts.map +1 -0
- package/dist/__tests__/auth/user-admin.test.js +198 -0
- package/dist/__tests__/auth/user-admin.test.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +883 -16
- package/dist/api/handlers.js.map +1 -1
- package/dist/auth/index.d.ts +4 -2
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/invites.d.ts +71 -0
- package/dist/auth/invites.d.ts.map +1 -0
- package/dist/auth/invites.js +279 -0
- package/dist/auth/invites.js.map +1 -0
- package/dist/auth/reset.d.ts.map +1 -1
- package/dist/auth/reset.js +27 -4
- package/dist/auth/reset.js.map +1 -1
- package/dist/auth/roles.d.ts +51 -0
- package/dist/auth/roles.d.ts.map +1 -0
- package/dist/auth/roles.js +159 -0
- package/dist/auth/roles.js.map +1 -0
- package/dist/auth/team.d.ts +86 -0
- package/dist/auth/team.d.ts.map +1 -0
- package/dist/auth/team.js +270 -0
- package/dist/auth/team.js.map +1 -0
- package/dist/auth/user-admin.d.ts +118 -0
- package/dist/auth/user-admin.d.ts.map +1 -0
- package/dist/auth/user-admin.js +292 -0
- package/dist/auth/user-admin.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
- package/prisma/migrations/0011_user_roles_owner_viewer/migration.sql +6 -0
- package/prisma/migrations/0012_invites_and_user_lifecycle/migration.sql +40 -0
- package/prisma/schema.prisma +43 -0
- package/dist/__tests__/auth/invite.test.d.ts +0 -2
- package/dist/__tests__/auth/invite.test.d.ts.map +0 -1
- package/dist/__tests__/auth/invite.test.js +0 -151
- package/dist/__tests__/auth/invite.test.js.map +0 -1
- package/dist/auth/invite.d.ts +0 -37
- package/dist/auth/invite.d.ts.map +0 -1
- package/dist/auth/invite.js +0 -104
- package/dist/auth/invite.js.map +0 -1
|
@@ -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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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-user admin read models for the User detail drawer (Overview / Security /
|
|
3
|
+
* Sessions / Activity / Permissions tabs).
|
|
4
|
+
*
|
|
5
|
+
* Pure data shaping over the Prisma client — no config or request coupling — so
|
|
6
|
+
* it unit-tests cleanly. The API layer supplies `workspaceDomain` (for the
|
|
7
|
+
* external-domain flag) and the viewer's `currentSessionId` (to mark the
|
|
8
|
+
* "this device" session). Mirrors the conventions in `team.ts`.
|
|
9
|
+
*/
|
|
10
|
+
import { type DisplayRole, type UserRoleEnum, type Permission } from './roles.js';
|
|
11
|
+
export type MemberStatus = 'active' | 'suspended' | 'locked' | 'deactivated';
|
|
12
|
+
export interface UserDetailOptions {
|
|
13
|
+
workspaceDomain?: string | null;
|
|
14
|
+
currentUserId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface UserDetail {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
email: string;
|
|
20
|
+
avatarUrl: string | null;
|
|
21
|
+
initials: string;
|
|
22
|
+
role: UserRoleEnum;
|
|
23
|
+
roleName: DisplayRole;
|
|
24
|
+
status: MemberStatus;
|
|
25
|
+
mfaEnabled: boolean;
|
|
26
|
+
mfaRequired: boolean;
|
|
27
|
+
passwordSet: boolean;
|
|
28
|
+
forcePasswordReset: boolean;
|
|
29
|
+
failedLoginCount: number;
|
|
30
|
+
lockedAt: string | null;
|
|
31
|
+
suspendedAt: string | null;
|
|
32
|
+
authProvider: string | null;
|
|
33
|
+
oauthProviders: string[];
|
|
34
|
+
/** True when the account can only sign in via an external identity provider. */
|
|
35
|
+
ssoOnly: boolean;
|
|
36
|
+
emailVerified: boolean;
|
|
37
|
+
lastLoginAt: string | null;
|
|
38
|
+
lastActiveAt: string | null;
|
|
39
|
+
acceptedInviteAt: string | null;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
invitedBy: {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
email: string;
|
|
45
|
+
} | null;
|
|
46
|
+
externalDomain: boolean;
|
|
47
|
+
contentOwnedCount: number;
|
|
48
|
+
isCurrentUser: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface SessionInfo {
|
|
51
|
+
id: string;
|
|
52
|
+
deviceLabel: string;
|
|
53
|
+
browser: string;
|
|
54
|
+
os: string;
|
|
55
|
+
ipCountry: string | null;
|
|
56
|
+
createdAt: string;
|
|
57
|
+
lastSeenAt: string;
|
|
58
|
+
expiresAt: string;
|
|
59
|
+
revokedAt: string | null;
|
|
60
|
+
current: boolean;
|
|
61
|
+
active: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface ActivityEvent {
|
|
64
|
+
id: string;
|
|
65
|
+
event: string;
|
|
66
|
+
/** Whether the user was the actor or the target of the event. */
|
|
67
|
+
role: 'actor' | 'target';
|
|
68
|
+
ipAddress: string | null;
|
|
69
|
+
timestamp: string;
|
|
70
|
+
details: Record<string, unknown> | null;
|
|
71
|
+
}
|
|
72
|
+
export interface PermissionView {
|
|
73
|
+
role: UserRoleEnum;
|
|
74
|
+
roleName: DisplayRole;
|
|
75
|
+
permissions: Permission[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Best-effort User-Agent parsing into a friendly device label. Intentionally
|
|
79
|
+
* dependency-free and conservative: an unknown UA degrades to "Unknown device"
|
|
80
|
+
* rather than guessing. This is presentational only — never used for auth.
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseUserAgent(ua: string | null | undefined): {
|
|
83
|
+
browser: string;
|
|
84
|
+
os: string;
|
|
85
|
+
deviceLabel: string;
|
|
86
|
+
};
|
|
87
|
+
export declare function getUserDetail(db: any, userId: string, opts?: UserDetailOptions): Promise<UserDetail | null>;
|
|
88
|
+
export declare function listUserSessions(db: any, userId: string, currentSessionId?: string): Promise<SessionInfo[]>;
|
|
89
|
+
export declare function listUserActivity(db: any, userId: string, limit?: number): Promise<ActivityEvent[]>;
|
|
90
|
+
export declare function getUserPermissionView(role: string): PermissionView;
|
|
91
|
+
export interface TransferResult {
|
|
92
|
+
documents: number;
|
|
93
|
+
media: number;
|
|
94
|
+
templates: number;
|
|
95
|
+
total: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Count the content a user *owns* (authored documents, uploaded media,
|
|
99
|
+
* created templates). Soft-deleted documents are included so the count
|
|
100
|
+
* reflects everything a transfer would reassign — nothing is silently
|
|
101
|
+
* orphaned. Immutable history (versions, audit logs) is never counted.
|
|
102
|
+
*/
|
|
103
|
+
export declare function countOwnedContent(db: any, userId: string): Promise<number>;
|
|
104
|
+
/**
|
|
105
|
+
* Reassign every piece of content owned by `fromUserId` to `toUserId`:
|
|
106
|
+
* authored documents (`Document.createdById`), uploaded media
|
|
107
|
+
* (`Media.uploadedById`), and created templates (`ContentTemplate.createdById`).
|
|
108
|
+
*
|
|
109
|
+
* Deliberately does NOT touch `updatedById`, version history, or audit logs —
|
|
110
|
+
* those record *who did what* and rewriting them would falsify the trail. This
|
|
111
|
+
* only moves the "owner of record" so content survives a user's removal.
|
|
112
|
+
*
|
|
113
|
+
* Returns the per-type counts actually reassigned. Each model is guarded so a
|
|
114
|
+
* trimmed Prisma schema (or test double) that lacks one of them is a no-op for
|
|
115
|
+
* that type rather than a hard failure.
|
|
116
|
+
*/
|
|
117
|
+
export declare function transferContentOwnership(db: any, fromUserId: string, toUserId: string): Promise<TransferResult>;
|
|
118
|
+
//# sourceMappingURL=user-admin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-admin.d.ts","sourceRoot":"","sources":["../../src/auth/user-admin.ts"],"names":[],"mappings":"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"}
|