Data Protection
Flowstate treats user data with the highest level of care. Every piece of data is validated, isolated, and protected through multiple layers of security controls.
Database Security with Prisma ORM
Section titled “Database Security with Prisma ORM”All database access goes through Prisma ORM, which provides type-safe, parameterized queries. This architecture prevents SQL injection attacks and ensures data integrity.
Why Prisma?
Section titled “Why Prisma?”Traditional database access using raw SQL queries is vulnerable to injection attacks:
// DANGEROUS: User input directly in queryconst user = await db.query(`SELECT * FROM users WHERE email = '${email}'`);If email contains ' OR '1'='1, the query becomes:
SELECT * FROM users WHERE email = '' OR '1'='1'This returns all users, bypassing authentication.
Prisma eliminates this risk by using parameterized queries:
// SAFE: Prisma handles escaping and parameterizationconst user = await prisma.user.findUnique({ where: { email: email },});Prisma generates safe queries that treat user input as data, never as executable code. Even if email contains SQL syntax, it’s treated as a literal string.
No Raw Queries
Section titled “No Raw Queries”The codebase has a strict policy: no raw database queries. All data access uses Prisma’s query builder. This ensures:
- Type safety: TypeScript catches errors at compile time
- Injection prevention: User input is always parameterized
- Consistent patterns: All queries follow the same structure
- Automatic escaping: Prisma handles special characters correctly
The only exception is database migrations, which are reviewed and tested before deployment.
Input Validation with Zod
Section titled “Input Validation with Zod”Every API endpoint validates input using Zod schemas before processing. Validation happens at the API boundary, before data reaches business logic or the database.
Validation Layers
Section titled “Validation Layers”Each endpoint has three validation layers:
- Schema validation: Zod checks that input matches the expected structure
- Business logic validation: Application code checks business rules (e.g., “user must own this resource”)
- Database constraints: MongoDB enforces uniqueness, required fields, and data types
This defense-in-depth approach catches errors early and prevents invalid data from entering the system.
Example: Room Creation
Section titled “Example: Room Creation”Creating a room requires validation at all three layers:
// Layer 1: Schema validation (Zod)const createRoomSchema = z.object({ name: z.string().min(1).max(100), description: z.string().max(500).optional(), isPublic: z.boolean().default(false),});
// Layer 2: Business logic validationif (user.roomCount >= user.maxRooms) { throw new Error('Room limit reached');}
// Layer 3: Database constraints (Prisma schema)model Room { id String @id @default(auto()) @map("_id") @db.ObjectId name String // Required field ownerId String @db.ObjectId owner User @relation(fields: [ownerId], references: [id]) @@unique([ownerId, name]) // Unique constraint}If validation fails at any layer, the request is rejected with a 400 Bad Request response. The error message explains what failed validation, helping developers debug issues without exposing internal system details.
Validation Benefits
Section titled “Validation Benefits”Zod validation provides:
- Type safety: Schemas are TypeScript types, so the compiler catches mismatches
- Runtime checks: Validation happens at runtime, catching errors that TypeScript can’t detect
- Automatic parsing: Zod converts strings to numbers, dates, and other types
- Custom validation: Business rules can be encoded in schemas (e.g., “email must be from allowed domain”)
- Error messages: Zod generates detailed error messages for failed validation
All validation schemas are defined in the same file as the route handler, making it easy to see what input is expected.
File Upload Protection
Section titled “File Upload Protection”File uploads are restricted to specific types and sizes to prevent abuse:
- Allowed types: JPEG, PNG, GIF, WebP only
- Maximum size: 5MB per file
- Path traversal prevention: Filenames are sanitized and validated
- Storage isolation: Files are stored in S3-compatible object storage with access controls
Upload Validation Process
Section titled “Upload Validation Process”When a file is uploaded:
- Size check: File size is checked before reading the entire file (prevents memory exhaustion)
- Type check: MIME type is validated against the allowed list
- Header validation: File headers are checked to ensure the file is actually the claimed type
- Filename sanitization: Filename is stripped of special characters and path separators
- Virus scanning: (Planned) Files will be scanned for malware before storage
- Storage: File is uploaded to S3-compatible storage with a unique key
If any validation step fails, the upload is rejected and the file is discarded.
Path Traversal Prevention
Section titled “Path Traversal Prevention”Filenames are sanitized to prevent path traversal attacks:
// DANGEROUS: User-provided filename could be "../../etc/passwd"const filePath = `/uploads/${filename}`;
// SAFE: Sanitize filename to remove path separatorsconst safeFilename = filename.replace(/[^a-zA-Z0-9._-]/g, '_');const filePath = `/uploads/${safeFilename}`;Even if an attacker uploads a file named ../../etc/passwd, it’s stored as _____etc_passwd in the uploads directory.
Storage Access Controls
Section titled “Storage Access Controls”Uploaded files are stored in S3-compatible object storage (MinIO in development, S3 or Cloudflare R2 in production). Access controls ensure:
- Private by default: Files are not publicly accessible
- Signed URLs: Access is granted via time-limited signed URLs
- User isolation: Users can only access their own files
- Expiry: Signed URLs expire after a short period (typically 1 hour)
When a user requests a file, the backend generates a signed URL that grants temporary access. The URL expires after the specified time, preventing long-term sharing of private files.
User Data Isolation
Section titled “User Data Isolation”User data is strictly isolated. Each user has their own:
- Canvas positions: Block positions are scoped to the user
- Sessions: Login sessions are tied to the user
- Uploaded files: Files are stored with user-scoped keys
- Rooms and offices: Access is controlled by membership
Database queries always filter by user ID to ensure users can only access their own data:
// CORRECT: Filter by authenticated userconst rooms = await prisma.room.findMany({ where: { ownerId: req.user.id },});
// WRONG: Returns all rooms (data leak)const rooms = await prisma.room.findMany();All queries that return user-specific data include a where clause that filters by the authenticated user’s ID. This prevents accidental data leaks.
Multi-Tenancy
Section titled “Multi-Tenancy”Rooms and offices are multi-tenant resources. Multiple users can access the same room, but access is controlled by membership:
// Check if user is a member of the roomconst membership = await prisma.roomMember.findUnique({ where: { roomId_userId: { roomId: roomId, userId: req.user.id, }, },});
if (!membership) { throw new Error('Access denied');}Users can only access rooms they’re members of. Room owners can invite other users, but invited users can’t invite additional users (unless the owner grants them that permission).
Content Moderation
Section titled “Content Moderation”Users can report content that violates community guidelines. The reporting system supports five specific reasons:
| Reason | Description |
|---|---|
| Spam | Misleading, repetitive, or commercial content |
| Harassment | Bullying, threats, or targeted abuse |
| Inappropriate | Offensive, explicit, or disturbing material |
| Copyright | Intellectual property violations |
| Other | Violations not covered by other categories |
Reporting Flow
Section titled “Reporting Flow”- User clicks “Report” on a piece of content (message, room, profile, etc.)
- User selects a reason and optionally provides additional context
- Report is submitted and stored in the database
- Content moderators review the report in the admin dashboard
- Moderator takes action: approve (no violation), reject (false report), or escalate (needs admin review)
- User who reported the content is notified of the outcome
All reports are tracked in the database with:
- Reporter ID: Who submitted the report
- Target ID: What was reported (message, room, user, etc.)
- Reason: Why it was reported
- Context: Additional details from the reporter
- Status: Pending, approved, rejected, or escalated
- Moderator ID: Who reviewed the report
- Resolution: What action was taken
Appeals Process
Section titled “Appeals Process”If content is moderated (removed or hidden), the content owner can appeal the decision:
- User receives a notification that their content was moderated
- User clicks “Appeal” and provides their reasoning
- Appeal is submitted and stored in the database
- A different moderator reviews the appeal (not the original moderator)
- Moderator upholds or overturns the original decision
- User is notified of the appeal outcome
Appeals ensure fairness and accountability. If a moderator makes a mistake, the appeal process provides a path to correction.
Moderator Accountability
Section titled “Moderator Accountability”All moderation actions are logged in the admin audit trail:
- Action type: Report reviewed, content removed, appeal processed, etc.
- Target: What was moderated
- Moderator: Who took the action
- Timestamp: When the action occurred
- Reason: Why the action was taken
Audit logs are immutable and queryable. If a moderator abuses their power, the logs provide evidence for investigation.
Admin Audit Logging
Section titled “Admin Audit Logging”Every privileged action taken by an admin is recorded in an immutable audit log. This provides accountability and enables investigation of suspicious activity.
What’s Logged
Section titled “What’s Logged”Audit logs capture:
- User management: Account creation, deletion, suspension, role changes
- Content moderation: Reports reviewed, content removed, appeals processed
- System configuration: Feature flags changed, settings updated
- Data access: Sensitive data viewed or exported
- Admin actions: Role promotions, permission grants, audit log queries
Each log entry includes:
- Action type: What happened (e.g.,
user_ban,content_delete,role_change) - Target: What was affected (user ID, content ID, resource ID)
- Timestamp: When the action occurred (UTC)
- IP address: Where the request originated
- User agent: Browser or client used
- Admin identity: Who performed the action (admin user ID and email)
- Details: Additional context (e.g., reason for ban, old and new role)
Audit Log Queries
Section titled “Audit Log Queries”Admins can query audit logs by:
- Date range: Show actions between two dates
- Action type: Filter by specific action types
- Admin: Show actions by a specific admin
- Target: Show actions affecting a specific user or resource
Query results are paginated and can be exported as CSV for external analysis.
Immutability
Section titled “Immutability”Audit logs are immutable. Once written, they cannot be modified or deleted. This ensures the integrity of the audit trail.
Even super admins cannot delete audit logs. The only way to remove logs is to delete the entire database, which would be immediately obvious and require physical access to the database server.
Graceful Data Handling
Section titled “Graceful Data Handling”Users have control over their data. They can:
- Export their data: Download a JSON file with all their data (canvas positions, rooms, messages, etc.)
- Delete their account: Permanently remove their account and all associated data
- Revoke sessions: Log out of specific devices or all devices
Account Deletion
Section titled “Account Deletion”Account deletion is permanent and irreversible. The process:
- User requests account deletion
- System sends a confirmation email with a single-use token
- User clicks the confirmation link within 30 minutes
- System validates the token and deletes the account
- All user data is permanently removed:
- User profile and authentication records
- Canvas positions and sessions
- Uploaded files (from S3 storage)
- Room memberships (user is removed from all rooms)
- Messages and content (marked as deleted, not removed)
Messages and content are marked as deleted rather than removed to preserve conversation context. The message text is replaced with “[deleted]” and the author is shown as “[deleted user]”.
Data Export
Section titled “Data Export”Users can export their data as a JSON file. The export includes:
- User profile (name, email, avatar URL)
- Canvas positions (all blocks and their positions)
- Rooms (owned and joined rooms)
- Messages (sent messages with timestamps)
- Sessions (active login sessions)
- Uploaded files (list of files with download URLs)
The export is generated on-demand and sent via email as a downloadable link. The link expires after 24 hours.
No PII in Application Logs
Section titled “No PII in Application Logs”Application logs are sanitized to prevent accidental exposure of personally identifiable information (PII).
What’s Sanitized
Section titled “What’s Sanitized”Logs never contain:
- Email addresses: Replaced with user IDs
- IP addresses: Replaced with hashed values
- Session tokens: Never logged
- Passwords: Never logged (and never stored)
- File contents: Only filenames and sizes are logged
Error messages are also sanitized. Stack traces are logged, but sensitive data is redacted:
// WRONG: Logs user emaillogger.error(`Failed to send email to ${user.email}`);
// CORRECT: Logs user IDlogger.error(`Failed to send email to user ${user.id}`);Log Retention
Section titled “Log Retention”Logs are retained for 90 days and then automatically deleted. This limits the window of exposure if logs are compromised.
Audit logs (admin actions) are retained indefinitely for accountability, but they don’t contain PII beyond user IDs.
Data Protection Checklist
Section titled “Data Protection Checklist”Flowstate’s data protection measures include:
- ✅ Prisma ORM (prevents SQL injection attacks)
- ✅ No raw queries (all database access is type-safe)
- ✅ Zod validation (input is validated before processing)
- ✅ File upload restrictions (type, size, and path validation)
- ✅ S3 storage (files are isolated and access-controlled)
- ✅ User data isolation (queries filter by user ID)
- ✅ Content moderation (reporting and appeals process)
- ✅ Admin audit logging (all privileged actions are logged)
- ✅ Account deletion (users can permanently remove their data)
- ✅ Data export (users can download their data)
- ✅ No PII in logs (logs are sanitized to prevent exposure)
- ✅ Log retention (logs are automatically deleted after 90 days)
Future Data Protection Enhancements
Section titled “Future Data Protection Enhancements”Planned improvements:
- Encryption at rest: Encrypt sensitive fields in the database
- Field-level access control: Restrict access to specific fields based on role
- Data retention policies: Automatically delete old data based on configurable policies
- GDPR compliance tools: Automated data subject access requests and right-to-be-forgotten workflows
- Virus scanning: Scan uploaded files for malware before storage
- Data loss prevention: Detect and prevent accidental exposure of sensitive data
- Backup encryption: Encrypt database backups before storage
Data protection is an ongoing commitment. The platform evolves to meet new regulatory requirements and incorporate industry best practices.