🎯 Security Philosophy

FlowState follows a zero-trust security model. Every request is authenticated, every action is authorized, and all data is encrypted at rest and in transit.

πŸ›‘οΈ
Cloudflare Advantage: Built-in DDoS protection, WAF, bot management, and edge authenticationβ€”all included at no extra cost.
πŸ”‘ Authentication Options
Method Implementation Pros Cons
Email/Password D1 + bcrypt Simple, no deps Password management
Magic Link Email token Passwordless Email dependency
OAuth (Google) OIDC flow Trusted, fast Google dependency
OAuth (Discord) Discord OAuth Music community Less mainstream
Cloudflare Access Zero Trust Enterprise SSO Paid tier
πŸ’‘
MVP Recommendation: Email/password + Google OAuth covers 95% of users. Add Discord for community engagement.
πŸ“Š User Schema
-- D1 Schema
CREATE TABLE users (
  id TEXT PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  email_verified BOOLEAN DEFAULT FALSE,
  password_hash TEXT,  -- NULL for OAuth-only users

  -- Profile
  display_name TEXT,
  avatar_url TEXT,

  -- OAuth connections
  google_id TEXT UNIQUE,
  discord_id TEXT UNIQUE,

  -- Subscription
  tier TEXT DEFAULT 'free' CHECK (tier IN ('free', 'pro', 'enterprise')),
  stripe_customer_id TEXT,
  subscription_ends_at DATETIME,

  -- Security
  two_factor_enabled BOOLEAN DEFAULT FALSE,
  two_factor_secret TEXT,
  failed_login_attempts INTEGER DEFAULT 0,
  locked_until DATETIME,

  -- Metadata
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  last_login_at DATETIME,
  last_ip TEXT
);

CREATE TABLE sessions (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL REFERENCES users(id),
  token_hash TEXT NOT NULL,
  device_info TEXT,
  ip_address TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  expires_at DATETIME NOT NULL,
  last_active_at DATETIME
);

CREATE INDEX idx_sessions_user ON sessions(user_id);
CREATE INDEX idx_sessions_expires ON sessions(expires_at);
πŸ” JWT Token Strategy
// auth.ts
interface JWTPayload {
  sub: string;      // user ID
  email: string;
  tier: 'free' | 'pro' | 'enterprise';
  iat: number;      // issued at
  exp: number;      // expiration
  jti: string;      // token ID (for revocation)
}

async function createToken(user: User, env: Env): Promise<string> {
  const payload: JWTPayload = {
    sub: user.id,
    email: user.email,
    tier: user.tier,
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, // 7 days
    jti: crypto.randomUUID()
  };

  return await new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .sign(new TextEncoder().encode(env.JWT_SECRET));
}

async function verifyToken(token: string, env: Env): Promise<JWTPayload> {
  const { payload } = await jwtVerify(
    token,
    new TextEncoder().encode(env.JWT_SECRET)
  );

  // Check if token is revoked
  const revoked = await env.REVOKED_TOKENS.get(payload.jti as string);
  if (revoked) {
    throw new Error('Token revoked');
  }

  return payload as JWTPayload;
}

Token Storage

Token Type Storage Lifetime
Access Token Memory (JS variable) 15 minutes
Refresh Token HttpOnly Cookie 7 days
Session ID D1 Database 30 days
πŸ›‘οΈ API Security

Rate Limiting

// rate-limit.ts
const LIMITS = {
  api: { requests: 100, window: 60 },      // 100 req/min
  auth: { requests: 5, window: 60 },       // 5 login attempts/min
  ai: { requests: 20, window: 60 },        // 20 AI calls/min
  export: { requests: 5, window: 3600 }    // 5 exports/hour
};

async function checkRateLimit(
  key: string,
  type: keyof typeof LIMITS,
  env: Env
): Promise<{ allowed: boolean; remaining: number }> {
  const limit = LIMITS[type];
  const current = await env.RATE_LIMITS.get(key);

  if (!current) {
    await env.RATE_LIMITS.put(key, '1', { expirationTtl: limit.window });
    return { allowed: true, remaining: limit.requests - 1 };
  }

  const count = parseInt(current);
  if (count >= limit.requests) {
    return { allowed: false, remaining: 0 };
  }

  await env.RATE_LIMITS.put(key, String(count + 1), { expirationTtl: limit.window });
  return { allowed: true, remaining: limit.requests - count - 1 };
}

Request Validation

// validation.ts
import { z } from 'zod';

const CreateProjectSchema = z.object({
  name: z.string().min(1).max(100),
  tempo: z.number().min(20).max(300).default(90),
  key: z.string().regex(/^[A-G][#b]?m?$/).optional()
});

async function handleCreateProject(request: Request) {
  const body = await request.json();
  const validated = CreateProjectSchema.parse(body);
  // Use validated data...
}
πŸ”’ Data Encryption
Layer Encryption Implementation
Transport TLS 1.3 Cloudflare (automatic)
Storage (R2) AES-256 Cloudflare (automatic)
Database (D1) AES-256 Cloudflare (automatic)
Passwords bcrypt (cost 12) Application layer
Sensitive fields AES-GCM Application layer

Field-Level Encryption

// encryption.ts
async function encryptField(plaintext: string, env: Env): Promise<string> {
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(env.ENCRYPTION_KEY),
    { name: 'AES-GCM' },
    false,
    ['encrypt']
  );

  const iv = crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    new TextEncoder().encode(plaintext)
  );

  // Return IV + ciphertext as base64
  const combined = new Uint8Array(iv.length + ciphertext.byteLength);
  combined.set(iv);
  combined.set(new Uint8Array(ciphertext), iv.length);

  return btoa(String.fromCharCode(...combined));
}
🌐 CORS Configuration
// cors.ts
const ALLOWED_ORIGINS = [
  'https://flowstate.app',
  'https://www.flowstate.app',
  'http://localhost:3000',  // Development
];

function handleCORS(request: Request): Response | null {
  const origin = request.headers.get('Origin');

  if (!origin || !ALLOWED_ORIGINS.includes(origin)) {
    return new Response('Forbidden', { status: 403 });
  }

  // Handle preflight
  if (request.method === 'OPTIONS') {
    return new Response(null, {
      headers: {
        'Access-Control-Allow-Origin': origin,
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
        'Access-Control-Max-Age': '86400',
        'Access-Control-Allow-Credentials': 'true'
      }
    });
  }

  return null; // Continue to handler
}
πŸ€– Bot Protection

Cloudflare provides built-in bot management.

Protection Cloudflare Feature Status
DDoS Automatic mitigation βœ… Included
WAF Managed rules βœ… Included
Bot Score Bot Management βœ… Included
Challenge Turnstile (CAPTCHA) βœ… Free
IP Reputation Threat intelligence βœ… Included

Turnstile Integration

// turnstile.ts
async function verifyTurnstile(token: string, env: Env): Promise<boolean> {
  const response = await fetch(
    'https://challenges.cloudflare.com/turnstile/v0/siteverify',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        secret: env.TURNSTILE_SECRET,
        response: token
      })
    }
  );

  const result = await response.json();
  return result.success;
}
πŸ“ Audit Logging
-- Audit log schema
CREATE TABLE audit_logs (
  id TEXT PRIMARY KEY,
  user_id TEXT,
  action TEXT NOT NULL,
  resource_type TEXT,
  resource_id TEXT,
  ip_address TEXT,
  user_agent TEXT,
  metadata TEXT,  -- JSON
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_audit_user ON audit_logs(user_id);
CREATE INDEX idx_audit_action ON audit_logs(action);
CREATE INDEX idx_audit_created ON audit_logs(created_at);

Logged Actions

Action Data Logged
auth.login Method, success/fail, IP
auth.logout Session ID
project.create Project ID, name
project.delete Project ID
project.share Project ID, invitee, role
export.audio Project ID, format
subscription.change Old tier, new tier
πŸ” Secret Management

All secrets stored as Cloudflare Worker secrets (encrypted at rest).

Required Secrets

Secret Purpose
JWT_SECRET Token signing
ENCRYPTION_KEY Field encryption
GOOGLE_CLIENT_SECRET Google OAuth
DISCORD_CLIENT_SECRET Discord OAuth
GEMINI_API_KEY AI services
STRIPE_SECRET_KEY Payments
TURNSTILE_SECRET Bot protection
# Setting secrets via Wrangler
wrangler secret put JWT_SECRET
wrangler secret put ENCRYPTION_KEY
wrangler secret put GEMINI_API_KEY
βœ… Security Checklist
Category Item Status
Auth Password hashing (bcrypt) MVP
Auth JWT with expiration MVP
Auth OAuth providers MVP
Auth 2FA (TOTP) v1.1
API Rate limiting MVP
API Input validation MVP
API CORS configuration MVP
Data TLS everywhere MVP
Data Encryption at rest MVP
Infra WAF rules MVP
Infra DDoS protection MVP
Audit Security logging MVP