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

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:
Sharang Parnerkar
2026-04-05 09:37:50 +02:00
parent c433bc021e
commit f565dfdb15
35 changed files with 4232 additions and 14 deletions

View 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);