History

Repair OpenAPI OAuth credential-binding cutover residue

Rhys Sullivan committed 6 months ago

Commit 70725e61 parent 7c00f9d5 files · +702 −0Browse files

Unified · Split

5 files
  1. apps/cloud/drizzle/0014_repair_openapi_oauth_cutover_residue.sql +241 −0
  2. apps/cloud/drizzle/meta/_journal.json +7 −0
  3. apps/local/drizzle/0009_repair_openapi_oauth_cutover_residue.sql +253 −0
  4. apps/local/drizzle/meta/_journal.json +7 −0
  5. apps/local/src/server/migrate-oauth-connections.test.ts +194 −0
apps/cloud/drizzle/0014_repair_openapi_oauth_cutover_residue.sql+241−0
@@ -0,0 +1,241 @@
1+-- Repair OpenAPI OAuth residue from the scoped-credential cutover.
2+--
3+-- 0009 only normalized provider='openapi:oauth2' rows whose provider_state
4+-- still had the old `flow` shape. Some live rows already had canonical
5+-- `kind` provider_state, so the provider key was skipped. 0012 also collapsed
6+-- user-scoped OpenAPI OAuth client credential bindings when the backing Secret
7+-- row lived at the source/org scope. Runtime refresh reads provider_state
8+-- directly, but edit/refresh UI reads credential_binding rows, so recreate
9+-- those explicit bindings from the already-canonical Connection rows.
10+
11+UPDATE "connection"
12+SET "provider" = 'oauth2',
13+ "updated_at" = now()
14+WHERE "provider" = 'openapi:oauth2'
15+ AND "provider_state" ? 'kind';--> statement-breakpoint
16+
17+WITH oauth_connections AS (
18+ SELECT
19+ b."scope_id",
20+ b."id" AS "binding_id",
21+ b."plugin_id",
22+ b."source_id",
23+ b."source_scope_id",
24+ s."oauth2"->>'clientIdSlot' AS "slot_key",
25+ c."provider_state"->>'clientIdSecretId' AS "secret_id",
26+ b."created_at"
27+ FROM "credential_binding" b
28+ JOIN "openapi_source" s
29+ ON s."id" = b."source_id"
30+ AND s."scope_id" = b."source_scope_id"
31+ JOIN "connection" c
32+ ON c."id" = b."connection_id"
33+ AND c."scope_id" = b."scope_id"
34+ WHERE b."plugin_id" = 'openapi'
35+ AND b."kind" = 'connection'
36+ AND s."oauth2" IS NOT NULL
37+ AND c."provider" = 'oauth2'
38+ AND c."provider_state" ? 'clientIdSecretId'
39+ AND coalesce(c."provider_state"->>'clientIdSecretId', '') <> ''
40+)
41+UPDATE "credential_binding" b
42+SET "secret_id" = r."secret_id",
43+ "text_value" = NULL,
44+ "connection_id" = NULL,
45+ "updated_at" = now()
46+FROM oauth_connections r
47+WHERE b."scope_id" = r."scope_id"
48+ AND b."plugin_id" = r."plugin_id"
49+ AND b."source_id" = r."source_id"
50+ AND b."source_scope_id" = r."source_scope_id"
51+ AND b."slot_key" = r."slot_key"
52+ AND b."kind" = 'secret';--> statement-breakpoint
53+
54+WITH oauth_connections AS (
55+ SELECT
56+ b."scope_id",
57+ b."id" AS "binding_id",
58+ b."plugin_id",
59+ b."source_id",
60+ b."source_scope_id",
61+ s."oauth2"->>'clientIdSlot' AS "slot_key",
62+ c."provider_state"->>'clientIdSecretId' AS "secret_id",
63+ b."created_at"
64+ FROM "credential_binding" b
65+ JOIN "openapi_source" s
66+ ON s."id" = b."source_id"
67+ AND s."scope_id" = b."source_scope_id"
68+ JOIN "connection" c
69+ ON c."id" = b."connection_id"
70+ AND c."scope_id" = b."scope_id"
71+ WHERE b."plugin_id" = 'openapi'
72+ AND b."kind" = 'connection'
73+ AND s."oauth2" IS NOT NULL
74+ AND c."provider" = 'oauth2'
75+ AND c."provider_state" ? 'clientIdSecretId'
76+ AND coalesce(c."provider_state"->>'clientIdSecretId', '') <> ''
77+)
78+INSERT INTO "credential_binding" (
79+ "id",
80+ "scope_id",
81+ "plugin_id",
82+ "source_id",
83+ "source_scope_id",
84+ "slot_key",
85+ "kind",
86+ "text_value",
87+ "secret_id",
88+ "connection_id",
89+ "created_at",
90+ "updated_at"
91+)
92+SELECT
93+ '[' || to_jsonb('oconn-openapi-oauth-client'::text)::text || ',' || to_jsonb(r."binding_id")::text || ',' || to_jsonb(r."scope_id")::text || ',' || to_jsonb(r."slot_key")::text || ']',
94+ r."scope_id",
95+ r."plugin_id",
96+ r."source_id",
97+ r."source_scope_id",
98+ r."slot_key",
99+ 'secret',
100+ NULL,
101+ r."secret_id",
102+ NULL,
103+ r."created_at",
104+ now()
105+FROM oauth_connections r
106+WHERE r."slot_key" IS NOT NULL
107+ AND NOT EXISTS (
108+ SELECT 1
109+ FROM "credential_binding" existing
110+ WHERE existing."scope_id" = r."scope_id"
111+ AND existing."plugin_id" = r."plugin_id"
112+ AND existing."source_id" = r."source_id"
113+ AND existing."source_scope_id" = r."source_scope_id"
114+ AND existing."slot_key" = r."slot_key"
115+ )
116+ON CONFLICT ("scope_id", "id") DO UPDATE SET
117+ "plugin_id" = excluded."plugin_id",
118+ "source_id" = excluded."source_id",
119+ "source_scope_id" = excluded."source_scope_id",
120+ "slot_key" = excluded."slot_key",
121+ "kind" = excluded."kind",
122+ "text_value" = excluded."text_value",
123+ "secret_id" = excluded."secret_id",
124+ "connection_id" = excluded."connection_id",
125+ "updated_at" = excluded."updated_at";--> statement-breakpoint
126+
127+WITH oauth_connections AS (
128+ SELECT
129+ b."scope_id",
130+ b."id" AS "binding_id",
131+ b."plugin_id",
132+ b."source_id",
133+ b."source_scope_id",
134+ coalesce(
135+ s."oauth2"->>'clientSecretSlot',
136+ 'oauth2:' || COALESCE(NULLIF(trim(both '-' from lower(regexp_replace(trim(s."oauth2"->>'securitySchemeName'), '[^a-zA-Z0-9]+', '-', 'g'))), ''), 'default') || ':client-secret'
137+ ) AS "slot_key",
138+ c."provider_state"->>'clientSecretSecretId' AS "secret_id",
139+ b."created_at"
140+ FROM "credential_binding" b
141+ JOIN "openapi_source" s
142+ ON s."id" = b."source_id"
143+ AND s."scope_id" = b."source_scope_id"
144+ JOIN "connection" c
145+ ON c."id" = b."connection_id"
146+ AND c."scope_id" = b."scope_id"
147+ WHERE b."plugin_id" = 'openapi'
148+ AND b."kind" = 'connection'
149+ AND s."oauth2" IS NOT NULL
150+ AND c."provider" = 'oauth2'
151+ AND c."provider_state" ? 'clientSecretSecretId'
152+ AND coalesce(c."provider_state"->>'clientSecretSecretId', '') <> ''
153+)
154+UPDATE "credential_binding" b
155+SET "secret_id" = r."secret_id",
156+ "text_value" = NULL,
157+ "connection_id" = NULL,
158+ "updated_at" = now()
159+FROM oauth_connections r
160+WHERE b."scope_id" = r."scope_id"
161+ AND b."plugin_id" = r."plugin_id"
162+ AND b."source_id" = r."source_id"
163+ AND b."source_scope_id" = r."source_scope_id"
164+ AND b."slot_key" = r."slot_key"
165+ AND b."kind" = 'secret';--> statement-breakpoint
166+
167+WITH oauth_connections AS (
168+ SELECT
169+ b."scope_id",
170+ b."id" AS "binding_id",
171+ b."plugin_id",
172+ b."source_id",
173+ b."source_scope_id",
174+ coalesce(
175+ s."oauth2"->>'clientSecretSlot',
176+ 'oauth2:' || COALESCE(NULLIF(trim(both '-' from lower(regexp_replace(trim(s."oauth2"->>'securitySchemeName'), '[^a-zA-Z0-9]+', '-', 'g'))), ''), 'default') || ':client-secret'
177+ ) AS "slot_key",
178+ c."provider_state"->>'clientSecretSecretId' AS "secret_id",
179+ b."created_at"
180+ FROM "credential_binding" b
181+ JOIN "openapi_source" s
182+ ON s."id" = b."source_id"
183+ AND s."scope_id" = b."source_scope_id"
184+ JOIN "connection" c
185+ ON c."id" = b."connection_id"
186+ AND c."scope_id" = b."scope_id"
187+ WHERE b."plugin_id" = 'openapi'
188+ AND b."kind" = 'connection'
189+ AND s."oauth2" IS NOT NULL
190+ AND c."provider" = 'oauth2'
191+ AND c."provider_state" ? 'clientSecretSecretId'
192+ AND coalesce(c."provider_state"->>'clientSecretSecretId', '') <> ''
193+)
194+INSERT INTO "credential_binding" (
195+ "id",
196+ "scope_id",
197+ "plugin_id",
198+ "source_id",
199+ "source_scope_id",
200+ "slot_key",
201+ "kind",
202+ "text_value",
203+ "secret_id",
204+ "connection_id",
205+ "created_at",
206+ "updated_at"
207+)
208+SELECT
209+ '[' || to_jsonb('oconn-openapi-oauth-secret'::text)::text || ',' || to_jsonb(r."binding_id")::text || ',' || to_jsonb(r."scope_id")::text || ',' || to_jsonb(r."slot_key")::text || ']',
210+ r."scope_id",
211+ r."plugin_id",
212+ r."source_id",
213+ r."source_scope_id",
214+ r."slot_key",
215+ 'secret',
216+ NULL,
217+ r."secret_id",
218+ NULL,
219+ r."created_at",
220+ now()
221+FROM oauth_connections r
222+WHERE r."slot_key" IS NOT NULL
223+ AND NOT EXISTS (
224+ SELECT 1
225+ FROM "credential_binding" existing
226+ WHERE existing."scope_id" = r."scope_id"
227+ AND existing."plugin_id" = r."plugin_id"
228+ AND existing."source_id" = r."source_id"
229+ AND existing."source_scope_id" = r."source_scope_id"
230+ AND existing."slot_key" = r."slot_key"
231+ )
232+ON CONFLICT ("scope_id", "id") DO UPDATE SET
233+ "plugin_id" = excluded."plugin_id",
234+ "source_id" = excluded."source_id",
235+ "source_scope_id" = excluded."source_scope_id",
236+ "slot_key" = excluded."slot_key",
237+ "kind" = excluded."kind",
238+ "text_value" = excluded."text_value",
239+ "secret_id" = excluded."secret_id",
240+ "connection_id" = excluded."connection_id",
241+ "updated_at" = excluded."updated_at";
apps/cloud/drizzle/meta/_journal.json+7−0
@@ -99,6 +99,13 @@ · 98 unmodified lines
9999 "when": 1778179800000,
100100 "tag": "0013_cleanup_orphan_oauth_rows",
101101 "breakpoints": true
102+ },
103+ {
104+ "idx": 14,
105+ "version": "7",
106+ "when": 1778192434062,
107+ "tag": "0014_repair_openapi_oauth_cutover_residue",
108+ "breakpoints": true
102109 }
103110 ]
104111 }
apps/local/drizzle/0009_repair_openapi_oauth_cutover_residue.sql+253−0
@@ -0,0 +1,253 @@
1+-- Repair OpenAPI OAuth residue from the scoped-credential cutover.
2+
3+UPDATE `connection`
4+SET `provider` = 'oauth2',
5+ `updated_at` = CAST(strftime('%s', 'now') AS INTEGER) * 1000
6+WHERE `provider` = 'openapi:oauth2'
7+ AND json_extract(`provider_state`, '$.kind') IS NOT NULL;--> statement-breakpoint
8+
9+WITH oauth_connections AS (
10+ SELECT
11+ b.`scope_id`,
12+ b.`id` AS `binding_id`,
13+ b.`plugin_id`,
14+ b.`source_id`,
15+ b.`source_scope_id`,
16+ json_extract(s.`oauth2`, '$.clientIdSlot') AS `slot_key`,
17+ json_extract(c.`provider_state`, '$.clientIdSecretId') AS `secret_id`,
18+ b.`created_at`
19+ FROM `credential_binding` b
20+ JOIN `openapi_source` s
21+ ON s.`id` = b.`source_id`
22+ AND s.`scope_id` = b.`source_scope_id`
23+ JOIN `connection` c
24+ ON c.`id` = b.`connection_id`
25+ AND c.`scope_id` = b.`scope_id`
26+ WHERE b.`plugin_id` = 'openapi'
27+ AND b.`kind` = 'connection'
28+ AND s.`oauth2` IS NOT NULL
29+ AND c.`provider` = 'oauth2'
30+ AND json_extract(c.`provider_state`, '$.clientIdSecretId') IS NOT NULL
31+ AND coalesce(json_extract(c.`provider_state`, '$.clientIdSecretId'), '') <> ''
32+)
33+UPDATE `credential_binding`
34+SET
35+ `secret_id` = (
36+ SELECT r.`secret_id`
37+ FROM oauth_connections r
38+ WHERE r.`scope_id` = `credential_binding`.`scope_id`
39+ AND r.`plugin_id` = `credential_binding`.`plugin_id`
40+ AND r.`source_id` = `credential_binding`.`source_id`
41+ AND r.`source_scope_id` = `credential_binding`.`source_scope_id`
42+ AND r.`slot_key` = `credential_binding`.`slot_key`
43+ LIMIT 1
44+ ),
45+ `text_value` = NULL,
46+ `connection_id` = NULL,
47+ `updated_at` = CAST(strftime('%s', 'now') AS INTEGER) * 1000
48+WHERE `kind` = 'secret'
49+ AND EXISTS (
50+ SELECT 1
51+ FROM oauth_connections r
52+ WHERE r.`scope_id` = `credential_binding`.`scope_id`
53+ AND r.`plugin_id` = `credential_binding`.`plugin_id`
54+ AND r.`source_id` = `credential_binding`.`source_id`
55+ AND r.`source_scope_id` = `credential_binding`.`source_scope_id`
56+ AND r.`slot_key` = `credential_binding`.`slot_key`
57+ );--> statement-breakpoint
58+
59+WITH oauth_connections AS (
60+ SELECT
61+ b.`scope_id`,
62+ b.`id` AS `binding_id`,
63+ b.`plugin_id`,
64+ b.`source_id`,
65+ b.`source_scope_id`,
66+ json_extract(s.`oauth2`, '$.clientIdSlot') AS `slot_key`,
67+ json_extract(c.`provider_state`, '$.clientIdSecretId') AS `secret_id`,
68+ b.`created_at`
69+ FROM `credential_binding` b
70+ JOIN `openapi_source` s
71+ ON s.`id` = b.`source_id`
72+ AND s.`scope_id` = b.`source_scope_id`
73+ JOIN `connection` c
74+ ON c.`id` = b.`connection_id`
75+ AND c.`scope_id` = b.`scope_id`
76+ WHERE b.`plugin_id` = 'openapi'
77+ AND b.`kind` = 'connection'
78+ AND s.`oauth2` IS NOT NULL
79+ AND c.`provider` = 'oauth2'
80+ AND json_extract(c.`provider_state`, '$.clientIdSecretId') IS NOT NULL
81+ AND coalesce(json_extract(c.`provider_state`, '$.clientIdSecretId'), '') <> ''
82+)
83+INSERT INTO `credential_binding` (
84+ `id`,
85+ `scope_id`,
86+ `plugin_id`,
87+ `source_id`,
88+ `source_scope_id`,
89+ `slot_key`,
90+ `kind`,
91+ `text_value`,
92+ `secret_id`,
93+ `connection_id`,
94+ `created_at`,
95+ `updated_at`
96+)
97+SELECT
98+ json_array('oconn-openapi-oauth-client', r.`binding_id`, r.`scope_id`, r.`slot_key`),
99+ r.`scope_id`,
100+ r.`plugin_id`,
101+ r.`source_id`,
102+ r.`source_scope_id`,
103+ r.`slot_key`,
104+ 'secret',
105+ NULL,
106+ r.`secret_id`,
107+ NULL,
108+ r.`created_at`,
109+ CAST(strftime('%s', 'now') AS INTEGER) * 1000
110+FROM oauth_connections r
111+WHERE r.`slot_key` IS NOT NULL
112+ AND NOT EXISTS (
113+ SELECT 1
114+ FROM `credential_binding` existing
115+ WHERE existing.`scope_id` = r.`scope_id`
116+ AND existing.`plugin_id` = r.`plugin_id`
117+ AND existing.`source_id` = r.`source_id`
118+ AND existing.`source_scope_id` = r.`source_scope_id`
119+ AND existing.`slot_key` = r.`slot_key`
120+ )
121+ON CONFLICT(`scope_id`, `id`) DO UPDATE SET
122+ `plugin_id` = excluded.`plugin_id`,
123+ `source_id` = excluded.`source_id`,
124+ `source_scope_id` = excluded.`source_scope_id`,
125+ `slot_key` = excluded.`slot_key`,
126+ `kind` = excluded.`kind`,
127+ `text_value` = excluded.`text_value`,
128+ `secret_id` = excluded.`secret_id`,
129+ `connection_id` = excluded.`connection_id`,
130+ `updated_at` = excluded.`updated_at`;--> statement-breakpoint
131+
132+WITH oauth_connections AS (
133+ SELECT
134+ b.`scope_id`,
135+ b.`id` AS `binding_id`,
136+ b.`plugin_id`,
137+ b.`source_id`,
138+ b.`source_scope_id`,
139+ json_extract(s.`oauth2`, '$.clientSecretSlot') AS `slot_key`,
140+ json_extract(c.`provider_state`, '$.clientSecretSecretId') AS `secret_id`,
141+ b.`created_at`
142+ FROM `credential_binding` b
143+ JOIN `openapi_source` s
144+ ON s.`id` = b.`source_id`
145+ AND s.`scope_id` = b.`source_scope_id`
146+ JOIN `connection` c
147+ ON c.`id` = b.`connection_id`
148+ AND c.`scope_id` = b.`scope_id`
149+ WHERE b.`plugin_id` = 'openapi'
150+ AND b.`kind` = 'connection'
151+ AND s.`oauth2` IS NOT NULL
152+ AND c.`provider` = 'oauth2'
153+ AND json_extract(c.`provider_state`, '$.clientSecretSecretId') IS NOT NULL
154+ AND coalesce(json_extract(c.`provider_state`, '$.clientSecretSecretId'), '') <> ''
155+)
156+UPDATE `credential_binding`
157+SET
158+ `secret_id` = (
159+ SELECT r.`secret_id`
160+ FROM oauth_connections r
161+ WHERE r.`scope_id` = `credential_binding`.`scope_id`
162+ AND r.`plugin_id` = `credential_binding`.`plugin_id`
163+ AND r.`source_id` = `credential_binding`.`source_id`
164+ AND r.`source_scope_id` = `credential_binding`.`source_scope_id`
165+ AND r.`slot_key` = `credential_binding`.`slot_key`
166+ LIMIT 1
167+ ),
168+ `text_value` = NULL,
169+ `connection_id` = NULL,
170+ `updated_at` = CAST(strftime('%s', 'now') AS INTEGER) * 1000
171+WHERE `kind` = 'secret'
172+ AND EXISTS (
173+ SELECT 1
174+ FROM oauth_connections r
175+ WHERE r.`scope_id` = `credential_binding`.`scope_id`
176+ AND r.`plugin_id` = `credential_binding`.`plugin_id`
177+ AND r.`source_id` = `credential_binding`.`source_id`
178+ AND r.`source_scope_id` = `credential_binding`.`source_scope_id`
179+ AND r.`slot_key` = `credential_binding`.`slot_key`
180+ );--> statement-breakpoint
181+
182+WITH oauth_connections AS (
183+ SELECT
184+ b.`scope_id`,
185+ b.`id` AS `binding_id`,
186+ b.`plugin_id`,
187+ b.`source_id`,
188+ b.`source_scope_id`,
189+ json_extract(s.`oauth2`, '$.clientSecretSlot') AS `slot_key`,
190+ json_extract(c.`provider_state`, '$.clientSecretSecretId') AS `secret_id`,
191+ b.`created_at`
192+ FROM `credential_binding` b
193+ JOIN `openapi_source` s
194+ ON s.`id` = b.`source_id`
195+ AND s.`scope_id` = b.`source_scope_id`
196+ JOIN `connection` c
197+ ON c.`id` = b.`connection_id`
198+ AND c.`scope_id` = b.`scope_id`
199+ WHERE b.`plugin_id` = 'openapi'
200+ AND b.`kind` = 'connection'
201+ AND s.`oauth2` IS NOT NULL
202+ AND c.`provider` = 'oauth2'
203+ AND json_extract(c.`provider_state`, '$.clientSecretSecretId') IS NOT NULL
204+ AND coalesce(json_extract(c.`provider_state`, '$.clientSecretSecretId'), '') <> ''
205+)
206+INSERT INTO `credential_binding` (
207+ `id`,
208+ `scope_id`,
209+ `plugin_id`,
210+ `source_id`,
211+ `source_scope_id`,
212+ `slot_key`,
213+ `kind`,
214+ `text_value`,
215+ `secret_id`,
216+ `connection_id`,
217+ `created_at`,
218+ `updated_at`
219+)
220+SELECT
221+ json_array('oconn-openapi-oauth-secret', r.`binding_id`, r.`scope_id`, r.`slot_key`),
222+ r.`scope_id`,
223+ r.`plugin_id`,
224+ r.`source_id`,
225+ r.`source_scope_id`,
226+ r.`slot_key`,
227+ 'secret',
228+ NULL,
229+ r.`secret_id`,
230+ NULL,
231+ r.`created_at`,
232+ CAST(strftime('%s', 'now') AS INTEGER) * 1000
233+FROM oauth_connections r
234+WHERE r.`slot_key` IS NOT NULL
235+ AND NOT EXISTS (
236+ SELECT 1
237+ FROM `credential_binding` existing
238+ WHERE existing.`scope_id` = r.`scope_id`
239+ AND existing.`plugin_id` = r.`plugin_id`
240+ AND existing.`source_id` = r.`source_id`
241+ AND existing.`source_scope_id` = r.`source_scope_id`
242+ AND existing.`slot_key` = r.`slot_key`
243+ )
244+ON CONFLICT(`scope_id`, `id`) DO UPDATE SET
245+ `plugin_id` = excluded.`plugin_id`,
246+ `source_id` = excluded.`source_id`,
247+ `source_scope_id` = excluded.`source_scope_id`,
248+ `slot_key` = excluded.`slot_key`,
249+ `kind` = excluded.`kind`,
250+ `text_value` = excluded.`text_value`,
251+ `secret_id` = excluded.`secret_id`,
252+ `connection_id` = excluded.`connection_id`,
253+ `updated_at` = excluded.`updated_at`;
apps/local/drizzle/meta/_journal.json+7−0
@@ -64,6 +64,13 @@ · 63 unmodified lines
6464 "when": 1778128200000,
6565 "tag": "0008_scoped_credentials_cutover",
6666 "breakpoints": true
67+ },
68+ {
69+ "idx": 9,
70+ "version": "6",
71+ "when": 1778192434062,
72+ "tag": "0009_repair_openapi_oauth_cutover_residue",
73+ "breakpoints": true
6774 }
6875 ]
6976 }
apps/local/src/server/migrate-oauth-connections.test.ts+194−0
@@ -6,6 +6,10 @@ import { join } from "node:path"; · 5 unmodified lines
66 import { Schema } from "effect";
77
88 const MIGRATION = join(import.meta.dirname, "../../drizzle/0008_scoped_credentials_cutover.sql");
9+const REPAIR_MIGRATION = join(
10+ import.meta.dirname,
11+ "../../drizzle/0009_repair_openapi_oauth_cutover_residue.sql",
12+);
913
1014 let workDir: string;
1115 let db: Database;
@@ -126,3 +130,193 @@ describe("0008_scoped_credentials_cutover OAuth connection section", () => { · 114 unmodified lines
126130 });
127131 });
128132 });
133+
134+describe("0009_repair_openapi_oauth_cutover_residue", () => {
135+ it("repairs already-canonical OpenAPI rows and restores user-scoped OAuth secret bindings", () => {
136+ const now = Date.now();
137+ db.exec(`
138+ CREATE TABLE \`openapi_source\` (
139+ \`id\` text NOT NULL,
140+ \`scope_id\` text NOT NULL,
141+ \`oauth2\` text,
142+ PRIMARY KEY(\`scope_id\`, \`id\`)
143+ );
144+ CREATE TABLE \`credential_binding\` (
145+ \`id\` text NOT NULL,
146+ \`scope_id\` text NOT NULL,
147+ \`plugin_id\` text NOT NULL,
148+ \`source_id\` text NOT NULL,
149+ \`source_scope_id\` text NOT NULL,
150+ \`slot_key\` text NOT NULL,
151+ \`kind\` text NOT NULL,
152+ \`text_value\` text,
153+ \`secret_id\` text,
154+ \`connection_id\` text,
155+ \`created_at\` integer NOT NULL,
156+ \`updated_at\` integer NOT NULL,
157+ PRIMARY KEY(\`scope_id\`, \`id\`)
158+ );
159+ `);
160+
161+ db.prepare(
162+ "INSERT INTO `openapi_source` (id, scope_id, oauth2) VALUES (?, ?, ?)",
163+ ).run(
164+ "dealcloud_api",
165+ "org-1",
166+ JSON.stringify({
167+ kind: "oauth2",
168+ securitySchemeName: "oauth2",
169+ clientIdSlot: "oauth2:oauth2:client-id",
170+ clientSecretSlot: "oauth2:oauth2:client-secret",
171+ connectionSlot: "oauth2:oauth2:connection",
172+ }),
173+ );
174+
175+ const insertConnection = db.prepare(
176+ "INSERT INTO `connection` (id, scope_id, provider, provider_state, scope, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
177+ );
178+ insertConnection.run(
179+ "openapi-oauth2-app-dealcloud_api",
180+ "org-1",
181+ "oauth2",
182+ JSON.stringify({
183+ kind: "client-credentials",
184+ tokenEndpoint: "https://resolve.dealcloud.com/oauth/token",
185+ clientIdSecretId: "dealcloud-client-id",
186+ clientSecretSecretId: "dealcloud-client-secret",
187+ }),
188+ null,
189+ now,
190+ );
191+ insertConnection.run(
192+ "openapi-oauth2-app-dealcloud_api",
193+ "user-org:user-jd:org-1",
194+ "openapi:oauth2",
195+ JSON.stringify({
196+ kind: "client-credentials",
197+ tokenEndpoint: "https://resolve.dealcloud.com/oauth/token",
198+ clientIdSecretId: "dealcloud-client-id-jd",
199+ clientSecretSecretId: "dealcloud-client-secret-jd",
200+ }),
201+ null,
202+ now,
203+ );
204+
205+ const insertBinding = db.prepare(
206+ "INSERT INTO `credential_binding` (id, scope_id, plugin_id, source_id, source_scope_id, slot_key, kind, text_value, secret_id, connection_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
207+ );
208+ insertBinding.run(
209+ "org-client-id",
210+ "org-1",
211+ "openapi",
212+ "dealcloud_api",
213+ "org-1",
214+ "oauth2:oauth2:client-id",
215+ "secret",
216+ null,
217+ "dealcloud-client-id-jd",
218+ null,
219+ now,
220+ now,
221+ );
222+ insertBinding.run(
223+ "org-client-secret",
224+ "org-1",
225+ "openapi",
226+ "dealcloud_api",
227+ "org-1",
228+ "oauth2:oauth2:client-secret",
229+ "secret",
230+ null,
231+ "dealcloud-client-secret-jd",
232+ null,
233+ now,
234+ now,
235+ );
236+ insertBinding.run(
237+ "org-connection",
238+ "org-1",
239+ "openapi",
240+ "dealcloud_api",
241+ "org-1",
242+ "oauth2:oauth2:connection",
243+ "connection",
244+ null,
245+ null,
246+ "openapi-oauth2-app-dealcloud_api",
247+ now,
248+ now,
249+ );
250+ insertBinding.run(
251+ "jd-connection",
252+ "user-org:user-jd:org-1",
253+ "openapi",
254+ "dealcloud_api",
255+ "org-1",
256+ "oauth2:oauth2:connection",
257+ "connection",
258+ null,
259+ null,
260+ "openapi-oauth2-app-dealcloud_api",
261+ now,
262+ now,
263+ );
264+
265+ db.exec(readFileSync(REPAIR_MIGRATION, "utf-8"));
266+
267+ const providers = decodeConnectionRows(
268+ db.prepare("SELECT provider, provider_state FROM `connection` ORDER BY scope_id").all(),
269+ );
270+ expect(providers.map((row) => row.provider)).toEqual(["oauth2", "oauth2"]);
271+
272+ const bindings = db
273+ .prepare(
274+ "SELECT scope_id, slot_key, kind, secret_id, connection_id FROM `credential_binding` WHERE source_id = ? ORDER BY scope_id, slot_key",
275+ )
276+ .all("dealcloud_api");
277+ expect(bindings).toEqual([
278+ {
279+ scope_id: "org-1",
280+ slot_key: "oauth2:oauth2:client-id",
281+ kind: "secret",
282+ secret_id: "dealcloud-client-id",
283+ connection_id: null,
284+ },
285+ {
286+ scope_id: "org-1",
287+ slot_key: "oauth2:oauth2:client-secret",
288+ kind: "secret",
289+ secret_id: "dealcloud-client-secret",
290+ connection_id: null,
291+ },
292+ {
293+ scope_id: "org-1",
294+ slot_key: "oauth2:oauth2:connection",
295+ kind: "connection",
296+ secret_id: null,
297+ connection_id: "openapi-oauth2-app-dealcloud_api",
298+ },
299+ {
300+ scope_id: "user-org:user-jd:org-1",
301+ slot_key: "oauth2:oauth2:client-id",
302+ kind: "secret",
303+ secret_id: "dealcloud-client-id-jd",
304+ connection_id: null,
305+ },
306+ {
307+ scope_id: "user-org:user-jd:org-1",
308+ slot_key: "oauth2:oauth2:client-secret",
309+ kind: "secret",
310+ secret_id: "dealcloud-client-secret-jd",
311+ connection_id: null,
312+ },
313+ {
314+ scope_id: "user-org:user-jd:org-1",
315+ slot_key: "oauth2:oauth2:connection",
316+ kind: "connection",
317+ secret_id: null,
318+ connection_id: "openapi-oauth2-app-dealcloud_api",
319+ },
320+ ]);
321+ });
322+});

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t