feat: auth, persistence, and AI integration
- Cookie-based auth via backend proxy (httpOnly JWTs) - Supabase Postgres persistence for sessions/messages/profiles + RLS - Fix cross-user session leak (token cache keyed by full token, not 50-char prefix) - Fix missing table grants (42501) via migration; auto-provision profiles on user creation - Chat validation, ownership checks, rate limiting, /api/chat/sessions route ordering - Frontend auth-state reset + credentials include - OpenRouter AI provider (OpenAI-compatible base URL, reasoning disabled) - Tests: chatValidation, appState
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
-- Production-like user control: application-level user table (public.profiles)
|
||||
-- auto-provisioned from auth.users, with admin-managed control fields.
|
||||
|
||||
-- 1. Control columns (inherit existing table-wide grants automatically)
|
||||
ALTER TABLE public.profiles
|
||||
ADD COLUMN IF NOT EXISTS role text NOT NULL DEFAULT 'user',
|
||||
ADD COLUMN IF NOT EXISTS is_banned boolean NOT NULL DEFAULT false;
|
||||
|
||||
-- 2. Backfill profiles for existing auth users (e.g. the current test accounts)
|
||||
INSERT INTO public.profiles (id, email)
|
||||
SELECT id, email FROM auth.users
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- 3. Auto-provision a profile row whenever an auth user is created (signup/admin)
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.profiles (id, email)
|
||||
VALUES (new.id, new.email)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
RETURN new;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 4. Keep profile email in sync with auth.users email changes
|
||||
CREATE OR REPLACE FUNCTION public.handle_user_email_change()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
BEGIN
|
||||
IF new.email IS DISTINCT FROM old.email THEN
|
||||
UPDATE public.profiles SET email = new.email WHERE id = new.id;
|
||||
END IF;
|
||||
RETURN new;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
||||
CREATE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
||||
|
||||
DROP TRIGGER IF EXISTS on_auth_user_email_change ON auth.users;
|
||||
CREATE TRIGGER on_auth_user_email_change
|
||||
AFTER UPDATE OF email ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_user_email_change();
|
||||
|
||||
-- 5. Users may update their own profile, but must NOT be able to escalate
|
||||
-- their own role or unban themselves.
|
||||
-- NOTE: a column-level REVOKE does NOT override a broad TABLE-level grant.
|
||||
-- The earlier grant migration gave INSERT,UPDATE,DELETE on profiles to
|
||||
-- anon/authenticated (table-wide => every column). So undo the table-level
|
||||
-- write grants for user-facing roles, then re-grant UPDATE only on the
|
||||
-- editable columns. Profiles are created via the trigger, so users need no
|
||||
-- INSERT or DELETE. service_role (backend/admin) keeps full access and
|
||||
-- bypasses RLS.
|
||||
REVOKE INSERT, UPDATE, DELETE ON public.profiles FROM authenticated;
|
||||
REVOKE INSERT, UPDATE, DELETE ON public.profiles FROM anon;
|
||||
|
||||
GRANT SELECT ON public.profiles TO anon, authenticated;
|
||||
GRANT UPDATE (display_name, avatar_url) ON public.profiles TO authenticated;
|
||||
Reference in New Issue
Block a user