feat: auth system (middleware + login) + remove .env from git + fix hardcoded token

CRITICAL SECURITY FIXES:
- src/middleware.ts: protects all routes except public ones (health, license check, seed-prod)
  Redirects unauthenticated users to /login, returns 401 for API routes
- src/app/login/page.tsx: login page with password input
- src/app/api/auth/login: HMAC-signed session cookie (7-day expiry)
- src/app/api/auth/logout: clears session cookie
- Top bar: Abmelden (logout) button now functional
- .env removed from git tracking (secrets no longer in repo)
- seed-prod: hardcoded admin token replaced with env MCP_API_TOKEN or random UUID
- ADMIN_PASSWORD env var required for login (set in Dokploy env)

Public routes (no auth needed):
  /api/health, /api/aos/seed-prod, /api/aos/licenses/check, /api/aos/licenses/validate
  /api/v1/* (token-auth'd), /api/mcp (token-auth'd), /login
This commit is contained in:
Z User committed 2026-10-28 22:38:39 +00:00
1 parent e7185103f5
commit 17ceefe32a
8 files changed
+277 -100

No files matched your search

+6 -4
View File
@@ -206,18 +206,20 @@ export async function POST(req: Request) {
const existingTokens = await db.apiToken.count()
if (existingTokens === 0) {
const adminScopes = ['mcp:read', 'mcp:write', 'crm.company:read', 'crm.deal:read', 'finance.invoice:read', 'leads:read', 'leads:write', 'support:read', 'support:write', 'licenses.license:read', 'licenses.license:write']
const token = 'aos_admin_seed_token_for_claude_code_2026'
const hash = crypto.createHash('sha256').update(token).digest('hex')
// Use MCP_API_TOKEN env var if set, otherwise generate a random one
const tokenValue = process.env.MCP_API_TOKEN || ('aos_' + crypto.randomUUID())
const hash = crypto.createHash('sha256').update(tokenValue).digest('hex')
await db.apiToken.create({
data: {
name: 'Claude Code Admin (seeded)',
name: 'MCP Admin Token (seeded)',
tokenHash: hash,
tokenPrefix: token.slice(0, 12) + '…',
tokenPrefix: tokenValue.slice(0, 12) + '…',
scopes: adminScopes.join(','),
expiresAt: null,
},
})
stats.apiTokens = 1
stats.mcpToken = tokenValue === process.env.MCP_API_TOKEN ? '(from env)' : tokenValue
} else {
stats.apiTokens = 0
}