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
+39
-32
@@ -1,41 +1,41 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const propertySchema = z.object({
|
||||
name: z.string().min(1, "Property name is required"),
|
||||
address_line1: z.string().min(1, "Address is required"),
|
||||
address_line2: z.string().optional(),
|
||||
city: z.string().min(1, "City is required"),
|
||||
state: z.string().optional(),
|
||||
postal_code: z.string().optional(),
|
||||
country: z.string().default("US"),
|
||||
name: z.string().min(1, "Property name is required").max(200),
|
||||
address_line1: z.string().min(1, "Address is required").max(300),
|
||||
address_line2: z.string().max(300).optional(),
|
||||
city: z.string().min(1, "City is required").max(120),
|
||||
state: z.string().max(120).optional(),
|
||||
postal_code: z.string().max(20).optional(),
|
||||
country: z.string().max(120).default("US"),
|
||||
property_type: z.enum(["residential", "commercial", "mixed"]).default("residential"),
|
||||
total_units: z.number().int().min(1).default(1),
|
||||
notes: z.string().optional(),
|
||||
notes: z.string().max(5000).optional(),
|
||||
image_url: z.string().url().nullable().optional(),
|
||||
})
|
||||
|
||||
export const unitSchema = z.object({
|
||||
property_id: z.string().uuid(),
|
||||
unit_number: z.string().min(1, "Unit number is required"),
|
||||
unit_number: z.string().min(1, "Unit number is required").max(50),
|
||||
bedrooms: z.number().int().min(0).default(1),
|
||||
bathrooms: z.number().min(0).default(1),
|
||||
sq_ft: z.number().int().positive().optional(),
|
||||
rent_amount: z.number().positive("Rent amount must be positive"),
|
||||
status: z.enum(["vacant", "occupied", "maintenance", "unavailable"]).default("vacant"),
|
||||
notes: z.string().optional(),
|
||||
notes: z.string().max(5000).optional(),
|
||||
})
|
||||
|
||||
export const tenantSchema = z.object({
|
||||
property_id: z.string().uuid(),
|
||||
unit_id: z.string().uuid().optional(),
|
||||
first_name: z.string().min(1, "First name is required"),
|
||||
last_name: z.string().min(1, "Last name is required"),
|
||||
email: z.string().email("Invalid email").optional().or(z.literal("")),
|
||||
phone: z.string().optional(),
|
||||
emergency_contact_name: z.string().optional(),
|
||||
emergency_contact_phone: z.string().optional(),
|
||||
first_name: z.string().min(1, "First name is required").max(100),
|
||||
last_name: z.string().min(1, "Last name is required").max(100),
|
||||
email: z.string().email("Invalid email").max(254).optional().or(z.literal("")),
|
||||
phone: z.string().max(40).optional(),
|
||||
emergency_contact_name: z.string().max(200).optional(),
|
||||
emergency_contact_phone: z.string().max(40).optional(),
|
||||
move_in_date: z.string().optional(),
|
||||
notes: z.string().optional(),
|
||||
notes: z.string().max(5000).optional(),
|
||||
})
|
||||
|
||||
export const rentPaymentSchema = z.object({
|
||||
@@ -46,19 +46,19 @@ export const rentPaymentSchema = z.object({
|
||||
due_date: z.string().min(1, "Due date is required"),
|
||||
paid_date: z.string().optional(),
|
||||
status: z.enum(["pending", "paid", "overdue", "partial", "waived"]).default("pending"),
|
||||
payment_method: z.string().optional(),
|
||||
notes: z.string().optional(),
|
||||
payment_method: z.string().max(100).optional(),
|
||||
notes: z.string().max(5000).optional(),
|
||||
})
|
||||
|
||||
export const maintenanceSchema = z.object({
|
||||
property_id: z.string().uuid(),
|
||||
unit_id: z.string().uuid().optional(),
|
||||
tenant_id: z.string().uuid().optional(),
|
||||
title: z.string().min(1, "Title is required"),
|
||||
description: z.string().min(1, "Description is required"),
|
||||
title: z.string().min(1, "Title is required").max(200),
|
||||
description: z.string().min(1, "Description is required").max(5000),
|
||||
category: z.enum(["plumbing", "electrical", "hvac", "appliance", "structural", "pest", "general"]).default("general"),
|
||||
priority: z.enum(["low", "medium", "high", "emergency"]).default("medium"),
|
||||
assigned_to: z.string().optional(),
|
||||
assigned_to: z.string().max(200).optional(),
|
||||
estimated_cost: z.number().positive().optional(),
|
||||
})
|
||||
|
||||
@@ -72,20 +72,20 @@ export const leaseSchema = z.object({
|
||||
security_deposit: z.number().positive().optional(),
|
||||
lease_type: z.enum(["fixed", "month_to_month"]).default("fixed"),
|
||||
auto_renew: z.boolean().default(false),
|
||||
notes: z.string().optional(),
|
||||
notes: z.string().max(5000).optional(),
|
||||
})
|
||||
|
||||
export const expenseSchema = z.object({
|
||||
property_id: z.string().uuid(),
|
||||
unit_id: z.string().uuid().optional(),
|
||||
category: z.enum(["repairs", "utilities", "insurance", "mortgage", "taxes", "management", "supplies", "other"]),
|
||||
description: z.string().min(1, "Description is required"),
|
||||
description: z.string().min(1, "Description is required").max(500),
|
||||
amount: z.number().positive("Amount must be positive"),
|
||||
expense_date: z.string().min(1, "Date is required"),
|
||||
vendor: z.string().optional(),
|
||||
vendor: z.string().max(200).optional(),
|
||||
is_recurring: z.boolean().default(false),
|
||||
recurrence: z.enum(["monthly", "quarterly", "yearly"]).optional(),
|
||||
notes: z.string().optional(),
|
||||
notes: z.string().max(5000).optional(),
|
||||
})
|
||||
|
||||
export type PropertyFormValues = z.infer<typeof propertySchema>
|
||||
@@ -95,11 +95,11 @@ export type RentPaymentFormValues = z.infer<typeof rentPaymentSchema>
|
||||
export type MaintenanceFormValues = z.infer<typeof maintenanceSchema>
|
||||
export type LeaseFormValues = z.infer<typeof leaseSchema>
|
||||
export const vendorSchema = z.object({
|
||||
name: z.string().min(1, "Vendor name is required"),
|
||||
trade: z.string().optional(),
|
||||
phone: z.string().optional(),
|
||||
email: z.string().email("Invalid email").optional().or(z.literal("")),
|
||||
notes: z.string().optional(),
|
||||
name: z.string().min(1, "Vendor name is required").max(200),
|
||||
trade: z.string().max(120).optional(),
|
||||
phone: z.string().max(40).optional(),
|
||||
email: z.string().email("Invalid email").max(254).optional().or(z.literal("")),
|
||||
notes: z.string().max(5000).optional(),
|
||||
property_id: z.string().uuid().optional().or(z.literal("")),
|
||||
})
|
||||
|
||||
@@ -108,13 +108,20 @@ export const inspectionSchema = z.object({
|
||||
unit_id: z.string().uuid().optional().or(z.literal("")),
|
||||
type: z.enum(["move_in", "move_out", "routine"]),
|
||||
date: z.string().min(1, "Date is required"),
|
||||
notes: z.string().optional(),
|
||||
notes: z.string().max(5000).optional(),
|
||||
})
|
||||
|
||||
export const paymentLinkSchema = z.object({
|
||||
payment_id: z.string().uuid("Valid payment ID is required"),
|
||||
})
|
||||
|
||||
export const followUpRuleSchema = z.object({
|
||||
type: z.enum(["overdue_rent", "maintenance_stale", "lease_renewal", "vacant_unit"]),
|
||||
name: z.string().min(1, "Name is required").max(200),
|
||||
trigger_days: z.number().int().min(0).max(3650).default(3),
|
||||
message_template: z.string().max(5000).optional(),
|
||||
})
|
||||
|
||||
export type ExpenseFormValues = z.infer<typeof expenseSchema>
|
||||
export type VendorFormValues = z.infer<typeof vendorSchema>
|
||||
export type InspectionFormValues = z.infer<typeof inspectionSchema>
|
||||
|
||||
Reference in New Issue
Block a user