feat: user management — admin create, password reset, archive/delete, quick invite

Phase 1: must_change_password enforcement + change password endpoint/page
Phase 2: Admin user creation (M365-style) with temp password
Phase 3: Password reset (self-service forgot + admin-triggered)
Phase 4: User archive (soft delete) + hard delete with precheck
Phase 5: Quick invite from admin Users page

Also fixes:
- Auto-create subscription for accounts missing one
- Hard delete precheck ignores sole-member personal accounts
- Seed script patches tree nodes for validation compliance

Migrations: 031 (must_change_password), 032 (password_reset_tokens), 033 (user soft delete)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-13 01:42:51 -05:00
parent b8f25f19eb
commit ad59446332
32 changed files with 3064 additions and 38 deletions

View File

@@ -158,6 +158,22 @@ export interface InviteCodeCreateRequest {
trial_duration_days?: number | null
}
// Admin user creation types
export interface AdminUserCreate {
email: string
name: string
account_mode: 'existing' | 'personal'
account_display_code?: string
account_role?: 'engineer' | 'viewer'
send_email: boolean
}
export interface AdminUserCreateResponse {
user: Record<string, unknown>
temporary_password: string
email_sent: boolean
}
// User detail types
export interface AccountSummary {
id: string
@@ -206,6 +222,7 @@ export interface UserDetailResponse {
is_super_admin: boolean
is_team_admin: boolean
created_at: string
deleted_at: string | null
account: AccountSummary | null
subscription: SubscriptionSummary | null
invite_code_used: InviteCodeUsedSummary | null

View File

@@ -2,6 +2,7 @@ export interface Token {
access_token: string
refresh_token: string
token_type: string
must_change_password?: boolean
}
export interface AuthState {

View File

@@ -6,6 +6,8 @@ export interface User {
name: string
role: UserRole
is_super_admin: boolean
is_active: boolean
must_change_password: boolean
account_id: string
account_role: 'owner' | 'engineer' | 'viewer'
created_at: string