veza/veza-backend-api/migrations/010_auth_and_users.sql
okinrev b7955a680c P0: stabilisation backend/chat/stream + nouvelle base migrations v1
Backend Go:
- Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN.
- Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError).
- Sécurisation de config.go, CORS, statuts de santé et monitoring.
- Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles).
- Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés.
- Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*.

Chat server (Rust):
- Refonte du pipeline JWT + sécurité, audit et rate limiting avancé.
- Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing).
- Nettoyage des panics, gestion d’erreurs robuste, logs structurés.
- Migrations chat alignées sur le schéma UUID et nouvelles features.

Stream server (Rust):
- Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core.
- Transactions P0 pour les jobs et segments, garanties d’atomicité.
- Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION).

Documentation & audits:
- TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services.
- Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3).
- Scripts de reset et de cleanup pour la lab DB et la V1.

Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 11:14:38 +01:00

216 lines
No EOL
7.3 KiB
SQL

-- 010_auth_and_users.sql
-- Core Authentication and User Identity Tables (Aligned with ORIGIN)
-- === USERS ===
CREATE TABLE public.users (
-- Primary Key
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Authentication
email VARCHAR(255) NOT NULL,
email_verified_at TIMESTAMPTZ,
password_hash VARCHAR(255),
-- Profile Basic
username VARCHAR(30) NOT NULL,
slug VARCHAR(255),
first_name VARCHAR(100),
last_name VARCHAR(100),
display_name VARCHAR(100),
-- Legacy Profile fields (kept for Go compatibility, prefer user_profiles)
avatar TEXT,
bio TEXT,
location VARCHAR(100),
birthdate TIMESTAMPTZ,
gender VARCHAR(20),
-- Role & Status
role public.user_role NOT NULL DEFAULT 'user',
is_active BOOLEAN NOT NULL DEFAULT true,
is_verified BOOLEAN NOT NULL DEFAULT false,
is_banned BOOLEAN NOT NULL DEFAULT false,
is_admin BOOLEAN DEFAULT false, -- Legacy boolean, prefer role='admin'
is_public BOOLEAN DEFAULT true, -- Legacy visibility
-- Security
token_version INTEGER NOT NULL DEFAULT 0,
last_password_change_at TIMESTAMPTZ,
-- Tracking
last_login_at TIMESTAMPTZ,
login_count INTEGER NOT NULL DEFAULT 0,
last_login_ip INET,
username_changed_at TIMESTAMPTZ,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
-- Constraints
CONSTRAINT chk_users_email_format CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
CONSTRAINT chk_users_username_format CHECK (username ~* '^[a-zA-Z0-9_]{3,30}$')
);
-- Indexes
CREATE UNIQUE INDEX idx_users_email ON public.users(email) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX idx_users_username ON public.users(username) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX idx_users_slug ON public.users(slug) WHERE deleted_at IS NULL;
CREATE INDEX idx_users_role ON public.users(role);
CREATE INDEX idx_users_created_at_desc ON public.users(created_at DESC);
CREATE INDEX idx_users_deleted_at ON public.users(deleted_at) WHERE deleted_at IS NOT NULL;
-- === FEDERATED IDENTITIES (OAuth) ===
CREATE TABLE public.federated_identities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
-- Provider
provider VARCHAR(50) NOT NULL,
provider_user_id VARCHAR(255) NOT NULL, -- ORIGIN name
provider_id TEXT, -- Legacy name (kept for compatibility if needed, else deprecate)
-- OAuth Data
access_token TEXT,
refresh_token TEXT,
token_expires_at TIMESTAMPTZ, -- ORIGIN name
expires_at TIMESTAMPTZ, -- Legacy name
-- Profile Data
provider_email VARCHAR(255),
provider_username VARCHAR(255),
provider_avatar_url TEXT,
provider_profile_data JSONB,
-- Legacy fields
email TEXT, -- Maps to provider_email
display_name TEXT,
avatar_url TEXT, -- Maps to provider_avatar_url
-- Status
is_primary BOOLEAN NOT NULL DEFAULT false,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_federated_identities_provider_user UNIQUE (provider, provider_user_id)
);
CREATE INDEX idx_federated_identities_user_id ON public.federated_identities(user_id);
CREATE INDEX idx_federated_identities_provider ON public.federated_identities(provider);
-- === REFRESH TOKENS ===
CREATE TABLE public.refresh_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
-- Token
token VARCHAR(255) NOT NULL UNIQUE,
token_hash VARCHAR(255) NOT NULL,
-- Metadata
device_name VARCHAR(255),
device_type VARCHAR(50),
user_agent TEXT,
ip_address INET,
-- Expiration
expires_at TIMESTAMPTZ NOT NULL,
last_used_at TIMESTAMPTZ,
-- Status
is_revoked BOOLEAN NOT NULL DEFAULT false,
revoked_at TIMESTAMPTZ,
revoked_reason VARCHAR(255),
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ, -- Legacy soft delete
CONSTRAINT chk_refresh_tokens_expires_future CHECK (expires_at > created_at)
);
CREATE INDEX idx_refresh_tokens_user_id ON public.refresh_tokens(user_id);
CREATE INDEX idx_refresh_tokens_token_hash ON public.refresh_tokens(token_hash);
CREATE INDEX idx_refresh_tokens_expires_at ON public.refresh_tokens(expires_at);
CREATE INDEX idx_refresh_tokens_is_revoked ON public.refresh_tokens(is_revoked) WHERE is_revoked = false;
-- === PASSWORD RESET TOKENS ===
CREATE TABLE public.password_reset_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
-- Token
token VARCHAR(255) NOT NULL UNIQUE,
token_hash VARCHAR(255) NOT NULL,
-- Status
used BOOLEAN NOT NULL DEFAULT false,
used_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ NOT NULL,
-- Metadata
ip_address INET,
user_agent TEXT,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT chk_password_reset_expires CHECK (expires_at > created_at)
);
CREATE INDEX idx_password_reset_tokens_user_id ON public.password_reset_tokens(user_id);
CREATE INDEX idx_password_reset_tokens_token_hash ON public.password_reset_tokens(token_hash);
CREATE INDEX idx_password_reset_tokens_expires_at ON public.password_reset_tokens(expires_at);
-- === EMAIL VERIFICATION TOKENS ===
CREATE TABLE public.email_verification_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
-- Token
token VARCHAR(255) NOT NULL UNIQUE,
token_hash VARCHAR(255) NOT NULL,
-- Email
email VARCHAR(255) NOT NULL,
-- Status
verified BOOLEAN NOT NULL DEFAULT false, -- Legacy used
used BOOLEAN NOT NULL DEFAULT false, -- Legacy used
verified_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ NOT NULL,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT chk_email_verification_expires CHECK (expires_at > created_at)
);
CREATE INDEX idx_email_verification_tokens_user_id ON public.email_verification_tokens(user_id);
CREATE INDEX idx_email_verification_tokens_token_hash ON public.email_verification_tokens(token_hash);
CREATE INDEX idx_email_verification_tokens_email ON public.email_verification_tokens(email);
-- === USER SESSIONS (Legacy/Auth) ===
CREATE TABLE public.user_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
session_token VARCHAR(255) NOT NULL UNIQUE,
ip_address INET, -- Changed to INET per Origin style
user_agent TEXT,
is_active BOOLEAN DEFAULT true,
last_activity TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_user_sessions_user_id ON public.user_sessions(user_id);
CREATE INDEX idx_user_sessions_expires_at ON public.user_sessions(expires_at);
CREATE INDEX idx_user_sessions_last_activity ON public.user_sessions(last_activity DESC);