fix(db): renumber board key scope migration
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
f78d1b4249
commit
ad32d2270d
|
|
@ -6,7 +6,7 @@ import {
|
|||
startEmbeddedPostgresTestDatabase,
|
||||
} from "./test-embedded-postgres.js";
|
||||
|
||||
const MIGRATION_FILE = "0212_cultured_george_stacy.sql";
|
||||
const MIGRATION_FILE = "0277_omniscient_darwin.sql";
|
||||
const cleanups: Array<() => Promise<void>> = [];
|
||||
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
|
||||
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip;
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "scope_config" jsonb;--> statement-breakpoint
|
||||
ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "token_prefix" text;--> statement-breakpoint
|
||||
ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "legacy_unrestricted" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
|
||||
-- Only rows present before this migration can enter the legacy-unrestricted state.
|
||||
-- Malformed non-null scope JSON is deliberately preserved so authentication fails closed.
|
||||
UPDATE "board_api_keys"
|
||||
SET "legacy_unrestricted" = true
|
||||
WHERE "scope_config" IS NULL;--> statement-breakpoint
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'board_api_keys_scope_legacy_check'
|
||||
AND conrelid = 'board_api_keys'::regclass
|
||||
) THEN
|
||||
ALTER TABLE "board_api_keys"
|
||||
ADD CONSTRAINT "board_api_keys_scope_legacy_check"
|
||||
CHECK (
|
||||
("legacy_unrestricted" = true AND "scope_config" IS NULL)
|
||||
OR
|
||||
("legacy_unrestricted" = false AND "scope_config" IS NOT NULL)
|
||||
);
|
||||
END IF;
|
||||
END $$;--> statement-breakpoint
|
||||
|
||||
-- Pending CLI auth challenges created before this migration remain nullable and
|
||||
-- cannot mint a key; every new challenge must persist a validated scope.
|
||||
ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "requested_scope_config" jsonb;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "pending_key_prefix" text;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "board_api_key_authorization_events" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"board_api_key_id" uuid NOT NULL,
|
||||
"owner_user_id" text NOT NULL,
|
||||
"token_prefix" text,
|
||||
"action" text NOT NULL,
|
||||
"classification" text NOT NULL,
|
||||
"authoritative_company_id" uuid,
|
||||
"authoritative_resource_type" text,
|
||||
"authoritative_resource_id" text,
|
||||
"decision" text NOT NULL,
|
||||
"reason" text NOT NULL,
|
||||
"request_id" text,
|
||||
"run_id" uuid,
|
||||
"details" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_key_created_idx"
|
||||
ON "board_api_key_authorization_events" USING btree ("board_api_key_id", "created_at" DESC);--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_company_created_idx"
|
||||
ON "board_api_key_authorization_events" USING btree ("authoritative_company_id", "created_at" DESC);
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
CREATE TABLE IF NOT EXISTS "board_api_key_authorization_events" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"board_api_key_id" uuid NOT NULL,
|
||||
"owner_user_id" text NOT NULL,
|
||||
"token_prefix" text,
|
||||
"action" text NOT NULL,
|
||||
"classification" text NOT NULL,
|
||||
"authoritative_company_id" uuid,
|
||||
"authoritative_resource_type" text,
|
||||
"authoritative_resource_id" text,
|
||||
"decision" text NOT NULL,
|
||||
"reason" text NOT NULL,
|
||||
"request_id" text,
|
||||
"run_id" uuid,
|
||||
"details" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);--> statement-breakpoint
|
||||
ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "scope_config" jsonb;--> statement-breakpoint
|
||||
ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "token_prefix" text;--> statement-breakpoint
|
||||
ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "legacy_unrestricted" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
|
||||
-- Only rows present before this migration can enter the legacy-unrestricted
|
||||
-- state. Malformed non-null scope JSON is deliberately preserved so
|
||||
-- authentication fails closed.
|
||||
UPDATE "board_api_keys"
|
||||
SET "legacy_unrestricted" = true
|
||||
WHERE "scope_config" IS NULL;--> statement-breakpoint
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'board_api_keys_scope_legacy_check'
|
||||
AND conrelid = 'board_api_keys'::regclass
|
||||
) THEN
|
||||
ALTER TABLE "board_api_keys"
|
||||
ADD CONSTRAINT "board_api_keys_scope_legacy_check"
|
||||
CHECK (
|
||||
("legacy_unrestricted" = true AND "scope_config" IS NULL)
|
||||
OR
|
||||
("legacy_unrestricted" = false AND "scope_config" IS NOT NULL)
|
||||
);
|
||||
END IF;
|
||||
END $$;--> statement-breakpoint
|
||||
|
||||
-- Pending CLI auth challenges created before this migration remain nullable
|
||||
-- and cannot mint a key; every new challenge must persist a validated scope.
|
||||
ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "requested_scope_config" jsonb;--> statement-breakpoint
|
||||
ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "pending_key_prefix" text;--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_key_created_idx" ON "board_api_key_authorization_events" USING btree ("board_api_key_id","created_at" DESC NULLS LAST);--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_company_created_idx" ON "board_api_key_authorization_events" USING btree ("authoritative_company_id","created_at" DESC NULLS LAST);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"id": "5f80a2a4-dcf6-48a0-be4b-f32dbb243e28",
|
||||
"id": "582904aa-9302-4986-b589-2e9cfa7d7b03",
|
||||
"prevId": "9c7c9e05-e663-4a29-8b46-63733f62da9b",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
|
|
@ -2402,119 +2402,6 @@
|
|||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_provider_defaults": {
|
||||
"name": "ai_provider_defaults",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"grant_id": {
|
||||
"name": "grant_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"ai_provider_defaults_owner_provider_uq": {
|
||||
"name": "ai_provider_defaults_owner_provider_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "provider",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"ai_provider_defaults_company_id_companies_id_fk": {
|
||||
"name": "ai_provider_defaults_company_id_companies_id_fk",
|
||||
"tableFrom": "ai_provider_defaults",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_provider_defaults_company_grant_fk": {
|
||||
"name": "ai_provider_defaults_company_grant_fk",
|
||||
"tableFrom": "ai_provider_defaults",
|
||||
"tableTo": "connection_grants",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"grant_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {
|
||||
"ai_provider_defaults_provider_check": {
|
||||
"name": "ai_provider_defaults_provider_check",
|
||||
"value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai')"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.approval_comments": {
|
||||
"name": "approval_comments",
|
||||
"schema": "",
|
||||
|
|
@ -3307,6 +3194,155 @@
|
|||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.board_api_key_authorization_events": {
|
||||
"name": "board_api_key_authorization_events",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"board_api_key_id": {
|
||||
"name": "board_api_key_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"owner_user_id": {
|
||||
"name": "owner_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token_prefix": {
|
||||
"name": "token_prefix",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"action": {
|
||||
"name": "action",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"classification": {
|
||||
"name": "classification",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"authoritative_company_id": {
|
||||
"name": "authoritative_company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"authoritative_resource_type": {
|
||||
"name": "authoritative_resource_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"authoritative_resource_id": {
|
||||
"name": "authoritative_resource_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"decision": {
|
||||
"name": "decision",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"reason": {
|
||||
"name": "reason",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"request_id": {
|
||||
"name": "request_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"run_id": {
|
||||
"name": "run_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"details": {
|
||||
"name": "details",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'::jsonb"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"board_api_key_authorization_events_key_created_idx": {
|
||||
"name": "board_api_key_authorization_events_key_created_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "board_api_key_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": false,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"board_api_key_authorization_events_company_created_idx": {
|
||||
"name": "board_api_key_authorization_events_company_created_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "authoritative_company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": false,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.board_api_keys": {
|
||||
"name": "board_api_keys",
|
||||
"schema": "",
|
||||
|
|
@ -3336,6 +3372,25 @@
|
|||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"scope_config": {
|
||||
"name": "scope_config",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"token_prefix": {
|
||||
"name": "token_prefix",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"legacy_unrestricted": {
|
||||
"name": "legacy_unrestricted",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"last_used_at": {
|
||||
"name": "last_used_at",
|
||||
"type": "timestamp with time zone",
|
||||
|
|
@ -3412,7 +3467,12 @@
|
|||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"checkConstraints": {
|
||||
"board_api_keys_scope_legacy_check": {
|
||||
"name": "board_api_keys_scope_legacy_check",
|
||||
"value": "(\"board_api_keys\".\"legacy_unrestricted\" = true and \"board_api_keys\".\"scope_config\" is null) or (\"board_api_keys\".\"legacy_unrestricted\" = false and \"board_api_keys\".\"scope_config\" is not null)"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.budget_incidents": {
|
||||
|
|
@ -8487,12 +8547,24 @@
|
|||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"requested_scope_config": {
|
||||
"name": "requested_scope_config",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"pending_key_hash": {
|
||||
"name": "pending_key_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"pending_key_prefix": {
|
||||
"name": "pending_key_prefix",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"pending_key_name": {
|
||||
"name": "pending_key_name",
|
||||
"type": "text",
|
||||
|
|
|
|||
|
|
@ -1930,8 +1930,8 @@
|
|||
{
|
||||
"idx": 277,
|
||||
"version": "7",
|
||||
"when": 1789260211664,
|
||||
"tag": "0277_uneven_lady_deathstrike",
|
||||
"when": 1789258667522,
|
||||
"tag": "0277_omniscient_darwin",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in New Issue