Files
HealthLog/prisma/migrations/0206_mcp_phase3_oauth_connection/migration.sql
T
Marc-André Bombeck fb986bc09d merge: bring the MCP server onto the v1.23 base
Combine the remote MCP server (stdio + remote /mcp + OAuth 2.1 + read
tools + deep-value reads + a confirmed, audience-bound write surface)
with the v1.23 account-security line (MFA, known/trusted devices,
free-text note encryption).

- schema: keep every v1.23 User relation (MFA, known/trusted devices)
  alongside the McpOAuthConnection relation; the MeasurementSource enum
  carries the MCP value.
- api-handler: the MCP audience guard (a token scoped to
  health:read/health:write is bound to /mcp and refused a REST write)
  and the step-up MFA helpers coexist.
- maintenance queues: register the MCP token-cleanup sweep next to the
  note-encryption backfill and key-rotation queues; shift it to 04:00
  so the two daily discovery ticks do not collide.
- migrations: renumber the MCP migrations to 0206/0207 so they follow
  the v1.23 chain (0199-0205).
- MCP mood writes route the note through the v1.23 at-rest cipher.
2026-06-28 00:52:04 +02:00

42 lines
2.1 KiB
SQL

-- MCP Phase 3 security fix (H2) — revocable, theft-detectable refresh chains.
--
-- The remote MCP OAuth bridge minted stateless `hlrt_` refresh tokens that
-- referenced no server row: they could not be revoked (the settings "revoke"
-- only killed the 60-minute access token) and a replayed, already-rotated
-- refresh token was not treated as theft. This migration adds the persistent
-- anchor the refresh artifact lacked.
--
-- `mcp_oauth_connections` is the revocable unit: one row per connector
-- connection that requested `offline_access`. `current_jti` is the only refresh
-- `jti` valid at any moment — every rotation advances it, so a presented `jti`
-- that is not `current_jti` is a replay (theft signal) that revokes the row.
-- `revoked_at` is the kill switch the settings card flips; revoking the row
-- stops every future refresh.
CREATE TABLE "mcp_oauth_connections" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"client_id" TEXT NOT NULL,
"client_name" TEXT NOT NULL,
"scope" TEXT NOT NULL,
"resource" TEXT NOT NULL,
"current_jti" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_used_at" TIMESTAMP(3),
"revoked_at" TIMESTAMP(3),
CONSTRAINT "mcp_oauth_connections_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "mcp_oauth_connections_user_id_idx" ON "mcp_oauth_connections"("user_id");
ALTER TABLE "mcp_oauth_connections" ADD CONSTRAINT "mcp_oauth_connections_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Link each OAuth-minted access token back to its connection so a connection
-- revoke kills every access row it issued, and so the connector token list can
-- exclude the transient access rows (they would otherwise flood it — M2).
ALTER TABLE "api_tokens" ADD COLUMN "mcp_connection_id" TEXT;
CREATE INDEX "api_tokens_mcp_connection_id_idx" ON "api_tokens"("mcp_connection_id");
ALTER TABLE "api_tokens" ADD CONSTRAINT "api_tokens_mcp_connection_id_fkey" FOREIGN KEY ("mcp_connection_id") REFERENCES "mcp_oauth_connections"("id") ON DELETE SET NULL ON UPDATE CASCADE;