From 637fb8d934859fba9b9737d069228d4e10d057a0 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 7 Aug 2026 04:12:42 +0000 Subject: [PATCH] Modified by www.SourceFiles.app --- .../20260726160000_mail_accounts.sql | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 supabase/migrations/20260726160000_mail_accounts.sql diff --git a/supabase/migrations/20260726160000_mail_accounts.sql b/supabase/migrations/20260726160000_mail_accounts.sql new file mode 100644 index 0000000..b990c1a --- /dev/null +++ b/supabase/migrations/20260726160000_mail_accounts.sql @@ -0,0 +1,63 @@ +-- Embedded mail: one shared IMAP/SMTP server configured by admins, with a +-- per-user mailbox login. +-- +-- Threat model drives the shape here. A mailbox password grants full read and +-- send access to someone's email, so: +-- +-- * user_mailboxes has RLS enabled and NO policies at all. Nothing reachable +-- from a browser can read it — not even the mailbox owner, and not admins. +-- Every access goes through server functions using the service role, the +-- same pattern intake_tokens uses. +-- * The password is never stored as plaintext. It is sealed with AES-256-GCM +-- using MAIL_CRED_KEY from .env.secret, so a database dump alone is not +-- enough to open anyone's mail. +-- * The IMAP/SMTP *host* settings are not secret, so admins may read those +-- directly to populate the setup form. + +CREATE TABLE IF NOT EXISTS public.mail_server_settings ( + -- Single-row table: the school has one mail server. + id BOOLEAN PRIMARY KEY DEFAULT TRUE CHECK (id = TRUE), + imap_host TEXT NOT NULL, + imap_port INT NOT NULL DEFAULT 993, + imap_secure BOOLEAN NOT NULL DEFAULT TRUE, -- implicit TLS on connect + smtp_host TEXT NOT NULL, + smtp_port INT NOT NULL DEFAULT 587, + smtp_secure BOOLEAN NOT NULL DEFAULT FALSE, -- false = STARTTLS upgrade + updated_by UUID REFERENCES auth.users(id), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +GRANT SELECT ON public.mail_server_settings TO authenticated; +GRANT ALL ON public.mail_server_settings TO service_role; +ALTER TABLE public.mail_server_settings ENABLE ROW LEVEL SECURITY; + +-- Host/port are operational config, not secrets. Admins read them to fill in +-- the setup form; writes go through a server function so they can be validated +-- against a live login first. +CREATE POLICY "mail settings admin read" ON public.mail_server_settings FOR SELECT TO authenticated + USING (public.current_user_has_role('admin')); + +CREATE TABLE IF NOT EXISTS public.user_mailboxes ( + user_id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + email TEXT NOT NULL, + -- AES-256-GCM sealed mailbox password. Never returned to any client. + secret_ciphertext TEXT NOT NULL, + secret_iv TEXT NOT NULL, + secret_tag TEXT NOT NULL, + last_verified_at TIMESTAMPTZ, + created_by UUID REFERENCES auth.users(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +CREATE INDEX IF NOT EXISTS idx_user_mailboxes_email ON public.user_mailboxes(email); +GRANT ALL ON public.user_mailboxes TO service_role; +ALTER TABLE public.user_mailboxes ENABLE ROW LEVEL SECURITY; +-- Deliberately no policies and no grant to `authenticated`: credentials must +-- never be reachable from the browser under any role. + +DROP TRIGGER IF EXISTS trg_mail_settings_upd ON public.mail_server_settings; +CREATE TRIGGER trg_mail_settings_upd BEFORE UPDATE ON public.mail_server_settings + FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); + +DROP TRIGGER IF EXISTS trg_user_mailboxes_upd ON public.user_mailboxes; +CREATE TRIGGER trg_user_mailboxes_upd BEFORE UPDATE ON public.user_mailboxes + FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();