-- ============================================================================ -- OrbiQuant IA - Trading Platform -- Schema: auth -- File: tables/01-users.sql -- Description: Core users table for authentication and user management -- ============================================================================ CREATE TABLE auth.users ( -- Primary Key id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Authentication Credentials email CITEXT NOT NULL UNIQUE, email_verified BOOLEAN NOT NULL DEFAULT false, email_verified_at TIMESTAMPTZ, password_hash VARCHAR(255), -- User Status and Role status auth.user_status NOT NULL DEFAULT 'pending_verification', role auth.user_role NOT NULL DEFAULT 'user', -- Multi-Factor Authentication mfa_enabled BOOLEAN NOT NULL DEFAULT false, mfa_method auth.mfa_method NOT NULL DEFAULT 'none', mfa_secret VARCHAR(255), backup_codes JSONB DEFAULT '[]', -- Phone Information phone_number VARCHAR(20), phone_verified BOOLEAN NOT NULL DEFAULT false, phone_verified_at TIMESTAMPTZ, -- Security Settings last_login_at TIMESTAMPTZ, last_login_ip INET, failed_login_attempts INTEGER NOT NULL DEFAULT 0, locked_until TIMESTAMPTZ, -- Account Lifecycle suspended_at TIMESTAMPTZ, suspended_reason TEXT, deactivated_at TIMESTAMPTZ, -- Audit Fields created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by_id UUID, updated_by_id UUID, -- Constraints CONSTRAINT valid_email CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'), -- NOTE: password_or_oauth constraint moved to 99-deferred-constraints.sql -- to resolve circular dependency with oauth_accounts CONSTRAINT failed_attempts_non_negative CHECK (failed_login_attempts >= 0), CONSTRAINT email_verified_at_consistency CHECK ( (email_verified = true AND email_verified_at IS NOT NULL) OR (email_verified = false AND email_verified_at IS NULL) ), CONSTRAINT phone_verified_at_consistency CHECK ( (phone_verified = true AND phone_verified_at IS NOT NULL) OR (phone_verified = false AND phone_verified_at IS NULL) ), CONSTRAINT mfa_secret_consistency CHECK ( (mfa_enabled = true AND mfa_secret IS NOT NULL AND mfa_method != 'none') OR (mfa_enabled = false) ) ); -- Indexes for Performance CREATE INDEX idx_users_email ON auth.users(email); CREATE INDEX idx_users_status ON auth.users(status); CREATE INDEX idx_users_role ON auth.users(role); CREATE INDEX idx_users_last_login ON auth.users(last_login_at DESC); CREATE INDEX idx_users_created_at ON auth.users(created_at DESC); CREATE INDEX idx_users_email_verified ON auth.users(email_verified) WHERE email_verified = false; CREATE INDEX idx_users_locked ON auth.users(locked_until) WHERE locked_until IS NOT NULL; CREATE INDEX idx_users_phone ON auth.users(phone_number) WHERE phone_number IS NOT NULL; -- Table Comments COMMENT ON TABLE auth.users IS 'Core users table for authentication and user management'; -- Column Comments COMMENT ON COLUMN auth.users.id IS 'Unique identifier for the user'; COMMENT ON COLUMN auth.users.email IS 'User email address (case-insensitive, unique)'; COMMENT ON COLUMN auth.users.email_verified IS 'Whether the email has been verified'; COMMENT ON COLUMN auth.users.email_verified_at IS 'Timestamp when email was verified'; COMMENT ON COLUMN auth.users.password_hash IS 'Bcrypt hashed password (null for OAuth-only users)'; COMMENT ON COLUMN auth.users.status IS 'Current status of the user account'; COMMENT ON COLUMN auth.users.role IS 'User role for role-based access control'; COMMENT ON COLUMN auth.users.mfa_enabled IS 'Whether multi-factor authentication is enabled'; COMMENT ON COLUMN auth.users.mfa_method IS 'MFA method used (totp, sms, email)'; COMMENT ON COLUMN auth.users.mfa_secret IS 'Secret key for TOTP MFA'; COMMENT ON COLUMN auth.users.phone_number IS 'User phone number for SMS verification'; COMMENT ON COLUMN auth.users.phone_verified IS 'Whether the phone number has been verified'; COMMENT ON COLUMN auth.users.phone_verified_at IS 'Timestamp when phone was verified'; COMMENT ON COLUMN auth.users.last_login_at IS 'Timestamp of last successful login'; COMMENT ON COLUMN auth.users.last_login_ip IS 'IP address of last successful login'; COMMENT ON COLUMN auth.users.failed_login_attempts IS 'Counter for failed login attempts'; COMMENT ON COLUMN auth.users.locked_until IS 'Account locked until this timestamp (null if not locked)'; COMMENT ON COLUMN auth.users.suspended_at IS 'Timestamp when account was suspended'; COMMENT ON COLUMN auth.users.suspended_reason IS 'Reason for account suspension'; COMMENT ON COLUMN auth.users.deactivated_at IS 'Timestamp when account was deactivated'; COMMENT ON COLUMN auth.users.created_at IS 'Timestamp when user was created'; COMMENT ON COLUMN auth.users.updated_at IS 'Timestamp when user was last updated'; COMMENT ON COLUMN auth.users.created_by_id IS 'ID of user who created this record'; COMMENT ON COLUMN auth.users.updated_by_id IS 'ID of user who last updated this record';