Public Access
feat: implement setup wizard for initial configuration and user registration
This commit is contained in:
1 parent
f7b93f8612
commit
ad777ef1c3
9 files changed
+576
-8
No files matched your search
@@ -0,0 +1,122 @@
|
||||
import { Router } from 'express'
|
||||
import bcrypt from 'bcrypt'
|
||||
import { getDb } from '../db/database.js'
|
||||
import { signToken } from '../utils/tokens.js'
|
||||
|
||||
const router = Router()
|
||||
const BCRYPT_ROUNDS = 12
|
||||
|
||||
function needsSetup(): boolean {
|
||||
const db = getDb()
|
||||
const row = db.prepare('SELECT COUNT(*) AS cnt FROM users').get() as { cnt: number }
|
||||
return row.cnt === 0
|
||||
}
|
||||
|
||||
// ─── Check Setup Status ──────────────────────────────────
|
||||
router.get('/status', (_req, res) => {
|
||||
try {
|
||||
res.json({ needsSetup: needsSetup() })
|
||||
} catch (err) {
|
||||
console.error('Setup status error:', err)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
})
|
||||
|
||||
// ─── Perform Initial Setup ───────────────────────────────
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
if (!needsSetup()) {
|
||||
res.status(403).json({ error: 'Setup has already been completed' })
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
displayName,
|
||||
smtpHost,
|
||||
smtpPort,
|
||||
smtpUser,
|
||||
smtpPass,
|
||||
smtpFrom,
|
||||
smtpSecure,
|
||||
appUrl,
|
||||
} = req.body
|
||||
|
||||
// Validate admin credentials
|
||||
if (!email || !password || !displayName) {
|
||||
res.status(400).json({ error: 'Email, password, and display name are required' })
|
||||
return
|
||||
}
|
||||
if (typeof email !== 'string' || !email.includes('@') || email.length > 255) {
|
||||
res.status(400).json({ error: 'Invalid email address' })
|
||||
return
|
||||
}
|
||||
if (typeof password !== 'string' || password.length < 8) {
|
||||
res.status(400).json({ error: 'Password must be at least 8 characters' })
|
||||
return
|
||||
}
|
||||
if (typeof displayName !== 'string' || displayName.trim().length < 1 || displayName.length > 100) {
|
||||
res.status(400).json({ error: 'Display name is required (max 100 characters)' })
|
||||
return
|
||||
}
|
||||
|
||||
const db = getDb()
|
||||
|
||||
// Create admin user (auto-verified since no SMTP yet)
|
||||
const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS)
|
||||
const result = db.prepare(`
|
||||
INSERT INTO users (email, password_hash, display_name, role, email_verified)
|
||||
VALUES (?, ?, ?, 'admin', 1)
|
||||
`).run(email.toLowerCase().trim(), passwordHash, displayName.trim())
|
||||
|
||||
const userId = result.lastInsertRowid as number
|
||||
|
||||
// Save settings
|
||||
const settings: Record<string, string> = {
|
||||
app_url: typeof appUrl === 'string' && appUrl.trim() ? appUrl.trim() : 'http://localhost:5173',
|
||||
}
|
||||
|
||||
if (typeof smtpHost === 'string' && smtpHost.trim()) {
|
||||
settings.smtp_host = smtpHost.trim()
|
||||
settings.smtp_port = String(Number(smtpPort) || 587)
|
||||
settings.smtp_user = typeof smtpUser === 'string' ? smtpUser : ''
|
||||
settings.smtp_pass = typeof smtpPass === 'string' ? smtpPass : ''
|
||||
settings.smtp_from = typeof smtpFrom === 'string' && smtpFrom.trim() ? smtpFrom.trim() : 'BrainWave <noreply@brainwave.local>'
|
||||
settings.smtp_secure = typeof smtpSecure === 'string' ? smtpSecure : 'auto'
|
||||
}
|
||||
|
||||
const upsert = db.prepare(`
|
||||
INSERT INTO site_settings (key, value) VALUES (?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = unixepoch()
|
||||
`)
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
upsert.run(key, value)
|
||||
}
|
||||
|
||||
// Generate JWT and log admin in immediately
|
||||
const token = signToken({
|
||||
userId,
|
||||
email: email.toLowerCase().trim(),
|
||||
displayName: displayName.trim(),
|
||||
role: 'admin',
|
||||
})
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Setup completed successfully',
|
||||
token,
|
||||
user: {
|
||||
id: userId,
|
||||
email: email.toLowerCase().trim(),
|
||||
displayName: displayName.trim(),
|
||||
role: 'admin',
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Setup error:', err)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
export { needsSetup }
|
||||
Reference in new issue
Block a user