Security hardening from 2026-07-01 audit
- Exclude supabase/ from Docker build context (leaked service_role key file) - /api/files: exact per-user namespace match + reject path traversal; storage resolveKey rejects ".."/"." segments (fixes cross-user file read) - Add ownsProperty/Unit/Tenant checks to tenants, maintenance (landlord path), and documents (JSON branch, now field-whitelisted) create handlers - Escape user data in follow-up + payment-link emails (reuse escapeHtml) - Neutralize CSV formula injection in toCsv + export routes - Tighter sign-in rate limit (10/min); env-gated email verification + sender - Per-request nonce CSP; drop script-src 'unsafe-inline' (styles unchanged) - Add input length bounds; validate follow-ups POST body Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
857b9a7811
commit
969d5d4c8a
+40
-3
@@ -21,9 +21,10 @@ export const auth = betterAuth({
|
||||
}),
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
// Login works immediately; flip to true once verification email is desired.
|
||||
// Recommended for production: set requireEmailVerification to true.
|
||||
requireEmailVerification: false,
|
||||
// Env-gated so production can require a verified email without breaking
|
||||
// local dev (where RESEND is typically unconfigured). Set
|
||||
// REQUIRE_EMAIL_VERIFICATION=true in production to enforce.
|
||||
requireEmailVerification: process.env.REQUIRE_EMAIL_VERIFICATION === "true",
|
||||
minPasswordLength: 8,
|
||||
sendResetPassword: async ({ user: u, url }) => {
|
||||
await sendEmail({
|
||||
@@ -33,6 +34,18 @@ export const auth = betterAuth({
|
||||
})
|
||||
},
|
||||
},
|
||||
// Send a verification email on sign-up. Enforcement of verified-email login
|
||||
// is gated by REQUIRE_EMAIL_VERIFICATION (see emailAndPassword above).
|
||||
emailVerification: {
|
||||
sendOnSignUp: true,
|
||||
sendVerificationEmail: async ({ user: u, url }) => {
|
||||
await sendEmail({
|
||||
to: u.email,
|
||||
subject: "Verify your email — Property Management Network",
|
||||
html: verifyEmailHtml(url),
|
||||
})
|
||||
},
|
||||
},
|
||||
socialProviders: {
|
||||
google: {
|
||||
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
||||
@@ -44,6 +57,11 @@ export const auth = betterAuth({
|
||||
enabled: true,
|
||||
window: 60, // seconds
|
||||
max: 20, // requests per window per IP for auth endpoints
|
||||
customRules: {
|
||||
// Tighter limit on the password sign-in endpoint to slow credential
|
||||
// stuffing / brute-force attempts.
|
||||
"/sign-in/email": { window: 60, max: 10 },
|
||||
},
|
||||
},
|
||||
// Auto-create the app `profiles` row whenever Better Auth creates a user
|
||||
// (replaces the old `handle_new_user` Postgres trigger).
|
||||
@@ -88,3 +106,22 @@ function resetPasswordHtml(url: string) {
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
function verifyEmailHtml(url: string) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body style="font-family: sans-serif; background: #09090b; color: #fff; padding: 40px 20px; max-width: 560px; margin: 0 auto;">
|
||||
<div style="background: #16161f; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 32px;">
|
||||
<h1 style="font-size: 20px; margin: 0 0 8px; color: #fff;">Verify your email</h1>
|
||||
<p style="color: rgba(255,255,255,0.6); margin: 0 0 24px;">
|
||||
Confirm your email address to finish setting up your account. If you didn't create an account, you can ignore this email.
|
||||
</p>
|
||||
<a href="${url}" style="display: inline-block; background: #6366f1; color: #fff; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600;">
|
||||
Verify Email
|
||||
</a>
|
||||
<p style="color: rgba(255,255,255,0.4); font-size: 12px; margin: 24px 0 0;">Property Management Network</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user