- POST /auth/forgot-password with welcome/reset email templates - POST /auth/reset-password with Zod validation, 4-hour tokens - Per-email rate limiting (3/hr) via Valkey, no user enumeration - Login page "Forgot password?" toggle with inline form - /reset-password page for setting new password from email link - Initial user seed sends welcome email instead of requiring password - CLI script for force-resetting passwords via kubectl exec - APP_URL env var in chart, removed INITIAL_USER_PASSWORD Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Force-reset a user's password from the command line.
|
|
*
|
|
* Usage:
|
|
* bun run packages/backend/src/scripts/reset-password.ts <email> <new-password>
|
|
*
|
|
* From a customer pod:
|
|
* kubectl exec -n customer-tvs deploy/customer-tvs-backend -- \
|
|
* bun run src/scripts/reset-password.ts user@example.com NewPassword123!
|
|
*/
|
|
import postgres from 'postgres'
|
|
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
import { eq } from 'drizzle-orm'
|
|
import bcrypt from 'bcryptjs'
|
|
import { users } from '../db/schema/users.js'
|
|
|
|
const [email, newPassword] = process.argv.slice(2)
|
|
|
|
if (!email || !newPassword) {
|
|
console.error('Usage: bun run reset-password.ts <email> <new-password>')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (newPassword.length < 12) {
|
|
console.error('Error: Password must be at least 12 characters')
|
|
process.exit(1)
|
|
}
|
|
|
|
const databaseUrl = process.env.DATABASE_URL
|
|
if (!databaseUrl) {
|
|
console.error('Error: DATABASE_URL is not set')
|
|
process.exit(1)
|
|
}
|
|
|
|
const sql = postgres(databaseUrl)
|
|
const db = drizzle(sql)
|
|
|
|
const [user] = await db.select({ id: users.id, email: users.email }).from(users).where(eq(users.email, email)).limit(1)
|
|
|
|
if (!user) {
|
|
console.error(`Error: No user found with email "${email}"`)
|
|
await sql.end()
|
|
process.exit(1)
|
|
}
|
|
|
|
const hash = await bcrypt.hash(newPassword, 10)
|
|
await db.update(users).set({ passwordHash: hash, updatedAt: new Date() }).where(eq(users.id, user.id))
|
|
|
|
console.log(`Password reset for ${email} (user ${user.id})`)
|
|
await sql.end()
|