Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening
Batch commit of the pending working tree on security/audit-fixes-2026-07. Major areas: - Outbound webhooks / Zapier: schema + signed delivery with retries, public v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain. - Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when Spaces is unconfigured instead of silently using ephemeral disk. - Integrations & features (concurrent work): accounting (QuickBooks/Xero), e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding, expanded legal pages. - DB migrations 0006–0009. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
969d5d4c8a
commit
c9968531e4
@@ -0,0 +1,33 @@
|
||||
-- NOTE: made idempotent (IF NOT EXISTS / guarded constraint) by hand.
|
||||
-- The admin tables/columns below were previously applied to some databases via
|
||||
-- `drizzle-kit push` without a migration file, so migration history had drifted.
|
||||
-- Guarding these statements lets 0001 provision a fresh database (creating the
|
||||
-- admin objects + app_settings) while safely no-op'ing on already-migrated DBs.
|
||||
CREATE TABLE IF NOT EXISTS "admin_audit_log" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"admin_id" text,
|
||||
"action" text NOT NULL,
|
||||
"target_user_id" text,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"ip_address" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "app_settings" (
|
||||
"key" text PRIMARY KEY NOT NULL,
|
||||
"value" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "session" ADD COLUMN IF NOT EXISTS "impersonated_by" text;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "role" text DEFAULT 'user';--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "banned" boolean DEFAULT false;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "ban_reason" text;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "ban_expires" timestamp;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'admin_audit_log_admin_id_user_id_fk'
|
||||
) THEN
|
||||
ALTER TABLE "admin_audit_log" ADD CONSTRAINT "admin_audit_log_admin_id_user_id_fk" FOREIGN KEY ("admin_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE "account_members" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"owner_id" text NOT NULL,
|
||||
"member_id" text,
|
||||
"email" text NOT NULL,
|
||||
"role" text DEFAULT 'member' NOT NULL,
|
||||
"status" text DEFAULT 'pending' NOT NULL,
|
||||
"invite_token" text DEFAULT gen_random_uuid()::text NOT NULL,
|
||||
"accepted_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "account_members_invite_token_unique" UNIQUE("invite_token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "profiles" ADD COLUMN "brand_name" text;--> statement-breakpoint
|
||||
ALTER TABLE "profiles" ADD COLUMN "brand_logo_url" text;--> statement-breakpoint
|
||||
ALTER TABLE "profiles" ADD COLUMN "brand_color" text;--> statement-breakpoint
|
||||
ALTER TABLE "profiles" ADD COLUMN "hide_powered_by" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "account_members" ADD CONSTRAINT "account_members_owner_id_profiles_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "account_members" ADD CONSTRAINT "account_members_member_id_profiles_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "api_keys" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"key_hash" text NOT NULL,
|
||||
"key_prefix" text NOT NULL,
|
||||
"last_used_at" timestamp with time zone,
|
||||
"revoked_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "api_keys_key_hash_unique" UNIQUE("key_hash")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "profiles" ADD COLUMN "calendar_token" text DEFAULT gen_random_uuid()::text;--> statement-breakpoint
|
||||
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_calendar_token_unique" UNIQUE("calendar_token");
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE "accounting_connections" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"access_token" text NOT NULL,
|
||||
"refresh_token" text NOT NULL,
|
||||
"expires_at" timestamp with time zone,
|
||||
"realm_id" text,
|
||||
"org_name" text,
|
||||
"status" text DEFAULT 'active' NOT NULL,
|
||||
"last_sync_at" timestamp with time zone,
|
||||
"last_error" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "accounting_connections" ADD CONSTRAINT "accounting_connections_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,38 @@
|
||||
CREATE TABLE "webhook_deliveries" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"endpoint_id" uuid NOT NULL,
|
||||
"event" text NOT NULL,
|
||||
"payload" jsonb NOT NULL,
|
||||
"status" text DEFAULT 'pending' NOT NULL,
|
||||
"attempts" integer DEFAULT 0 NOT NULL,
|
||||
"max_attempts" integer DEFAULT 5 NOT NULL,
|
||||
"next_attempt_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"response_status" integer,
|
||||
"response_body" text,
|
||||
"error" text,
|
||||
"delivered_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "webhook_endpoints" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"description" text,
|
||||
"events" text[] DEFAULT '{}'::text[] NOT NULL,
|
||||
"secret" text NOT NULL,
|
||||
"status" text DEFAULT 'active' NOT NULL,
|
||||
"source" text DEFAULT 'dashboard' NOT NULL,
|
||||
"last_success_at" timestamp with time zone,
|
||||
"last_error_at" timestamp with time zone,
|
||||
"last_error" text,
|
||||
"failure_count" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "webhook_deliveries" ADD CONSTRAINT "webhook_deliveries_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "webhook_deliveries" ADD CONSTRAINT "webhook_deliveries_endpoint_id_webhook_endpoints_id_fk" FOREIGN KEY ("endpoint_id") REFERENCES "public"."webhook_endpoints"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "webhook_endpoints" ADD CONSTRAINT "webhook_endpoints_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,19 @@
|
||||
CREATE TABLE "signature_requests" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"lease_id" uuid,
|
||||
"provider" text NOT NULL,
|
||||
"external_id" text,
|
||||
"status" text DEFAULT 'sent' NOT NULL,
|
||||
"signer_email" text NOT NULL,
|
||||
"signer_name" text,
|
||||
"document_name" text,
|
||||
"signed_document_url" text,
|
||||
"last_error" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"completed_at" timestamp with time zone,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "signature_requests" ADD CONSTRAINT "signature_requests_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "signature_requests" ADD CONSTRAINT "signature_requests_lease_id_leases_id_fk" FOREIGN KEY ("lease_id") REFERENCES "public"."leases"("id") ON DELETE set null ON UPDATE no action;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "properties" ADD COLUMN "latitude" double precision;--> statement-breakpoint
|
||||
ALTER TABLE "properties" ADD COLUMN "longitude" double precision;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "profiles" ADD COLUMN "paypal_subscription_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "profiles" ADD COLUMN "billing_provider" text;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,69 @@
|
||||
"when": 1782223467789,
|
||||
"tag": "0000_next_red_skull",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1782928287616,
|
||||
"tag": "0001_furry_christian_walker",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1782935722973,
|
||||
"tag": "0002_quiet_freak",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1782975002382,
|
||||
"tag": "0003_api_keys",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1782978054678,
|
||||
"tag": "0004_yellow_switch",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1782980416142,
|
||||
"tag": "0005_large_black_queen",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1782980797596,
|
||||
"tag": "0006_webhooks",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1782981198010,
|
||||
"tag": "0007_chubby_sinister_six",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "7",
|
||||
"when": 1782992530365,
|
||||
"tag": "0008_dazzling_white_tiger",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1782994066547,
|
||||
"tag": "0009_amusing_blackheart",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user