851dd389d7
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. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
137 lines
5.3 KiB
TypeScript
137 lines
5.3 KiB
TypeScript
export const orgResponse = {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', format: 'uuid' },
|
|
name: { type: 'string' },
|
|
parent_id: { type: 'string', format: 'uuid', nullable: true },
|
|
metadata: { type: 'object' },
|
|
},
|
|
};
|
|
|
|
export const roleResponse = {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', format: 'uuid' },
|
|
org_id: { type: 'string', format: 'uuid' },
|
|
org_name: { type: 'string' },
|
|
name: { type: 'string' },
|
|
description: { type: 'string', nullable: true },
|
|
},
|
|
};
|
|
|
|
export const ruleResponse = {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', format: 'uuid' },
|
|
role_id: { type: 'string', format: 'uuid' },
|
|
org_id: { type: 'string', format: 'uuid' },
|
|
org_name: { type: 'string' },
|
|
role_name: { type: 'string' },
|
|
activity: { type: 'string' },
|
|
view: { type: 'string' },
|
|
modality: { type: 'string', enum: ['permission', 'prohibition'] },
|
|
priority: { type: 'integer', nullable: true },
|
|
scope: { type: 'string' },
|
|
},
|
|
};
|
|
|
|
export const userResponse = {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', format: 'uuid' },
|
|
email: { type: 'string', format: 'email' },
|
|
name: { type: 'string' },
|
|
account_type: { type: 'string', enum: ['user', 'system', 'service'] },
|
|
is_active: { type: 'boolean' },
|
|
needs_placement: { type: 'boolean' },
|
|
created_at: { type: 'string', format: 'date-time' },
|
|
roles: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
role_id: { type: 'string', format: 'uuid' },
|
|
role_name: { type: 'string' },
|
|
org_id: { type: 'string', format: 'uuid' },
|
|
org_name: { type: 'string' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
export const crossOrgRuleResponse = {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', format: 'uuid' },
|
|
source_org_id: { type: 'string', format: 'uuid' },
|
|
source_role_id: { type: 'string', format: 'uuid' },
|
|
target_org_id: { type: 'string', format: 'uuid' },
|
|
source_org_name: { type: 'string' },
|
|
source_role_name: { type: 'string' },
|
|
target_org_name: { type: 'string' },
|
|
activity: { type: 'string' },
|
|
view: { type: 'string' },
|
|
modality: { type: 'string', enum: ['permission', 'prohibition'] },
|
|
priority: { type: 'integer', nullable: true },
|
|
},
|
|
};
|
|
|
|
export const userRuleResponse = {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', format: 'uuid' },
|
|
user_id: { type: 'string', format: 'uuid' },
|
|
org_id: { type: 'string', format: 'uuid' },
|
|
user_name: { type: 'string' },
|
|
user_email: { type: 'string', nullable: true },
|
|
org_name: { type: 'string' },
|
|
activity: { type: 'string' },
|
|
view: { type: 'string' },
|
|
modality: { type: 'string', enum: ['permission', 'prohibition'] },
|
|
priority: { type: 'integer', nullable: true },
|
|
},
|
|
};
|
|
|
|
export const globalRuleResponse = {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', format: 'uuid' },
|
|
user_id: { type: 'string', format: 'uuid', nullable: true },
|
|
user_name: { type: 'string', nullable: true },
|
|
user_email: { type: 'string', nullable: true },
|
|
activity: { type: 'string', nullable: true },
|
|
view: { type: 'string', nullable: true },
|
|
modality: { type: 'string', enum: ['permission', 'prohibition'] },
|
|
priority: { type: 'integer', nullable: true },
|
|
is_system_principal: { type: 'boolean' },
|
|
is_system_managed: { type: 'boolean' },
|
|
},
|
|
};
|
|
|
|
export const delegationResponse = {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', format: 'uuid' },
|
|
delegator_id: { type: 'string', format: 'uuid' },
|
|
delegatee_id: { type: 'string', format: 'uuid' },
|
|
delegator_name: { type: 'string', nullable: true },
|
|
delegator_email: { type: 'string', nullable: true },
|
|
delegatee_name: { type: 'string', nullable: true },
|
|
delegatee_email: { type: 'string', nullable: true },
|
|
role_id: { type: 'string', format: 'uuid' },
|
|
role_name: { type: 'string' },
|
|
org_id: { type: 'string', format: 'uuid' },
|
|
org_name: { type: 'string' },
|
|
valid_from: { type: 'string', format: 'date-time' },
|
|
valid_until: { type: 'string', format: 'date-time' },
|
|
revoked: { type: 'boolean' },
|
|
created_at: { type: 'string', format: 'date-time' },
|
|
},
|
|
};
|
|
|
|
export const notFound = { description: 'Resource not found.' };
|
|
export const forbidden = { description: 'Insufficient permissions.' };
|
|
export const unauthorized = { description: 'Missing or invalid token.' };
|
|
export const badRequest = { description: 'X-Org-Id header is required.' };
|