π― 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 |