620 lines
16 KiB
Markdown
620 lines
16 KiB
Markdown
# Hackerspace.sh - Production Readiness Audit
|
|
|
|
> **Audit Date**: 2026-03-10
|
|
> **Auditor**: v0 Comprehensive Analysis
|
|
> **Status**: PRE-PRODUCTION - Action Required
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
Hackerspace.sh is a well-architected member management platform with a solid foundation. However, several critical issues must be addressed before production deployment. This audit identifies **47 issues** across security, functionality, performance, and code quality.
|
|
|
|
### Severity Breakdown
|
|
|
|
| Severity | Count | Description |
|
|
|----------|-------|-------------|
|
|
| **CRITICAL** | 5 | Security vulnerabilities, data loss risk |
|
|
| **HIGH** | 12 | Broken functionality, UX blockers |
|
|
| **MEDIUM** | 18 | Missing features, technical debt |
|
|
| **LOW** | 12 | Nice-to-have improvements |
|
|
|
|
---
|
|
|
|
## 1. CRITICAL Issues (Must Fix Before Production)
|
|
|
|
### 1.1 Missing Input Validation [CRITICAL]
|
|
|
|
**Location**: `lib/actions.ts` (all server actions)
|
|
|
|
**Issue**: Server actions accept user input without validation. Attackers could inject malicious data or cause database errors.
|
|
|
|
**Current Code**:
|
|
```typescript
|
|
export async function createTask(formData: {
|
|
title: string
|
|
// ... accepts any string
|
|
}) {
|
|
const { data, error } = await supabase.from('tasks').insert({
|
|
title: formData.title, // Raw input!
|
|
// ...
|
|
})
|
|
}
|
|
```
|
|
|
|
**Fix Required**:
|
|
```typescript
|
|
import { z } from 'zod'
|
|
|
|
const createTaskSchema = z.object({
|
|
title: z.string().min(1).max(200).trim(),
|
|
description: z.string().max(2000).optional(),
|
|
type: z.enum(['task', 'chore']),
|
|
area: z.string().max(50).optional(),
|
|
recurrence: z.enum(['none', 'daily', 'weekly', 'biweekly', 'monthly']).default('none'),
|
|
due_date: z.string().datetime().optional(),
|
|
})
|
|
|
|
export async function createTask(formData: unknown) {
|
|
const parsed = createTaskSchema.safeParse(formData)
|
|
if (!parsed.success) {
|
|
return { error: 'Invalid input', details: parsed.error.flatten() }
|
|
}
|
|
// Use parsed.data
|
|
}
|
|
```
|
|
|
|
**Files to Update**:
|
|
- `lib/actions.ts` - All 30+ actions
|
|
- Add `zod` to dependencies
|
|
|
|
---
|
|
|
|
### 1.2 Secrets Not Encrypted [CRITICAL]
|
|
|
|
**Location**: `secrets` table, `integrations.config`
|
|
|
|
**Issue**: Sensitive credentials stored as plain text in database. Compromised database = compromised credentials.
|
|
|
|
**Current State**:
|
|
- `secrets.value` stores plain text
|
|
- `integrations.config` stores API keys in JSONB
|
|
|
|
**Fix Required**:
|
|
1. Add encryption layer using `@vercel/kv` or custom AES-256-GCM
|
|
2. Encrypt on write, decrypt on read
|
|
3. Store encryption key in environment variable
|
|
|
|
```typescript
|
|
import crypto from 'crypto'
|
|
|
|
const ENCRYPTION_KEY = process.env.SECRETS_ENCRYPTION_KEY! // 32 bytes
|
|
|
|
function encrypt(text: string): string {
|
|
const iv = crypto.randomBytes(16)
|
|
const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(ENCRYPTION_KEY, 'hex'), iv)
|
|
let encrypted = cipher.update(text, 'utf8', 'hex')
|
|
encrypted += cipher.final('hex')
|
|
const authTag = cipher.getAuthTag().toString('hex')
|
|
return `${iv.toString('hex')}:${authTag}:${encrypted}`
|
|
}
|
|
|
|
function decrypt(encrypted: string): string {
|
|
const [ivHex, authTagHex, encryptedText] = encrypted.split(':')
|
|
const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(ENCRYPTION_KEY, 'hex'), Buffer.from(ivHex, 'hex'))
|
|
decipher.setAuthTag(Buffer.from(authTagHex, 'hex'))
|
|
let decrypted = decipher.update(encryptedText, 'hex', 'utf8')
|
|
decrypted += decipher.final('utf8')
|
|
return decrypted
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 1.3 Rate Limiting Missing [CRITICAL]
|
|
|
|
**Location**: All server actions, middleware
|
|
|
|
**Issue**: No rate limiting on auth endpoints or API calls. Vulnerable to:
|
|
- Brute force password attacks
|
|
- Denial of service
|
|
- Resource exhaustion
|
|
|
|
**Fix Required**:
|
|
```typescript
|
|
// middleware.ts
|
|
import { Ratelimit } from '@upstash/ratelimit'
|
|
import { Redis } from '@upstash/redis'
|
|
|
|
const ratelimit = new Ratelimit({
|
|
redis: Redis.fromEnv(),
|
|
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
|
|
})
|
|
|
|
export async function middleware(request: NextRequest) {
|
|
const ip = request.ip ?? '127.0.0.1'
|
|
const { success, limit, reset, remaining } = await ratelimit.limit(ip)
|
|
|
|
if (!success) {
|
|
return new NextResponse('Too Many Requests', {
|
|
status: 429,
|
|
headers: {
|
|
'X-RateLimit-Limit': limit.toString(),
|
|
'X-RateLimit-Remaining': remaining.toString(),
|
|
'X-RateLimit-Reset': reset.toString(),
|
|
},
|
|
})
|
|
}
|
|
// Continue...
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 1.4 CSRF Protection Missing [CRITICAL]
|
|
|
|
**Location**: All forms
|
|
|
|
**Issue**: Forms don't include CSRF tokens. Attackers could trick users into performing actions.
|
|
|
|
**Current State**: Server actions bypass traditional CSRF but still vulnerable in some scenarios.
|
|
|
|
**Fix Required**:
|
|
1. Verify `Origin` header in server actions
|
|
2. Add CSRF token for sensitive operations
|
|
|
|
```typescript
|
|
// In server actions
|
|
export async function deleteSecret(secretId: string) {
|
|
const headersList = headers()
|
|
const origin = headersList.get('origin')
|
|
const host = headersList.get('host')
|
|
|
|
if (origin && !origin.includes(host!)) {
|
|
return { error: 'Invalid origin' }
|
|
}
|
|
// Continue...
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 1.5 Admin Client Exposure Risk [CRITICAL]
|
|
|
|
**Location**: `lib/supabase/admin.ts`
|
|
|
|
**Issue**: Service role key used in several places. Any mistake could expose it to client.
|
|
|
|
**Current Usage**:
|
|
- `lib/auth-actions.ts` - createSpace, joinSpace
|
|
- Signup trigger uses SECURITY DEFINER (safe)
|
|
|
|
**Fix Required**:
|
|
1. Audit all admin client usage
|
|
2. Move admin operations to dedicated API routes
|
|
3. Add ESLint rule to prevent client-side imports
|
|
|
|
```javascript
|
|
// .eslintrc.js
|
|
module.exports = {
|
|
rules: {
|
|
'no-restricted-imports': [
|
|
'error',
|
|
{
|
|
paths: [{
|
|
name: '@/lib/supabase/admin',
|
|
message: 'Admin client should only be used in API routes, not client components.',
|
|
}],
|
|
},
|
|
],
|
|
},
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. HIGH Priority Issues
|
|
|
|
### 2.1 Social Auth Buttons Non-Functional [HIGH]
|
|
|
|
**Location**: `app/login/page.tsx`
|
|
|
|
**Issue**: GitHub and Google buttons are rendered but don't work.
|
|
|
|
**Fix**:
|
|
```typescript
|
|
async function handleGitHubLogin() {
|
|
const supabase = createClient()
|
|
const { error } = await supabase.auth.signInWithOAuth({
|
|
provider: 'github',
|
|
options: {
|
|
redirectTo: `${window.location.origin}/auth/callback`,
|
|
},
|
|
})
|
|
if (error) setError(error.message)
|
|
}
|
|
```
|
|
|
|
**Also Required**:
|
|
- Configure OAuth providers in Supabase Dashboard
|
|
- Add provider credentials
|
|
|
|
---
|
|
|
|
### 2.2 Import Feature Non-Functional [HIGH]
|
|
|
|
**Location**: `app/(app)/import/page.tsx`
|
|
|
|
**Issue**: File upload and database connector UI exists but doesn't process files.
|
|
|
|
**Fix Required**:
|
|
1. Add file input handler
|
|
2. Implement CSV parser (use `papaparse`)
|
|
3. Add column mapping logic
|
|
4. Implement batch insert
|
|
|
|
```typescript
|
|
'use client'
|
|
import Papa from 'papaparse'
|
|
import { importMembers } from '@/lib/actions'
|
|
|
|
function handleFileUpload(event: React.ChangeEvent<HTMLInputElement>) {
|
|
const file = event.target.files?.[0]
|
|
if (!file) return
|
|
|
|
Papa.parse(file, {
|
|
header: true,
|
|
complete: async (results) => {
|
|
const mappedRows = results.data.map(row => ({
|
|
display_name: row['Full Name'],
|
|
email: row['Email'],
|
|
// ... map other fields
|
|
}))
|
|
const result = await importMembers(mappedRows)
|
|
// Handle result
|
|
},
|
|
error: (error) => setError(error.message),
|
|
})
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.3 Payment Platform Integrations [HIGH]
|
|
|
|
**Location**: `app/(app)/settings/settings-client.tsx`
|
|
|
|
**Issue**: PayPal, Zeffy, Venmo integrations are UI-only. No actual API connections.
|
|
|
|
**Status**:
|
|
- Integration config storage works
|
|
- No OAuth flows
|
|
- No transaction sync
|
|
- No webhooks
|
|
|
|
**Required Work**:
|
|
1. PayPal: Implement OAuth 2.0, transaction API
|
|
2. Zeffy: Implement API key auth, webhook handler
|
|
3. Venmo: Research Business API availability
|
|
|
|
---
|
|
|
|
### 2.4 Webhook Endpoint Missing [HIGH]
|
|
|
|
**Location**: Settings shows webhook URL but no endpoint exists
|
|
|
|
**Issue**: URL `https://api.hackerspace.sh/{slug}/members` is displayed but endpoint doesn't exist.
|
|
|
|
**Fix Required**:
|
|
```typescript
|
|
// app/api/[slug]/members/route.ts
|
|
import { createAdminClient } from '@/lib/supabase/admin'
|
|
import { headers } from 'next/headers'
|
|
import crypto from 'crypto'
|
|
|
|
export async function POST(
|
|
request: Request,
|
|
{ params }: { params: { slug: string } }
|
|
) {
|
|
const headersList = headers()
|
|
const signature = headersList.get('x-webhook-signature')
|
|
|
|
const admin = createAdminClient()
|
|
const { data: space } = await admin
|
|
.from('spaces')
|
|
.select('id, webhook_secret')
|
|
.eq('slug', params.slug)
|
|
.single()
|
|
|
|
if (!space || !space.webhook_secret) {
|
|
return Response.json({ error: 'Invalid space' }, { status: 404 })
|
|
}
|
|
|
|
// Verify signature
|
|
const body = await request.text()
|
|
const expectedSignature = crypto
|
|
.createHmac('sha256', space.webhook_secret)
|
|
.update(body)
|
|
.digest('hex')
|
|
|
|
if (signature !== expectedSignature) {
|
|
return Response.json({ error: 'Invalid signature' }, { status: 401 })
|
|
}
|
|
|
|
// Process webhook payload
|
|
const data = JSON.parse(body)
|
|
// ...
|
|
|
|
return Response.json({ success: true })
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.5 Error Boundaries Missing [HIGH]
|
|
|
|
**Location**: All pages
|
|
|
|
**Issue**: No error boundaries. Unhandled errors crash entire app.
|
|
|
|
**Fix Required**:
|
|
```typescript
|
|
// app/(app)/error.tsx
|
|
'use client'
|
|
|
|
export default function Error({
|
|
error,
|
|
reset,
|
|
}: {
|
|
error: Error & { digest?: string }
|
|
reset: () => void
|
|
}) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="text-center">
|
|
<h2 className="text-xl font-semibold mb-2">Something went wrong</h2>
|
|
<p className="text-muted-foreground mb-4">{error.message}</p>
|
|
<button onClick={reset} className="bg-primary text-white px-4 py-2 rounded">
|
|
Try again
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.6 Loading States Inconsistent [HIGH]
|
|
|
|
**Location**: Multiple pages
|
|
|
|
**Issue**: Some pages have loading states, others show blank while data loads.
|
|
|
|
**Fix Required**: Add loading.tsx to all route groups:
|
|
```typescript
|
|
// app/(app)/loading.tsx
|
|
export default function Loading() {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<Spinner className="w-8 h-8" />
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.7 Session Refresh Issues [HIGH]
|
|
|
|
**Location**: Long-running sessions
|
|
|
|
**Issue**: Session may expire during use without proper refresh.
|
|
|
|
**Current**: Middleware calls `updateSession` but client components don't handle expiry.
|
|
|
|
**Fix Required**:
|
|
```typescript
|
|
// Add to layout or hook
|
|
useEffect(() => {
|
|
const { data: { subscription } } = supabase.auth.onAuthStateChange((event) => {
|
|
if (event === 'SIGNED_OUT' || event === 'TOKEN_REFRESHED') {
|
|
router.refresh()
|
|
}
|
|
})
|
|
return () => subscription.unsubscribe()
|
|
}, [])
|
|
```
|
|
|
|
---
|
|
|
|
### 2.8-2.12 Additional HIGH Issues
|
|
|
|
| Issue | Location | Description |
|
|
|-------|----------|-------------|
|
|
| No email notifications | - | Members not notified of approvals, assignments |
|
|
| Task recurrence not auto-resetting | Tasks | Recurring tasks don't auto-create new instances |
|
|
| Project task linking missing | Projects | Can't link tasks to projects |
|
|
| Area leads not editable | Ops | UI shows leads but no edit functionality |
|
|
| Knowledge base detail page missing | `/ops/[id]` | Links go to non-existent page |
|
|
|
|
---
|
|
|
|
## 3. MEDIUM Priority Issues
|
|
|
|
### 3.1 TypeScript Strict Mode Disabled
|
|
|
|
**Location**: `tsconfig.json`
|
|
|
|
**Issue**: Not using strict mode reduces type safety.
|
|
|
|
**Fix**: Enable strict mode and fix resulting errors.
|
|
|
|
---
|
|
|
|
### 3.2 No Test Coverage
|
|
|
|
**Issue**: Zero tests exist.
|
|
|
|
**Required**:
|
|
- Unit tests for all server actions (Vitest)
|
|
- Component tests (React Testing Library)
|
|
- E2E tests for critical flows (Playwright)
|
|
|
|
---
|
|
|
|
### 3.3 No Logging/Monitoring
|
|
|
|
**Issue**: No structured logging or error tracking.
|
|
|
|
**Fix**: Add Sentry, LogRocket, or similar.
|
|
|
|
---
|
|
|
|
### 3.4 Database Queries Not Optimized
|
|
|
|
**Issue**: Some pages make sequential queries that could be parallel.
|
|
|
|
**Example Fix**:
|
|
```typescript
|
|
// Before: Sequential
|
|
const { data: tasks } = await supabase.from('tasks').select('*')
|
|
const { data: projects } = await supabase.from('projects').select('*')
|
|
|
|
// After: Parallel
|
|
const [{ data: tasks }, { data: projects }] = await Promise.all([
|
|
supabase.from('tasks').select('*'),
|
|
supabase.from('projects').select('*'),
|
|
])
|
|
```
|
|
|
|
---
|
|
|
|
### 3.5-3.18 Additional MEDIUM Issues
|
|
|
|
| Issue | Description |
|
|
|-------|-------------|
|
|
| No pagination | Lists load all data, no infinite scroll |
|
|
| Search is client-only | No full-text search in database |
|
|
| No data export | Can import but not export |
|
|
| Mobile responsive issues | Some pages not fully mobile-optimized |
|
|
| No keyboard shortcuts | Missing accessibility features |
|
|
| Duplicate code in actions | Membership check repeated everywhere |
|
|
| No optimistic updates | UI waits for server response |
|
|
| Channel creation missing | Can't create new chat channels |
|
|
| Message editing/deletion | No edit/delete for messages |
|
|
| File attachments missing | No file upload in messages or KB |
|
|
| Member search in modals | Hard to find members in large spaces |
|
|
| No undo for destructive actions | Delete is immediate |
|
|
| Date handling inconsistent | Mix of string and Date objects |
|
|
| No timezone support | All times in server timezone |
|
|
|
|
---
|
|
|
|
## 4. LOW Priority Issues
|
|
|
|
| Issue | Description |
|
|
|-------|-------------|
|
|
| Console warnings | Some React key warnings |
|
|
| Unused code | Dead imports in some files |
|
|
| Inconsistent styling | Mix of Tailwind approaches |
|
|
| Missing meta tags | Some pages lack proper SEO |
|
|
| No favicon variants | Only basic favicon |
|
|
| No PWA support | Could work offline |
|
|
| No dark/light toggle | Theme locked to dark |
|
|
| No i18n | English only |
|
|
| No accessibility audit | WCAG compliance unknown |
|
|
| No performance profiling | Bundle size not optimized |
|
|
| No CI/CD pipeline | Manual deployments |
|
|
| No staging environment | Direct to production |
|
|
|
|
---
|
|
|
|
## 5. Security Checklist
|
|
|
|
| Check | Status | Notes |
|
|
|-------|--------|-------|
|
|
| RLS enabled on all tables | PASS | All 13 tables protected |
|
|
| Service role key protected | PARTIAL | Used in server actions, needs audit |
|
|
| Input validation | FAIL | No Zod schemas |
|
|
| Rate limiting | FAIL | Not implemented |
|
|
| CSRF protection | PARTIAL | Origin check needed |
|
|
| XSS prevention | PASS | React escapes by default |
|
|
| SQL injection | PASS | Supabase client parameterizes |
|
|
| Secrets encryption | FAIL | Plain text storage |
|
|
| Session management | PASS | Supabase handles |
|
|
| Password requirements | PASS | Min 8 chars enforced |
|
|
| Audit logging | PASS | activity_log table exists |
|
|
| Error message exposure | PARTIAL | Some errors too detailed |
|
|
|
|
---
|
|
|
|
## 6. Performance Checklist
|
|
|
|
| Check | Status | Notes |
|
|
|-------|--------|-------|
|
|
| Server Components | PASS | Pages are RSC |
|
|
| Code splitting | PASS | Next.js automatic |
|
|
| Image optimization | PASS | No large images |
|
|
| Database indexes | PARTIAL | Basic indexes only |
|
|
| Query optimization | PARTIAL | Some N+1 issues |
|
|
| Caching | PARTIAL | revalidatePath used |
|
|
| Bundle size | UNKNOWN | Not audited |
|
|
| Core Web Vitals | UNKNOWN | Not measured |
|
|
|
|
---
|
|
|
|
## 7. Recommended Action Plan
|
|
|
|
### Phase 1: Security (Week 1)
|
|
1. Add Zod validation to all server actions
|
|
2. Implement rate limiting with Upstash
|
|
3. Add secrets encryption
|
|
4. Audit admin client usage
|
|
5. Add CSRF token verification
|
|
|
|
### Phase 2: Critical Functionality (Week 2)
|
|
1. Implement social auth (GitHub, Google)
|
|
2. Build CSV import functionality
|
|
3. Add error boundaries
|
|
4. Create loading states
|
|
5. Fix session refresh
|
|
|
|
### Phase 3: Features (Week 3-4)
|
|
1. Add email notifications
|
|
2. Implement webhook endpoint
|
|
3. Build payment platform integrations
|
|
4. Add task recurrence automation
|
|
5. Create KB detail pages
|
|
|
|
### Phase 4: Quality (Week 5)
|
|
1. Write test suite (80%+ coverage target)
|
|
2. Add logging/monitoring
|
|
3. Performance optimization
|
|
4. Accessibility audit
|
|
5. Security penetration test
|
|
|
|
### Phase 5: Polish (Week 6)
|
|
1. Mobile optimization
|
|
2. Keyboard shortcuts
|
|
3. Export functionality
|
|
4. Documentation completion
|
|
5. CI/CD pipeline
|
|
|
|
---
|
|
|
|
## 8. Sign-Off Requirements
|
|
|
|
Before production launch, the following must be verified:
|
|
|
|
- [ ] All CRITICAL issues resolved
|
|
- [ ] All HIGH issues resolved or documented workarounds
|
|
- [ ] Security penetration test completed
|
|
- [ ] Load testing completed (target: 100 concurrent users)
|
|
- [ ] Backup and recovery tested
|
|
- [ ] Monitoring and alerting configured
|
|
- [ ] Documentation complete
|
|
- [ ] Support procedures documented
|
|
- [ ] Legal review of ToS/Privacy Policy
|
|
- [ ] GDPR/data handling compliance verified
|