Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
30 lines
1.3 KiB
PL/PgSQL
30 lines
1.3 KiB
PL/PgSQL
-- 007: first-party auth (email + password) and membership invitations.
|
|
-- Passwords live in their own table (never in users, never in audit diffs);
|
|
-- hashes are scrypt with a per-user salt, produced by apps/api/src/auth/password.ts.
|
|
CREATE TABLE user_passwords (
|
|
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
password_hash text NOT NULL,
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Invited members exist as memberships before they first sign in.
|
|
ALTER TABLE memberships
|
|
ADD COLUMN status text NOT NULL DEFAULT 'active'
|
|
CHECK (status IN ('active', 'invited'));
|
|
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON user_passwords TO sp_app;
|
|
|
|
-- Login happens before any tenant context exists, and memberships is FORCE
|
|
-- RLS. This narrow SECURITY DEFINER lookup returns only the caller-supplied
|
|
-- user's own memberships so login can pick a workspace without weakening RLS.
|
|
CREATE FUNCTION auth_user_memberships(p_user_id uuid)
|
|
RETURNS TABLE(workspace_id uuid, role text, status text, created_at timestamptz)
|
|
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public AS $$
|
|
SELECT m.workspace_id, m.role, m.status, m.created_at
|
|
FROM memberships m
|
|
WHERE m.user_id = p_user_id
|
|
ORDER BY m.created_at ASC
|
|
$$;
|
|
|
|
REVOKE ALL ON FUNCTION auth_user_memberships(uuid) FROM PUBLIC;
|
|
GRANT EXECUTE ON FUNCTION auth_user_memberships(uuid) TO sp_app;
|