feat(accounts): service-account management, generated passwords, perms-view fixes
Permissions view - Restore the System Accounts listing path and audit related tabs. - Lock the framework system_users deny rules (is_system_managed) and system-principal global rules; show them read-only. System vs service accounts - Only account_type='system' is immutable. service-bot is no longer a system principal; ensureInternalAccounts repairs existing rows. - GET /users now surfaces system/service accounts (gated by read/system_users, read/service_users) and Type is the first column. Service accounts - UI to create/edit/delete service accounts (API-only, no login). - Admin API-key management: GET/POST/DELETE /users/:id/keys, restricted to service accounts, scope-capped to the account's own permissions, with a key-management card on the user detail page. Password policy - Admins never set passwords. Creating a user account auto-generates a strong password (shown once) and forces a change on first login; admin "Reset password" does the same. - Forced change flow: login returns password_change_required + challenge token; POST /auth/change-password validates strength, clears the flag, issues the session. Enforced after password and MFA. - Strength: length 12-128, reject common passwords and email/name. UI - Lock indicators in action columns use a shared LockBtn (tooltip, aligned with other action buttons). - Account-type selection in create is a segmented button selector.
This commit is contained in:
+26
-14
@@ -80,6 +80,7 @@ export interface GlobalRule {
|
||||
modality: 'permission' | 'prohibition';
|
||||
priority: number | null;
|
||||
is_system_principal: boolean;
|
||||
is_system_managed: boolean;
|
||||
}
|
||||
|
||||
export interface RoleHierarchy {
|
||||
@@ -91,15 +92,6 @@ export interface RoleHierarchy {
|
||||
org_name: string;
|
||||
}
|
||||
|
||||
export interface SystemPrincipal {
|
||||
user_id: string;
|
||||
user_name: string;
|
||||
user_email: string | null;
|
||||
description: string | null;
|
||||
created_at: string;
|
||||
global_rules: { id: string; activity: string | null; view: string | null; modality: 'permission' | 'prohibition' }[];
|
||||
}
|
||||
|
||||
export interface UserDetail {
|
||||
id: string;
|
||||
email: string;
|
||||
@@ -109,6 +101,22 @@ export interface UserDetail {
|
||||
needs_placement?: boolean;
|
||||
created_at: string;
|
||||
roles: { role_id: string; role_name: string; org_id: string; org_name: string }[];
|
||||
// Present only on the create/reset response for 'user' accounts: the one-time
|
||||
// generated password the account must change on first login.
|
||||
generated_password?: string;
|
||||
}
|
||||
|
||||
export interface ServiceAccountKey {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
key_prefix: string;
|
||||
readonly: boolean;
|
||||
permission_scope: unknown;
|
||||
expires_at: string | null;
|
||||
last_used_at: string | null;
|
||||
created_at: string;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export interface RuleUser {
|
||||
@@ -183,11 +191,13 @@ export interface MorbacApi {
|
||||
getGlobalRules(token: string, orgIds: string[]): Promise<GlobalRule[]>;
|
||||
createGlobalRule(token: string, orgIds: string[], data: { user_id?: string | null; activity?: string | null; view?: string | null; modality: 'permission' | 'prohibition'; priority?: number }): Promise<GlobalRule>;
|
||||
deleteGlobalRule(token: string, orgIds: string[], id: string): Promise<null>;
|
||||
getSystemPrincipals(token: string, orgIds: string[]): Promise<SystemPrincipal[]>;
|
||||
getUsers(token: string, orgIds: string[]): Promise<UserDetail[]>;
|
||||
createUser(token: string, orgIds: string[], data: { email: string; name: string; password?: string }): Promise<UserDetail>;
|
||||
updateUser(token: string, orgIds: string[], id: string, data: { name?: string; email?: string; is_active?: boolean; password?: string }): Promise<UserDetail>;
|
||||
createUser(token: string, orgIds: string[], data: { email: string; name: string; password?: string; account_type?: 'user' | 'service' }): Promise<UserDetail>;
|
||||
updateUser(token: string, orgIds: string[], id: string, data: { name?: string; email?: string; is_active?: boolean; reset_password?: boolean }): Promise<UserDetail>;
|
||||
deleteUser(token: string, orgIds: string[], id: string): Promise<null>;
|
||||
getUserKeys(token: string, orgIds: string[], userId: string): Promise<ServiceAccountKey[]>;
|
||||
createUserKey(token: string, orgIds: string[], userId: string, data: { name: string; description?: string; readonly?: boolean; expires_at?: string | null }): Promise<ServiceAccountKey>;
|
||||
deleteUserKey(token: string, orgIds: string[], userId: string, keyId: string): Promise<null>;
|
||||
assignRole(token: string, orgIds: string[], data: { user_id: string; role_id: string; org_id: string }): Promise<unknown>;
|
||||
removeRole(token: string, orgIds: string[], data: { user_id: string; role_id: string; org_id: string }): Promise<unknown>;
|
||||
getActivityUsers(token: string, orgIds: string[], name: string): Promise<RuleUser[]>;
|
||||
@@ -271,13 +281,15 @@ export function createMorbacApi(apiUrl: string): MorbacApi {
|
||||
createGlobalRule: (t, orgIds, d) => api('POST', '/global-rules', t, orgIds, d) as Promise<GlobalRule>,
|
||||
deleteGlobalRule: (t, orgIds, id) => api('DELETE', `/global-rules/${id}`, t, orgIds) as Promise<null>,
|
||||
|
||||
getSystemPrincipals: (t, orgIds) => api('GET', '/system-principals', t, orgIds) as Promise<SystemPrincipal[]>,
|
||||
|
||||
getUsers: (t, orgIds) => api('GET', '/users', t, orgIds) as Promise<UserDetail[]>,
|
||||
createUser: (t, orgIds, d) => api('POST', '/users', t, orgIds, d) as Promise<UserDetail>,
|
||||
updateUser: (t, orgIds, id, d) => api('PATCH', `/users/${id}`, t, orgIds, d) as Promise<UserDetail>,
|
||||
deleteUser: (t, orgIds, id) => api('DELETE', `/users/${id}`, t, orgIds) as Promise<null>,
|
||||
|
||||
getUserKeys: (t, orgIds, userId) => api('GET', `/users/${userId}/keys`, t, orgIds) as Promise<ServiceAccountKey[]>,
|
||||
createUserKey: (t, orgIds, userId, d) => api('POST', `/users/${userId}/keys`, t, orgIds, d) as Promise<ServiceAccountKey>,
|
||||
deleteUserKey: (t, orgIds, userId, keyId) => api('DELETE', `/users/${userId}/keys/${keyId}`, t, orgIds) as Promise<null>,
|
||||
|
||||
assignRole: (t, orgIds, d) => api('POST', `/users/${d.user_id}/roles`, t, orgIds, { role_id: d.role_id, org_id: d.org_id }),
|
||||
removeRole: (t, orgIds, d) => api('DELETE', `/users/${d.user_id}/roles`, t, orgIds, { role_id: d.role_id, org_id: d.org_id }),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user