feat(pitch-deck): add passwordless investor auth, audit logs, snapshots & PWA
Some checks failed
CI / go-lint (pull_request) Failing after 17s
CI / python-lint (pull_request) Failing after 12s
CI / nodejs-lint (pull_request) Failing after 7s
CI / test-go-consent (pull_request) Failing after 11s
CI / test-python-voice (pull_request) Failing after 11s
CI / test-bqas (pull_request) Failing after 11s
CI / Deploy (pull_request) Has been skipped
Some checks failed
CI / go-lint (pull_request) Failing after 17s
CI / python-lint (pull_request) Failing after 12s
CI / nodejs-lint (pull_request) Failing after 7s
CI / test-go-consent (pull_request) Failing after 11s
CI / test-python-voice (pull_request) Failing after 11s
CI / test-bqas (pull_request) Failing after 11s
CI / Deploy (pull_request) Has been skipped
Implement a complete investor access system for the pitch deck: - Passwordless magic link auth (jose JWT + nodemailer SMTP) - Per-investor audit logging (slide views, assumption changes, chat) - Financial model snapshot persistence (auto-save/restore per investor) - PWA support (manifest, service worker, offline caching, icons) - Security safeguards (watermark overlay, rate limiting, anti-scraping headers, content protection, single-session enforcement) - Admin API for invite/revoke/audit-log management - Integrated into docker-compose.coolify.yml for production deployment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
79
pitch-deck/migrations/001_investor_auth.sql
Normal file
79
pitch-deck/migrations/001_investor_auth.sql
Normal file
@@ -0,0 +1,79 @@
|
||||
-- =========================================================
|
||||
-- Pitch Deck: Investor Auth, Audit Logs, Snapshots
|
||||
-- =========================================================
|
||||
|
||||
-- Invited investors
|
||||
CREATE TABLE IF NOT EXISTS pitch_investors (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
name VARCHAR(255),
|
||||
company VARCHAR(255),
|
||||
invited_by VARCHAR(255) NOT NULL DEFAULT 'admin',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'invited'
|
||||
CHECK (status IN ('invited', 'active', 'revoked')),
|
||||
last_login_at TIMESTAMPTZ,
|
||||
login_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_investors_email ON pitch_investors(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_investors_status ON pitch_investors(status);
|
||||
|
||||
-- Single-use magic link tokens
|
||||
CREATE TABLE IF NOT EXISTS pitch_magic_links (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
investor_id UUID NOT NULL REFERENCES pitch_investors(id) ON DELETE CASCADE,
|
||||
token VARCHAR(128) NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
used_at TIMESTAMPTZ,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_magic_links_token ON pitch_magic_links(token);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_magic_links_investor ON pitch_magic_links(investor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_magic_links_expires ON pitch_magic_links(expires_at);
|
||||
|
||||
-- Audit log for all investor activity
|
||||
CREATE TABLE IF NOT EXISTS pitch_audit_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
investor_id UUID REFERENCES pitch_investors(id) ON DELETE SET NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
details JSONB DEFAULT '{}',
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
slide_id VARCHAR(50),
|
||||
session_id UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_audit_created ON pitch_audit_logs(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_audit_investor ON pitch_audit_logs(investor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_audit_action ON pitch_audit_logs(action);
|
||||
|
||||
-- Per-investor financial model snapshots (JSONB)
|
||||
CREATE TABLE IF NOT EXISTS pitch_investor_snapshots (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
investor_id UUID NOT NULL REFERENCES pitch_investors(id) ON DELETE CASCADE,
|
||||
scenario_id UUID NOT NULL,
|
||||
assumptions JSONB NOT NULL,
|
||||
label VARCHAR(255),
|
||||
is_latest BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_snapshots_investor ON pitch_investor_snapshots(investor_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_pitch_snapshots_latest
|
||||
ON pitch_investor_snapshots(investor_id, scenario_id) WHERE is_latest = true;
|
||||
|
||||
-- Active sessions
|
||||
CREATE TABLE IF NOT EXISTS pitch_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
investor_id UUID NOT NULL REFERENCES pitch_investors(id) ON DELETE CASCADE,
|
||||
token_hash VARCHAR(128) NOT NULL,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
revoked BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_sessions_investor ON pitch_sessions(investor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_pitch_sessions_token ON pitch_sessions(token_hash);
|
||||
Reference in New Issue
Block a user