mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
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.
42 lines
2.1 KiB
SQL
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;
|