managarten/services/mana-auth/sql/004_spaces.sql
Till JS 698ffe797c feat(spaces): add spaces pg schema — credentials + module_permissions
Groundwork for server-side Space extensions that must NOT live in Dexie:
  - spaces.credentials         — per-space OAuth tokens, API keys, SMTP
                                 configs. Access tokens are stored
                                 encrypted at rest with the service KEK.
  - spaces.module_permissions  — role × module read/write/admin overrides
                                 on top of the SPACE_MODULE_ALLOWLIST
                                 defaults.

Both tables FK to auth.organizations with ON DELETE CASCADE so deleting
a space drops its credentials and permission overrides automatically.

RLS is intentionally deferred — enabling it now would lock out services
that don't yet pass space context. A follow-up migration turns it on
after mana-api speaks the Spaces protocol end-to-end.

To apply locally: bun run db:push in services/mana-auth, or psql -f
sql/004_spaces.sql against the mana_platform DB.

No runtime code reads these tables yet — they're the scaffolding that
Task-8 (mana-sync) and the eventual social-relay/clubs modules will
consume.

Plan: docs/plans/spaces-foundation.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:13:33 +02:00

70 lines
3.6 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Migration 004: Spaces schema
--
-- Adds the `spaces` schema with two server-side tables that extend Better
-- Auth organizations for our multi-tenancy model. See
-- docs/plans/spaces-foundation.md for the full RFC, and the Drizzle
-- definitions at src/db/schema/spaces.ts.
--
-- Why a separate schema:
-- - Keeps auth tables focused on identity, not domain extensions
-- - Lets us grant narrower RLS policies per schema
-- - Mirrors the pgSchema-per-concern pattern used across mana_platform
--
-- Idempotent: re-running on a partially-migrated DB is safe.
-- ─── Schema ──────────────────────────────────────────────────────
CREATE SCHEMA IF NOT EXISTS spaces;
-- ─── credentials ────────────────────────────────────────────────
-- Per-space external credentials: OAuth tokens, API keys, SMTP configs.
-- NEVER stored client-side — these are server-held secrets, wrapped with
-- the service-wide KEK (same mechanism as auth.encryption_vaults).
CREATE TABLE IF NOT EXISTS spaces.credentials (
space_id TEXT NOT NULL,
provider TEXT NOT NULL,
access_token_encrypted TEXT NOT NULL,
refresh_token_encrypted TEXT,
expires_at TIMESTAMPTZ,
scopes TEXT[],
provider_account_id TEXT,
metadata_json TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (space_id, provider),
CONSTRAINT space_credentials_space_fk
FOREIGN KEY (space_id) REFERENCES auth.organizations (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS space_credentials_space_idx
ON spaces.credentials (space_id);
-- ─── module_permissions ─────────────────────────────────────────
-- Role × module permission matrix. If no row exists for a given
-- (space, role, module) tuple, the default is derived from SPACE_MODULE_ALLOWLIST
-- plus role-tier fallback (owner > admin > member). Rows here are
-- explicit overrides — typically written when a space owner customises
-- the default permissions for a custom role.
CREATE TABLE IF NOT EXISTS spaces.module_permissions (
space_id TEXT NOT NULL,
role TEXT NOT NULL,
module_id TEXT NOT NULL,
can_read BOOLEAN NOT NULL DEFAULT TRUE,
can_write BOOLEAN NOT NULL DEFAULT FALSE,
can_admin BOOLEAN NOT NULL DEFAULT FALSE,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (space_id, role, module_id),
CONSTRAINT space_module_permissions_space_fk
FOREIGN KEY (space_id) REFERENCES auth.organizations (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS space_module_permissions_space_module_idx
ON spaces.module_permissions (space_id, module_id);
-- ─── RLS ─────────────────────────────────────────────────────────
-- Defer enabling RLS until the rest of the app is scope-aware. Turning
-- it on now would lock out services that don't yet pass the space
-- context. Re-enable in a follow-up migration once mana-api speaks the
-- Spaces protocol end-to-end.
--
-- ALTER TABLE spaces.credentials ENABLE ROW LEVEL SECURITY;
-- ALTER TABLE spaces.module_permissions ENABLE ROW LEVEL SECURITY;