216 lines
7.3 KiB
MySQL
216 lines
7.3 KiB
MySQL
|
|
-- 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);
|