History

Re-register DCR clients when the flow redirect URI changes

Rhys Sullivan committed 3 months ago

Commit d5ded651 parent ff7392014 files · +2333 −13Browse files

Unified · Split

14 files
  1. .changeset/dcr-redirect-uri-reuse.md +5 −0
  2. apps/cloud/drizzle/0011_tense_caretaker.sql +1 −0
  3. apps/cloud/drizzle/meta/0011_snapshot.json +1296 −0
  4. apps/cloud/drizzle/meta/_journal.json +7 −0
  5. apps/cloud/src/db/executor-schema.ts +1 −0
  6. apps/local/drizzle/0004_cultured_shadowcat.sql +1 −0
  7. apps/local/drizzle/meta/0004_snapshot.json +927 −0
  8. apps/local/drizzle/meta/_journal.json +7 −0
  9. apps/local/src/db/executor-schema.ts +1 −0
  10. apps/local/src/db/sqlite-fumadb.ts +6 −0
  11. packages/core/sdk/src/core-schema.ts +9 −0
  12. packages/core/sdk/src/oauth-client.ts +5 −0
  13. packages/core/sdk/src/oauth-register-dynamic.test.ts +14 −2
  14. packages/core/sdk/src/oauth-service.ts +53 −11
.changeset/dcr-redirect-uri-reuse.md+5−0
@@ -0,0 +1,5 @@
1+---
2+"@executor-js/sdk": patch
3+---
4+
5+Re-register a dynamically registered OAuth client when the configured callback URL changes instead of reusing the stale registration. DCR clients now persist the redirect URI they registered with the authorization server (`oauth_client.origin_redirect_uri`), and the per-issuer reuse lookup compares it against the current flow callback — a mismatch (for example after a sandbox recreation moved the callback origin) mints a fresh client rather than pairing the old registration with the new callback, which strict providers reject with `invalid_redirect_uri`. The stale client row is left in place so existing connections keep refreshing through it; clients persisted before this release have no stored redirect URI and continue to be reused as before.
apps/cloud/drizzle/0011_tense_caretaker.sql+1−0
@@ -0,0 +1 @@
1+ALTER TABLE "oauth_client" ADD COLUMN "origin_redirect_uri" text;
apps/cloud/drizzle/meta/0011_snapshot.json+1296−0
@@ -0,0 +1,1296 @@
1+{
2+ "id": "e285be7d-2427-4b1b-8b86-50a80ada25af",
3+ "prevId": "c27a8958-ad73-4590-9b9b-d1b2584e0bc6",
4+ "version": "7",
5+ "dialect": "postgresql",
6+ "tables": {
7+ "public.accounts": {
8+ "name": "accounts",
9+ "schema": "",
10+ "columns": {
11+ "id": {
12+ "name": "id",
13+ "type": "text",
14+ "primaryKey": true,
15+ "notNull": true
16+ },
17+ "created_at": {
18+ "name": "created_at",
19+ "type": "timestamp with time zone",
20+ "primaryKey": false,
21+ "notNull": true,
22+ "default": "now()"
23+ }
24+ },
25+ "indexes": {},
26+ "foreignKeys": {},
27+ "compositePrimaryKeys": {},
28+ "uniqueConstraints": {},
29+ "policies": {},
30+ "checkConstraints": {},
31+ "isRLSEnabled": false
32+ },
33+ "public.memberships": {
34+ "name": "memberships",
35+ "schema": "",
36+ "columns": {
37+ "account_id": {
38+ "name": "account_id",
39+ "type": "text",
40+ "primaryKey": false,
41+ "notNull": true
42+ },
43+ "organization_id": {
44+ "name": "organization_id",
45+ "type": "text",
46+ "primaryKey": false,
47+ "notNull": true
48+ },
49+ "created_at": {
50+ "name": "created_at",
51+ "type": "timestamp with time zone",
52+ "primaryKey": false,
53+ "notNull": true,
54+ "default": "now()"
55+ }
56+ },
57+ "indexes": {},
58+ "foreignKeys": {
59+ "memberships_account_id_accounts_id_fk": {
60+ "name": "memberships_account_id_accounts_id_fk",
61+ "tableFrom": "memberships",
62+ "tableTo": "accounts",
63+ "columnsFrom": ["account_id"],
64+ "columnsTo": ["id"],
65+ "onDelete": "cascade",
66+ "onUpdate": "no action"
67+ },
68+ "memberships_organization_id_organizations_id_fk": {
69+ "name": "memberships_organization_id_organizations_id_fk",
70+ "tableFrom": "memberships",
71+ "tableTo": "organizations",
72+ "columnsFrom": ["organization_id"],
73+ "columnsTo": ["id"],
74+ "onDelete": "cascade",
75+ "onUpdate": "no action"
76+ }
77+ },
78+ "compositePrimaryKeys": {
79+ "memberships_account_id_organization_id_pk": {
80+ "name": "memberships_account_id_organization_id_pk",
81+ "columns": ["account_id", "organization_id"]
82+ }
83+ },
84+ "uniqueConstraints": {},
85+ "policies": {},
86+ "checkConstraints": {},
87+ "isRLSEnabled": false
88+ },
89+ "public.organizations": {
90+ "name": "organizations",
91+ "schema": "",
92+ "columns": {
93+ "id": {
94+ "name": "id",
95+ "type": "text",
96+ "primaryKey": true,
97+ "notNull": true
98+ },
99+ "name": {
100+ "name": "name",
101+ "type": "text",
102+ "primaryKey": false,
103+ "notNull": true
104+ },
105+ "slug": {
106+ "name": "slug",
107+ "type": "text",
108+ "primaryKey": false,
109+ "notNull": true
110+ },
111+ "created_at": {
112+ "name": "created_at",
113+ "type": "timestamp with time zone",
114+ "primaryKey": false,
115+ "notNull": true,
116+ "default": "now()"
117+ }
118+ },
119+ "indexes": {
120+ "organizations_slug_unique": {
121+ "name": "organizations_slug_unique",
122+ "columns": [
123+ {
124+ "expression": "slug",
125+ "isExpression": false,
126+ "asc": true,
127+ "nulls": "last"
128+ }
129+ ],
130+ "isUnique": true,
131+ "concurrently": false,
132+ "method": "btree",
133+ "with": {}
134+ }
135+ },
136+ "foreignKeys": {},
137+ "compositePrimaryKeys": {},
138+ "uniqueConstraints": {},
139+ "policies": {},
140+ "checkConstraints": {},
141+ "isRLSEnabled": false
142+ },
143+ "public.blob": {
144+ "name": "blob",
145+ "schema": "",
146+ "columns": {
147+ "namespace": {
148+ "name": "namespace",
149+ "type": "varchar(255)",
150+ "primaryKey": false,
151+ "notNull": true
152+ },
153+ "key": {
154+ "name": "key",
155+ "type": "varchar(255)",
156+ "primaryKey": false,
157+ "notNull": true
158+ },
159+ "value": {
160+ "name": "value",
161+ "type": "text",
162+ "primaryKey": false,
163+ "notNull": true
164+ },
165+ "row_id": {
166+ "name": "row_id",
167+ "type": "varchar(255)",
168+ "primaryKey": true,
169+ "notNull": true
170+ },
171+ "id": {
172+ "name": "id",
173+ "type": "varchar(255)",
174+ "primaryKey": false,
175+ "notNull": true
176+ }
177+ },
178+ "indexes": {
179+ "blob_id_uidx": {
180+ "name": "blob_id_uidx",
181+ "columns": [
182+ {
183+ "expression": "id",
184+ "isExpression": false,
185+ "asc": true,
186+ "nulls": "last"
187+ }
188+ ],
189+ "isUnique": true,
190+ "concurrently": false,
191+ "method": "btree",
192+ "with": {}
193+ }
194+ },
195+ "foreignKeys": {},
196+ "compositePrimaryKeys": {},
197+ "uniqueConstraints": {},
198+ "policies": {},
199+ "checkConstraints": {},
200+ "isRLSEnabled": false
201+ },
202+ "public.connection": {
203+ "name": "connection",
204+ "schema": "",
205+ "columns": {
206+ "integration": {
207+ "name": "integration",
208+ "type": "varchar(255)",
209+ "primaryKey": false,
210+ "notNull": true
211+ },
212+ "name": {
213+ "name": "name",
214+ "type": "varchar(255)",
215+ "primaryKey": false,
216+ "notNull": true
217+ },
218+ "template": {
219+ "name": "template",
220+ "type": "text",
221+ "primaryKey": false,
222+ "notNull": true
223+ },
224+ "provider": {
225+ "name": "provider",
226+ "type": "text",
227+ "primaryKey": false,
228+ "notNull": true
229+ },
230+ "item_ids": {
231+ "name": "item_ids",
232+ "type": "json",
233+ "primaryKey": false,
234+ "notNull": true
235+ },
236+ "identity_label": {
237+ "name": "identity_label",
238+ "type": "text",
239+ "primaryKey": false,
240+ "notNull": false
241+ },
242+ "description": {
243+ "name": "description",
244+ "type": "text",
245+ "primaryKey": false,
246+ "notNull": false
247+ },
248+ "last_health": {
249+ "name": "last_health",
250+ "type": "json",
251+ "primaryKey": false,
252+ "notNull": false
253+ },
254+ "tools_synced_at": {
255+ "name": "tools_synced_at",
256+ "type": "bigint",
257+ "primaryKey": false,
258+ "notNull": false
259+ },
260+ "oauth_client": {
261+ "name": "oauth_client",
262+ "type": "text",
263+ "primaryKey": false,
264+ "notNull": false
265+ },
266+ "oauth_client_owner": {
267+ "name": "oauth_client_owner",
268+ "type": "text",
269+ "primaryKey": false,
270+ "notNull": false
271+ },
272+ "refresh_item_id": {
273+ "name": "refresh_item_id",
274+ "type": "text",
275+ "primaryKey": false,
276+ "notNull": false
277+ },
278+ "expires_at": {
279+ "name": "expires_at",
280+ "type": "bigint",
281+ "primaryKey": false,
282+ "notNull": false
283+ },
284+ "oauth_scope": {
285+ "name": "oauth_scope",
286+ "type": "text",
287+ "primaryKey": false,
288+ "notNull": false
289+ },
290+ "oauth_token_url": {
291+ "name": "oauth_token_url",
292+ "type": "text",
293+ "primaryKey": false,
294+ "notNull": false
295+ },
296+ "provider_state": {
297+ "name": "provider_state",
298+ "type": "json",
299+ "primaryKey": false,
300+ "notNull": false
301+ },
302+ "created_at": {
303+ "name": "created_at",
304+ "type": "timestamp",
305+ "primaryKey": false,
306+ "notNull": true
307+ },
308+ "updated_at": {
309+ "name": "updated_at",
310+ "type": "timestamp",
311+ "primaryKey": false,
312+ "notNull": true
313+ },
314+ "row_id": {
315+ "name": "row_id",
316+ "type": "varchar(255)",
317+ "primaryKey": true,
318+ "notNull": true
319+ },
320+ "tenant": {
321+ "name": "tenant",
322+ "type": "varchar(255)",
323+ "primaryKey": false,
324+ "notNull": true
325+ },
326+ "owner": {
327+ "name": "owner",
328+ "type": "varchar(255)",
329+ "primaryKey": false,
330+ "notNull": true
331+ },
332+ "subject": {
333+ "name": "subject",
334+ "type": "varchar(255)",
335+ "primaryKey": false,
336+ "notNull": true
337+ }
338+ },
339+ "indexes": {
340+ "connection_uidx": {
341+ "name": "connection_uidx",
342+ "columns": [
343+ {
344+ "expression": "tenant",
345+ "isExpression": false,
346+ "asc": true,
347+ "nulls": "last"
348+ },
349+ {
350+ "expression": "owner",
351+ "isExpression": false,
352+ "asc": true,
353+ "nulls": "last"
354+ },
355+ {
356+ "expression": "subject",
357+ "isExpression": false,
358+ "asc": true,
359+ "nulls": "last"
360+ },
361+ {
362+ "expression": "integration",
363+ "isExpression": false,
364+ "asc": true,
365+ "nulls": "last"
366+ },
367+ {
368+ "expression": "name",
369+ "isExpression": false,
370+ "asc": true,
371+ "nulls": "last"
372+ }
373+ ],
374+ "isUnique": true,
375+ "concurrently": false,
376+ "method": "btree",
377+ "with": {}
378+ }
379+ },
380+ "foreignKeys": {},
381+ "compositePrimaryKeys": {},
382+ "uniqueConstraints": {},
383+ "policies": {},
384+ "checkConstraints": {},
385+ "isRLSEnabled": false
386+ },
387+ "public.definition": {
388+ "name": "definition",
389+ "schema": "",
390+ "columns": {
391+ "integration": {
392+ "name": "integration",
393+ "type": "varchar(255)",
394+ "primaryKey": false,
395+ "notNull": true
396+ },
397+ "connection": {
398+ "name": "connection",
399+ "type": "varchar(255)",
400+ "primaryKey": false,
401+ "notNull": true
402+ },
403+ "plugin_id": {
404+ "name": "plugin_id",
405+ "type": "text",
406+ "primaryKey": false,
407+ "notNull": true
408+ },
409+ "name": {
410+ "name": "name",
411+ "type": "text",
412+ "primaryKey": false,
413+ "notNull": true
414+ },
415+ "schema": {
416+ "name": "schema",
417+ "type": "json",
418+ "primaryKey": false,
419+ "notNull": true
420+ },
421+ "created_at": {
422+ "name": "created_at",
423+ "type": "timestamp",
424+ "primaryKey": false,
425+ "notNull": true
426+ },
427+ "row_id": {
428+ "name": "row_id",
429+ "type": "varchar(255)",
430+ "primaryKey": true,
431+ "notNull": true
432+ },
433+ "tenant": {
434+ "name": "tenant",
435+ "type": "varchar(255)",
436+ "primaryKey": false,
437+ "notNull": true
438+ },
439+ "owner": {
440+ "name": "owner",
441+ "type": "varchar(255)",
442+ "primaryKey": false,
443+ "notNull": true
444+ },
445+ "subject": {
446+ "name": "subject",
447+ "type": "varchar(255)",
448+ "primaryKey": false,
449+ "notNull": true
450+ }
451+ },
452+ "indexes": {
453+ "definition_uidx": {
454+ "name": "definition_uidx",
455+ "columns": [
456+ {
457+ "expression": "tenant",
458+ "isExpression": false,
459+ "asc": true,
460+ "nulls": "last"
461+ },
462+ {
463+ "expression": "owner",
464+ "isExpression": false,
465+ "asc": true,
466+ "nulls": "last"
467+ },
468+ {
469+ "expression": "subject",
470+ "isExpression": false,
471+ "asc": true,
472+ "nulls": "last"
473+ },
474+ {
475+ "expression": "integration",
476+ "isExpression": false,
477+ "asc": true,
478+ "nulls": "last"
479+ },
480+ {
481+ "expression": "connection",
482+ "isExpression": false,
483+ "asc": true,
484+ "nulls": "last"
485+ },
486+ {
487+ "expression": "name",
488+ "isExpression": false,
489+ "asc": true,
490+ "nulls": "last"
491+ }
492+ ],
493+ "isUnique": true,
494+ "concurrently": false,
495+ "method": "btree",
496+ "with": {}
497+ }
498+ },
499+ "foreignKeys": {},
500+ "compositePrimaryKeys": {},
501+ "uniqueConstraints": {},
502+ "policies": {},
503+ "checkConstraints": {},
504+ "isRLSEnabled": false
505+ },
506+ "public.integration": {
507+ "name": "integration",
508+ "schema": "",
509+ "columns": {
510+ "slug": {
511+ "name": "slug",
512+ "type": "varchar(255)",
513+ "primaryKey": false,
514+ "notNull": true
515+ },
516+ "plugin_id": {
517+ "name": "plugin_id",
518+ "type": "text",
519+ "primaryKey": false,
520+ "notNull": true
521+ },
522+ "name": {
523+ "name": "name",
524+ "type": "text",
525+ "primaryKey": false,
526+ "notNull": false
527+ },
528+ "description": {
529+ "name": "description",
530+ "type": "text",
531+ "primaryKey": false,
532+ "notNull": false
533+ },
534+ "config": {
535+ "name": "config",
536+ "type": "json",
537+ "primaryKey": false,
538+ "notNull": false
539+ },
540+ "health_check": {
541+ "name": "health_check",
542+ "type": "json",
543+ "primaryKey": false,
544+ "notNull": false
545+ },
546+ "config_revised_at": {
547+ "name": "config_revised_at",
548+ "type": "bigint",
549+ "primaryKey": false,
550+ "notNull": false
551+ },
552+ "can_remove": {
553+ "name": "can_remove",
554+ "type": "boolean",
555+ "primaryKey": false,
556+ "notNull": true,
557+ "default": true
558+ },
559+ "can_refresh": {
560+ "name": "can_refresh",
561+ "type": "boolean",
562+ "primaryKey": false,
563+ "notNull": true,
564+ "default": false
565+ },
566+ "created_at": {
567+ "name": "created_at",
568+ "type": "timestamp",
569+ "primaryKey": false,
570+ "notNull": true
571+ },
572+ "updated_at": {
573+ "name": "updated_at",
574+ "type": "timestamp",
575+ "primaryKey": false,
576+ "notNull": true
577+ },
578+ "row_id": {
579+ "name": "row_id",
580+ "type": "varchar(255)",
581+ "primaryKey": true,
582+ "notNull": true
583+ },
584+ "tenant": {
585+ "name": "tenant",
586+ "type": "varchar(255)",
587+ "primaryKey": false,
588+ "notNull": true
589+ }
590+ },
591+ "indexes": {
592+ "integration_uidx": {
593+ "name": "integration_uidx",
594+ "columns": [
595+ {
596+ "expression": "tenant",
597+ "isExpression": false,
598+ "asc": true,
599+ "nulls": "last"
600+ },
601+ {
602+ "expression": "slug",
603+ "isExpression": false,
604+ "asc": true,
605+ "nulls": "last"
606+ }
607+ ],
608+ "isUnique": true,
609+ "concurrently": false,
610+ "method": "btree",
611+ "with": {}
612+ }
613+ },
614+ "foreignKeys": {},
615+ "compositePrimaryKeys": {},
616+ "uniqueConstraints": {},
617+ "policies": {},
618+ "checkConstraints": {},
619+ "isRLSEnabled": false
620+ },
621+ "public.oauth_client": {
622+ "name": "oauth_client",
623+ "schema": "",
624+ "columns": {
625+ "slug": {
626+ "name": "slug",
627+ "type": "varchar(255)",
628+ "primaryKey": false,
629+ "notNull": true
630+ },
631+ "authorization_url": {
632+ "name": "authorization_url",
633+ "type": "text",
634+ "primaryKey": false,
635+ "notNull": true
636+ },
637+ "token_url": {
638+ "name": "token_url",
639+ "type": "text",
640+ "primaryKey": false,
641+ "notNull": true
642+ },
643+ "grant": {
644+ "name": "grant",
645+ "type": "text",
646+ "primaryKey": false,
647+ "notNull": true
648+ },
649+ "client_id": {
650+ "name": "client_id",
651+ "type": "text",
652+ "primaryKey": false,
653+ "notNull": true
654+ },
655+ "client_secret_item_id": {
656+ "name": "client_secret_item_id",
657+ "type": "text",
658+ "primaryKey": false,
659+ "notNull": false
660+ },
661+ "resource": {
662+ "name": "resource",
663+ "type": "text",
664+ "primaryKey": false,
665+ "notNull": false
666+ },
667+ "origin_kind": {
668+ "name": "origin_kind",
669+ "type": "text",
670+ "primaryKey": false,
671+ "notNull": false
672+ },
673+ "origin_integration": {
674+ "name": "origin_integration",
675+ "type": "text",
676+ "primaryKey": false,
677+ "notNull": false
678+ },
679+ "origin_issuer": {
680+ "name": "origin_issuer",
681+ "type": "text",
682+ "primaryKey": false,
683+ "notNull": false
684+ },
685+ "origin_redirect_uri": {
686+ "name": "origin_redirect_uri",
687+ "type": "text",
688+ "primaryKey": false,
689+ "notNull": false
690+ },
691+ "created_at": {
692+ "name": "created_at",
693+ "type": "timestamp",
694+ "primaryKey": false,
695+ "notNull": true
696+ },
697+ "row_id": {
698+ "name": "row_id",
699+ "type": "varchar(255)",
700+ "primaryKey": true,
701+ "notNull": true
702+ },
703+ "tenant": {
704+ "name": "tenant",
705+ "type": "varchar(255)",
706+ "primaryKey": false,
707+ "notNull": true
708+ },
709+ "owner": {
710+ "name": "owner",
711+ "type": "varchar(255)",
712+ "primaryKey": false,
713+ "notNull": true
714+ },
715+ "subject": {
716+ "name": "subject",
717+ "type": "varchar(255)",
718+ "primaryKey": false,
719+ "notNull": true
720+ }
721+ },
722+ "indexes": {
723+ "oauth_client_uidx": {
724+ "name": "oauth_client_uidx",
725+ "columns": [
726+ {
727+ "expression": "tenant",
728+ "isExpression": false,
729+ "asc": true,
730+ "nulls": "last"
731+ },
732+ {
733+ "expression": "owner",
734+ "isExpression": false,
735+ "asc": true,
736+ "nulls": "last"
737+ },
738+ {
739+ "expression": "subject",
740+ "isExpression": false,
741+ "asc": true,
742+ "nulls": "last"
743+ },
744+ {
745+ "expression": "slug",
746+ "isExpression": false,
747+ "asc": true,
748+ "nulls": "last"
749+ }
750+ ],
751+ "isUnique": true,
752+ "concurrently": false,
753+ "method": "btree",
754+ "with": {}
755+ }
756+ },
757+ "foreignKeys": {},
758+ "compositePrimaryKeys": {},
759+ "uniqueConstraints": {},
760+ "policies": {},
761+ "checkConstraints": {},
762+ "isRLSEnabled": false
763+ },
764+ "public.oauth_session": {
765+ "name": "oauth_session",
766+ "schema": "",
767+ "columns": {
768+ "state": {
769+ "name": "state",
770+ "type": "varchar(255)",
771+ "primaryKey": false,
772+ "notNull": true
773+ },
774+ "client_slug": {
775+ "name": "client_slug",
776+ "type": "text",
777+ "primaryKey": false,
778+ "notNull": true
779+ },
780+ "integration": {
781+ "name": "integration",
782+ "type": "text",
783+ "primaryKey": false,
784+ "notNull": true
785+ },
786+ "name": {
787+ "name": "name",
788+ "type": "text",
789+ "primaryKey": false,
790+ "notNull": true
791+ },
792+ "template": {
793+ "name": "template",
794+ "type": "text",
795+ "primaryKey": false,
796+ "notNull": true
797+ },
798+ "redirect_url": {
799+ "name": "redirect_url",
800+ "type": "text",
801+ "primaryKey": false,
802+ "notNull": true
803+ },
804+ "pkce_verifier": {
805+ "name": "pkce_verifier",
806+ "type": "text",
807+ "primaryKey": false,
808+ "notNull": false
809+ },
810+ "identity_label": {
811+ "name": "identity_label",
812+ "type": "text",
813+ "primaryKey": false,
814+ "notNull": false
815+ },
816+ "payload": {
817+ "name": "payload",
818+ "type": "json",
819+ "primaryKey": false,
820+ "notNull": true
821+ },
822+ "expires_at": {
823+ "name": "expires_at",
824+ "type": "bigint",
825+ "primaryKey": false,
826+ "notNull": true
827+ },
828+ "created_at": {
829+ "name": "created_at",
830+ "type": "timestamp",
831+ "primaryKey": false,
832+ "notNull": true
833+ },
834+ "row_id": {
835+ "name": "row_id",
836+ "type": "varchar(255)",
837+ "primaryKey": true,
838+ "notNull": true
839+ },
840+ "tenant": {
841+ "name": "tenant",
842+ "type": "varchar(255)",
843+ "primaryKey": false,
844+ "notNull": true
845+ },
846+ "owner": {
847+ "name": "owner",
848+ "type": "varchar(255)",
849+ "primaryKey": false,
850+ "notNull": true
851+ },
852+ "subject": {
853+ "name": "subject",
854+ "type": "varchar(255)",
855+ "primaryKey": false,
856+ "notNull": true
857+ }
858+ },
859+ "indexes": {
860+ "oauth_session_uidx": {
861+ "name": "oauth_session_uidx",
862+ "columns": [
863+ {
864+ "expression": "tenant",
865+ "isExpression": false,
866+ "asc": true,
867+ "nulls": "last"
868+ },
869+ {
870+ "expression": "state",
871+ "isExpression": false,
872+ "asc": true,
873+ "nulls": "last"
874+ }
875+ ],
876+ "isUnique": true,
877+ "concurrently": false,
878+ "method": "btree",
879+ "with": {}
880+ }
881+ },
882+ "foreignKeys": {},
883+ "compositePrimaryKeys": {},
884+ "uniqueConstraints": {},
885+ "policies": {},
886+ "checkConstraints": {},
887+ "isRLSEnabled": false
888+ },
889+ "public.plugin_storage": {
890+ "name": "plugin_storage",
891+ "schema": "",
892+ "columns": {
893+ "plugin_id": {
894+ "name": "plugin_id",
895+ "type": "varchar(255)",
896+ "primaryKey": false,
897+ "notNull": true
898+ },
899+ "collection": {
900+ "name": "collection",
901+ "type": "varchar(255)",
902+ "primaryKey": false,
903+ "notNull": true
904+ },
905+ "key": {
906+ "name": "key",
907+ "type": "varchar(255)",
908+ "primaryKey": false,
909+ "notNull": true
910+ },
911+ "data": {
912+ "name": "data",
913+ "type": "json",
914+ "primaryKey": false,
915+ "notNull": true
916+ },
917+ "created_at": {
918+ "name": "created_at",
919+ "type": "timestamp",
920+ "primaryKey": false,
921+ "notNull": true
922+ },
923+ "updated_at": {
924+ "name": "updated_at",
925+ "type": "timestamp",
926+ "primaryKey": false,
927+ "notNull": true
928+ },
929+ "row_id": {
930+ "name": "row_id",
931+ "type": "varchar(255)",
932+ "primaryKey": true,
933+ "notNull": true
934+ },
935+ "tenant": {
936+ "name": "tenant",
937+ "type": "varchar(255)",
938+ "primaryKey": false,
939+ "notNull": true
940+ },
941+ "owner": {
942+ "name": "owner",
943+ "type": "varchar(255)",
944+ "primaryKey": false,
945+ "notNull": true
946+ },
947+ "subject": {
948+ "name": "subject",
949+ "type": "varchar(255)",
950+ "primaryKey": false,
951+ "notNull": true
952+ }
953+ },
954+ "indexes": {
955+ "plugin_storage_uidx": {
956+ "name": "plugin_storage_uidx",
957+ "columns": [
958+ {
959+ "expression": "tenant",
960+ "isExpression": false,
961+ "asc": true,
962+ "nulls": "last"
963+ },
964+ {
965+ "expression": "owner",
966+ "isExpression": false,
967+ "asc": true,
968+ "nulls": "last"
969+ },
970+ {
971+ "expression": "subject",
972+ "isExpression": false,
973+ "asc": true,
974+ "nulls": "last"
975+ },
976+ {
977+ "expression": "plugin_id",
978+ "isExpression": false,
979+ "asc": true,
980+ "nulls": "last"
981+ },
982+ {
983+ "expression": "collection",
984+ "isExpression": false,
985+ "asc": true,
986+ "nulls": "last"
987+ },
988+ {
989+ "expression": "key",
990+ "isExpression": false,
991+ "asc": true,
992+ "nulls": "last"
993+ }
994+ ],
995+ "isUnique": true,
996+ "concurrently": false,
997+ "method": "btree",
998+ "with": {}
999+ }
1000+ },
1001+ "foreignKeys": {},
1002+ "compositePrimaryKeys": {},
1003+ "uniqueConstraints": {},
1004+ "policies": {},
1005+ "checkConstraints": {},
1006+ "isRLSEnabled": false
1007+ },
1008+ "public.private_executor_cloud_settings": {
1009+ "name": "private_executor_cloud_settings",
1010+ "schema": "",
1011+ "columns": {
1012+ "id": {
1013+ "name": "id",
1014+ "type": "varchar(255)",
1015+ "primaryKey": true,
1016+ "notNull": true
1017+ },
1018+ "version": {
1019+ "name": "version",
1020+ "type": "varchar(255)",
1021+ "primaryKey": false,
1022+ "notNull": true,
1023+ "default": "'1.0.0'"
1024+ }
1025+ },
1026+ "indexes": {},
1027+ "foreignKeys": {},
1028+ "compositePrimaryKeys": {},
1029+ "uniqueConstraints": {},
1030+ "policies": {},
1031+ "checkConstraints": {},
1032+ "isRLSEnabled": false
1033+ },
1034+ "public.tool": {
1035+ "name": "tool",
1036+ "schema": "",
1037+ "columns": {
1038+ "integration": {
1039+ "name": "integration",
1040+ "type": "varchar(255)",
1041+ "primaryKey": false,
1042+ "notNull": true
1043+ },
1044+ "connection": {
1045+ "name": "connection",
1046+ "type": "varchar(255)",
1047+ "primaryKey": false,
1048+ "notNull": true
1049+ },
1050+ "plugin_id": {
1051+ "name": "plugin_id",
1052+ "type": "text",
1053+ "primaryKey": false,
1054+ "notNull": true
1055+ },
1056+ "name": {
1057+ "name": "name",
1058+ "type": "varchar(255)",
1059+ "primaryKey": false,
1060+ "notNull": true
1061+ },
1062+ "description": {
1063+ "name": "description",
1064+ "type": "text",
1065+ "primaryKey": false,
1066+ "notNull": true
1067+ },
1068+ "input_schema": {
1069+ "name": "input_schema",
1070+ "type": "json",
1071+ "primaryKey": false,
1072+ "notNull": false
1073+ },
1074+ "output_schema": {
1075+ "name": "output_schema",
1076+ "type": "json",
1077+ "primaryKey": false,
1078+ "notNull": false
1079+ },
1080+ "annotations": {
1081+ "name": "annotations",
1082+ "type": "json",
1083+ "primaryKey": false,
1084+ "notNull": false
1085+ },
1086+ "created_at": {
1087+ "name": "created_at",
1088+ "type": "timestamp",
1089+ "primaryKey": false,
1090+ "notNull": true
1091+ },
1092+ "updated_at": {
1093+ "name": "updated_at",
1094+ "type": "timestamp",
1095+ "primaryKey": false,
1096+ "notNull": true
1097+ },
1098+ "row_id": {
1099+ "name": "row_id",
1100+ "type": "varchar(255)",
1101+ "primaryKey": true,
1102+ "notNull": true
1103+ },
1104+ "tenant": {
1105+ "name": "tenant",
1106+ "type": "varchar(255)",
1107+ "primaryKey": false,
1108+ "notNull": true
1109+ },
1110+ "owner": {
1111+ "name": "owner",
1112+ "type": "varchar(255)",
1113+ "primaryKey": false,
1114+ "notNull": true
1115+ },
1116+ "subject": {
1117+ "name": "subject",
1118+ "type": "varchar(255)",
1119+ "primaryKey": false,
1120+ "notNull": true
1121+ }
1122+ },
1123+ "indexes": {
1124+ "tool_uidx": {
1125+ "name": "tool_uidx",
1126+ "columns": [
1127+ {
1128+ "expression": "tenant",
1129+ "isExpression": false,
1130+ "asc": true,
1131+ "nulls": "last"
1132+ },
1133+ {
1134+ "expression": "owner",
1135+ "isExpression": false,
1136+ "asc": true,
1137+ "nulls": "last"
1138+ },
1139+ {
1140+ "expression": "subject",
1141+ "isExpression": false,
1142+ "asc": true,
1143+ "nulls": "last"
1144+ },
1145+ {
1146+ "expression": "integration",
1147+ "isExpression": false,
1148+ "asc": true,
1149+ "nulls": "last"
1150+ },
1151+ {
1152+ "expression": "connection",
1153+ "isExpression": false,
1154+ "asc": true,
1155+ "nulls": "last"
1156+ },
1157+ {
1158+ "expression": "name",
1159+ "isExpression": false,
1160+ "asc": true,
1161+ "nulls": "last"
1162+ }
1163+ ],
1164+ "isUnique": true,
1165+ "concurrently": false,
1166+ "method": "btree",
1167+ "with": {}
1168+ }
1169+ },
1170+ "foreignKeys": {},
1171+ "compositePrimaryKeys": {},
1172+ "uniqueConstraints": {},
1173+ "policies": {},
1174+ "checkConstraints": {},
1175+ "isRLSEnabled": false
1176+ },
1177+ "public.tool_policy": {
1178+ "name": "tool_policy",
1179+ "schema": "",
1180+ "columns": {
1181+ "id": {
1182+ "name": "id",
1183+ "type": "varchar(255)",
1184+ "primaryKey": false,
1185+ "notNull": true
1186+ },
1187+ "pattern": {
1188+ "name": "pattern",
1189+ "type": "text",
1190+ "primaryKey": false,
1191+ "notNull": true
1192+ },
1193+ "action": {
1194+ "name": "action",
1195+ "type": "text",
1196+ "primaryKey": false,
1197+ "notNull": true
1198+ },
1199+ "position": {
1200+ "name": "position",
1201+ "type": "text",
1202+ "primaryKey": false,
1203+ "notNull": true
1204+ },
1205+ "created_at": {
1206+ "name": "created_at",
1207+ "type": "timestamp",
1208+ "primaryKey": false,
1209+ "notNull": true
1210+ },
1211+ "updated_at": {
1212+ "name": "updated_at",
1213+ "type": "timestamp",
1214+ "primaryKey": false,
1215+ "notNull": true
1216+ },
1217+ "row_id": {
1218+ "name": "row_id",
1219+ "type": "varchar(255)",
1220+ "primaryKey": true,
1221+ "notNull": true
1222+ },
1223+ "tenant": {
1224+ "name": "tenant",
1225+ "type": "varchar(255)",
1226+ "primaryKey": false,
1227+ "notNull": true
1228+ },
1229+ "owner": {
1230+ "name": "owner",
1231+ "type": "varchar(255)",
1232+ "primaryKey": false,
1233+ "notNull": true
1234+ },
1235+ "subject": {
1236+ "name": "subject",
1237+ "type": "varchar(255)",
1238+ "primaryKey": false,
1239+ "notNull": true
1240+ }
1241+ },
1242+ "indexes": {
1243+ "tool_policy_uidx": {
1244+ "name": "tool_policy_uidx",
1245+ "columns": [
1246+ {
1247+ "expression": "tenant",
1248+ "isExpression": false,
1249+ "asc": true,
1250+ "nulls": "last"
1251+ },
1252+ {
1253+ "expression": "owner",
1254+ "isExpression": false,
1255+ "asc": true,
1256+ "nulls": "last"
1257+ },
1258+ {
1259+ "expression": "subject",
1260+ "isExpression": false,
1261+ "asc": true,
1262+ "nulls": "last"
1263+ },
1264+ {
1265+ "expression": "id",
1266+ "isExpression": false,
1267+ "asc": true,
1268+ "nulls": "last"
1269+ }
1270+ ],
1271+ "isUnique": true,
1272+ "concurrently": false,
1273+ "method": "btree",
1274+ "with": {}
1275+ }
1276+ },
1277+ "foreignKeys": {},
1278+ "compositePrimaryKeys": {},
1279+ "uniqueConstraints": {},
1280+ "policies": {},
1281+ "checkConstraints": {},
1282+ "isRLSEnabled": false
1283+ }
1284+ },
1285+ "enums": {},
1286+ "schemas": {},
1287+ "sequences": {},
1288+ "roles": {},
1289+ "policies": {},
1290+ "views": {},
1291+ "_meta": {
1292+ "columns": {},
1293+ "schemas": {},
1294+ "tables": {}
1295+ }
1296+}
apps/cloud/drizzle/meta/_journal.json+7−0
@@ -78,6 +78,13 @@ · 77 unmodified lines
7878 "when": 1783660669921,
7979 "tag": "0010_ndjson_output_arrays",
8080 "breakpoints": true
81+ },
82+ {
83+ "idx": 11,
84+ "version": "7",
85+ "when": 1784581232732,
86+ "tag": "0011_tense_caretaker",
87+ "breakpoints": true
8188 }
8289 ]
8390 }
apps/cloud/src/db/executor-schema.ts+1−0
@@ -86,6 +86,7 @@ export const oauth_client = pgTable( · 85 unmodified lines
8686 origin_kind: text("origin_kind"),
8787 origin_integration: text("origin_integration"),
8888 origin_issuer: text("origin_issuer"),
89+ origin_redirect_uri: text("origin_redirect_uri"),
8990 created_at: timestamp("created_at").notNull(),
9091 row_id: varchar("row_id", { length: 255 })
9192 .primaryKey()
apps/local/drizzle/0004_cultured_shadowcat.sql+1−0
@@ -0,0 +1 @@
1+ALTER TABLE `oauth_client` ADD `origin_redirect_uri` text;
apps/local/drizzle/meta/0004_snapshot.json+927−0
@@ -0,0 +1,927 @@
1+{
2+ "version": "6",
3+ "dialect": "sqlite",
4+ "id": "85f30981-cda9-45df-b757-b2836fb70e07",
5+ "prevId": "80ff1a5d-ab69-416c-868e-2bf6609fa5b4",
6+ "tables": {
7+ "blob": {
8+ "name": "blob",
9+ "columns": {
10+ "namespace": {
11+ "name": "namespace",
12+ "type": "text",
13+ "primaryKey": false,
14+ "notNull": true,
15+ "autoincrement": false
16+ },
17+ "key": {
18+ "name": "key",
19+ "type": "text",
20+ "primaryKey": false,
21+ "notNull": true,
22+ "autoincrement": false
23+ },
24+ "value": {
25+ "name": "value",
26+ "type": "text",
27+ "primaryKey": false,
28+ "notNull": true,
29+ "autoincrement": false
30+ },
31+ "row_id": {
32+ "name": "row_id",
33+ "type": "text",
34+ "primaryKey": true,
35+ "notNull": true,
36+ "autoincrement": false
37+ },
38+ "id": {
39+ "name": "id",
40+ "type": "text",
41+ "primaryKey": false,
42+ "notNull": true,
43+ "autoincrement": false
44+ }
45+ },
46+ "indexes": {
47+ "blob_id_uidx": {
48+ "name": "blob_id_uidx",
49+ "columns": ["id"],
50+ "isUnique": true
51+ }
52+ },
53+ "foreignKeys": {},
54+ "compositePrimaryKeys": {},
55+ "uniqueConstraints": {},
56+ "checkConstraints": {}
57+ },
58+ "connection": {
59+ "name": "connection",
60+ "columns": {
61+ "integration": {
62+ "name": "integration",
63+ "type": "text",
64+ "primaryKey": false,
65+ "notNull": true,
66+ "autoincrement": false
67+ },
68+ "name": {
69+ "name": "name",
70+ "type": "text",
71+ "primaryKey": false,
72+ "notNull": true,
73+ "autoincrement": false
74+ },
75+ "template": {
76+ "name": "template",
77+ "type": "text",
78+ "primaryKey": false,
79+ "notNull": true,
80+ "autoincrement": false
81+ },
82+ "provider": {
83+ "name": "provider",
84+ "type": "text",
85+ "primaryKey": false,
86+ "notNull": true,
87+ "autoincrement": false
88+ },
89+ "item_ids": {
90+ "name": "item_ids",
91+ "type": "text",
92+ "primaryKey": false,
93+ "notNull": true,
94+ "autoincrement": false
95+ },
96+ "identity_label": {
97+ "name": "identity_label",
98+ "type": "text",
99+ "primaryKey": false,
100+ "notNull": false,
101+ "autoincrement": false
102+ },
103+ "oauth_client": {
104+ "name": "oauth_client",
105+ "type": "text",
106+ "primaryKey": false,
107+ "notNull": false,
108+ "autoincrement": false
109+ },
110+ "oauth_client_owner": {
111+ "name": "oauth_client_owner",
112+ "type": "text",
113+ "primaryKey": false,
114+ "notNull": false,
115+ "autoincrement": false
116+ },
117+ "refresh_item_id": {
118+ "name": "refresh_item_id",
119+ "type": "text",
120+ "primaryKey": false,
121+ "notNull": false,
122+ "autoincrement": false
123+ },
124+ "expires_at": {
125+ "name": "expires_at",
126+ "type": "blob",
127+ "primaryKey": false,
128+ "notNull": false,
129+ "autoincrement": false
130+ },
131+ "oauth_scope": {
132+ "name": "oauth_scope",
133+ "type": "text",
134+ "primaryKey": false,
135+ "notNull": false,
136+ "autoincrement": false
137+ },
138+ "oauth_token_url": {
139+ "name": "oauth_token_url",
140+ "type": "text",
141+ "primaryKey": false,
142+ "notNull": false,
143+ "autoincrement": false
144+ },
145+ "provider_state": {
146+ "name": "provider_state",
147+ "type": "text",
148+ "primaryKey": false,
149+ "notNull": false,
150+ "autoincrement": false
151+ },
152+ "created_at": {
153+ "name": "created_at",
154+ "type": "integer",
155+ "primaryKey": false,
156+ "notNull": true,
157+ "autoincrement": false
158+ },
159+ "updated_at": {
160+ "name": "updated_at",
161+ "type": "integer",
162+ "primaryKey": false,
163+ "notNull": true,
164+ "autoincrement": false
165+ },
166+ "row_id": {
167+ "name": "row_id",
168+ "type": "text",
169+ "primaryKey": true,
170+ "notNull": true,
171+ "autoincrement": false
172+ },
173+ "tenant": {
174+ "name": "tenant",
175+ "type": "text",
176+ "primaryKey": false,
177+ "notNull": true,
178+ "autoincrement": false
179+ },
180+ "owner": {
181+ "name": "owner",
182+ "type": "text",
183+ "primaryKey": false,
184+ "notNull": true,
185+ "autoincrement": false
186+ },
187+ "subject": {
188+ "name": "subject",
189+ "type": "text",
190+ "primaryKey": false,
191+ "notNull": true,
192+ "autoincrement": false
193+ }
194+ },
195+ "indexes": {
196+ "connection_uidx": {
197+ "name": "connection_uidx",
198+ "columns": ["tenant", "owner", "subject", "integration", "name"],
199+ "isUnique": true
200+ }
201+ },
202+ "foreignKeys": {},
203+ "compositePrimaryKeys": {},
204+ "uniqueConstraints": {},
205+ "checkConstraints": {}
206+ },
207+ "definition": {
208+ "name": "definition",
209+ "columns": {
210+ "integration": {
211+ "name": "integration",
212+ "type": "text",
213+ "primaryKey": false,
214+ "notNull": true,
215+ "autoincrement": false
216+ },
217+ "connection": {
218+ "name": "connection",
219+ "type": "text",
220+ "primaryKey": false,
221+ "notNull": true,
222+ "autoincrement": false
223+ },
224+ "plugin_id": {
225+ "name": "plugin_id",
226+ "type": "text",
227+ "primaryKey": false,
228+ "notNull": true,
229+ "autoincrement": false
230+ },
231+ "name": {
232+ "name": "name",
233+ "type": "text",
234+ "primaryKey": false,
235+ "notNull": true,
236+ "autoincrement": false
237+ },
238+ "schema": {
239+ "name": "schema",
240+ "type": "text",
241+ "primaryKey": false,
242+ "notNull": true,
243+ "autoincrement": false
244+ },
245+ "created_at": {
246+ "name": "created_at",
247+ "type": "integer",
248+ "primaryKey": false,
249+ "notNull": true,
250+ "autoincrement": false
251+ },
252+ "row_id": {
253+ "name": "row_id",
254+ "type": "text",
255+ "primaryKey": true,
256+ "notNull": true,
257+ "autoincrement": false
258+ },
259+ "tenant": {
260+ "name": "tenant",
261+ "type": "text",
262+ "primaryKey": false,
263+ "notNull": true,
264+ "autoincrement": false
265+ },
266+ "owner": {
267+ "name": "owner",
268+ "type": "text",
269+ "primaryKey": false,
270+ "notNull": true,
271+ "autoincrement": false
272+ },
273+ "subject": {
274+ "name": "subject",
275+ "type": "text",
276+ "primaryKey": false,
277+ "notNull": true,
278+ "autoincrement": false
279+ }
280+ },
281+ "indexes": {
282+ "definition_uidx": {
283+ "name": "definition_uidx",
284+ "columns": ["tenant", "owner", "subject", "integration", "connection", "name"],
285+ "isUnique": true
286+ }
287+ },
288+ "foreignKeys": {},
289+ "compositePrimaryKeys": {},
290+ "uniqueConstraints": {},
291+ "checkConstraints": {}
292+ },
293+ "integration": {
294+ "name": "integration",
295+ "columns": {
296+ "slug": {
297+ "name": "slug",
298+ "type": "text",
299+ "primaryKey": false,
300+ "notNull": true,
301+ "autoincrement": false
302+ },
303+ "plugin_id": {
304+ "name": "plugin_id",
305+ "type": "text",
306+ "primaryKey": false,
307+ "notNull": true,
308+ "autoincrement": false
309+ },
310+ "description": {
311+ "name": "description",
312+ "type": "text",
313+ "primaryKey": false,
314+ "notNull": true,
315+ "autoincrement": false
316+ },
317+ "config": {
318+ "name": "config",
319+ "type": "text",
320+ "primaryKey": false,
321+ "notNull": false,
322+ "autoincrement": false
323+ },
324+ "can_remove": {
325+ "name": "can_remove",
326+ "type": "integer",
327+ "primaryKey": false,
328+ "notNull": true,
329+ "autoincrement": false,
330+ "default": 1
331+ },
332+ "can_refresh": {
333+ "name": "can_refresh",
334+ "type": "integer",
335+ "primaryKey": false,
336+ "notNull": true,
337+ "autoincrement": false,
338+ "default": 0
339+ },
340+ "created_at": {
341+ "name": "created_at",
342+ "type": "integer",
343+ "primaryKey": false,
344+ "notNull": true,
345+ "autoincrement": false
346+ },
347+ "updated_at": {
348+ "name": "updated_at",
349+ "type": "integer",
350+ "primaryKey": false,
351+ "notNull": true,
352+ "autoincrement": false
353+ },
354+ "row_id": {
355+ "name": "row_id",
356+ "type": "text",
357+ "primaryKey": true,
358+ "notNull": true,
359+ "autoincrement": false
360+ },
361+ "tenant": {
362+ "name": "tenant",
363+ "type": "text",
364+ "primaryKey": false,
365+ "notNull": true,
366+ "autoincrement": false
367+ }
368+ },
369+ "indexes": {
370+ "integration_uidx": {
371+ "name": "integration_uidx",
372+ "columns": ["tenant", "slug"],
373+ "isUnique": true
374+ }
375+ },
376+ "foreignKeys": {},
377+ "compositePrimaryKeys": {},
378+ "uniqueConstraints": {},
379+ "checkConstraints": {}
380+ },
381+ "oauth_client": {
382+ "name": "oauth_client",
383+ "columns": {
384+ "slug": {
385+ "name": "slug",
386+ "type": "text",
387+ "primaryKey": false,
388+ "notNull": true,
389+ "autoincrement": false
390+ },
391+ "authorization_url": {
392+ "name": "authorization_url",
393+ "type": "text",
394+ "primaryKey": false,
395+ "notNull": true,
396+ "autoincrement": false
397+ },
398+ "token_url": {
399+ "name": "token_url",
400+ "type": "text",
401+ "primaryKey": false,
402+ "notNull": true,
403+ "autoincrement": false
404+ },
405+ "grant": {
406+ "name": "grant",
407+ "type": "text",
408+ "primaryKey": false,
409+ "notNull": true,
410+ "autoincrement": false
411+ },
412+ "client_id": {
413+ "name": "client_id",
414+ "type": "text",
415+ "primaryKey": false,
416+ "notNull": true,
417+ "autoincrement": false
418+ },
419+ "client_secret_item_id": {
420+ "name": "client_secret_item_id",
421+ "type": "text",
422+ "primaryKey": false,
423+ "notNull": false,
424+ "autoincrement": false
425+ },
426+ "resource": {
427+ "name": "resource",
428+ "type": "text",
429+ "primaryKey": false,
430+ "notNull": false,
431+ "autoincrement": false
432+ },
433+ "origin_kind": {
434+ "name": "origin_kind",
435+ "type": "text",
436+ "primaryKey": false,
437+ "notNull": false,
438+ "autoincrement": false
439+ },
440+ "origin_integration": {
441+ "name": "origin_integration",
442+ "type": "text",
443+ "primaryKey": false,
444+ "notNull": false,
445+ "autoincrement": false
446+ },
447+ "origin_issuer": {
448+ "name": "origin_issuer",
449+ "type": "text",
450+ "primaryKey": false,
451+ "notNull": false,
452+ "autoincrement": false
453+ },
454+ "origin_redirect_uri": {
455+ "name": "origin_redirect_uri",
456+ "type": "text",
457+ "primaryKey": false,
458+ "notNull": false,
459+ "autoincrement": false
460+ },
461+ "created_at": {
462+ "name": "created_at",
463+ "type": "integer",
464+ "primaryKey": false,
465+ "notNull": true,
466+ "autoincrement": false
467+ },
468+ "row_id": {
469+ "name": "row_id",
470+ "type": "text",
471+ "primaryKey": true,
472+ "notNull": true,
473+ "autoincrement": false
474+ },
475+ "tenant": {
476+ "name": "tenant",
477+ "type": "text",
478+ "primaryKey": false,
479+ "notNull": true,
480+ "autoincrement": false
481+ },
482+ "owner": {
483+ "name": "owner",
484+ "type": "text",
485+ "primaryKey": false,
486+ "notNull": true,
487+ "autoincrement": false
488+ },
489+ "subject": {
490+ "name": "subject",
491+ "type": "text",
492+ "primaryKey": false,
493+ "notNull": true,
494+ "autoincrement": false
495+ }
496+ },
497+ "indexes": {
498+ "oauth_client_uidx": {
499+ "name": "oauth_client_uidx",
500+ "columns": ["tenant", "owner", "subject", "slug"],
501+ "isUnique": true
502+ }
503+ },
504+ "foreignKeys": {},
505+ "compositePrimaryKeys": {},
506+ "uniqueConstraints": {},
507+ "checkConstraints": {}
508+ },
509+ "oauth_session": {
510+ "name": "oauth_session",
511+ "columns": {
512+ "state": {
513+ "name": "state",
514+ "type": "text",
515+ "primaryKey": false,
516+ "notNull": true,
517+ "autoincrement": false
518+ },
519+ "client_slug": {
520+ "name": "client_slug",
521+ "type": "text",
522+ "primaryKey": false,
523+ "notNull": true,
524+ "autoincrement": false
525+ },
526+ "integration": {
527+ "name": "integration",
528+ "type": "text",
529+ "primaryKey": false,
530+ "notNull": true,
531+ "autoincrement": false
532+ },
533+ "name": {
534+ "name": "name",
535+ "type": "text",
536+ "primaryKey": false,
537+ "notNull": true,
538+ "autoincrement": false
539+ },
540+ "template": {
541+ "name": "template",
542+ "type": "text",
543+ "primaryKey": false,
544+ "notNull": true,
545+ "autoincrement": false
546+ },
547+ "redirect_url": {
548+ "name": "redirect_url",
549+ "type": "text",
550+ "primaryKey": false,
551+ "notNull": true,
552+ "autoincrement": false
553+ },
554+ "pkce_verifier": {
555+ "name": "pkce_verifier",
556+ "type": "text",
557+ "primaryKey": false,
558+ "notNull": false,
559+ "autoincrement": false
560+ },
561+ "identity_label": {
562+ "name": "identity_label",
563+ "type": "text",
564+ "primaryKey": false,
565+ "notNull": false,
566+ "autoincrement": false
567+ },
568+ "payload": {
569+ "name": "payload",
570+ "type": "text",
571+ "primaryKey": false,
572+ "notNull": true,
573+ "autoincrement": false
574+ },
575+ "expires_at": {
576+ "name": "expires_at",
577+ "type": "blob",
578+ "primaryKey": false,
579+ "notNull": true,
580+ "autoincrement": false
581+ },
582+ "created_at": {
583+ "name": "created_at",
584+ "type": "integer",
585+ "primaryKey": false,
586+ "notNull": true,
587+ "autoincrement": false
588+ },
589+ "row_id": {
590+ "name": "row_id",
591+ "type": "text",
592+ "primaryKey": true,
593+ "notNull": true,
594+ "autoincrement": false
595+ },
596+ "tenant": {
597+ "name": "tenant",
598+ "type": "text",
599+ "primaryKey": false,
600+ "notNull": true,
601+ "autoincrement": false
602+ },
603+ "owner": {
604+ "name": "owner",
605+ "type": "text",
606+ "primaryKey": false,
607+ "notNull": true,
608+ "autoincrement": false
609+ },
610+ "subject": {
611+ "name": "subject",
612+ "type": "text",
613+ "primaryKey": false,
614+ "notNull": true,
615+ "autoincrement": false
616+ }
617+ },
618+ "indexes": {
619+ "oauth_session_uidx": {
620+ "name": "oauth_session_uidx",
621+ "columns": ["tenant", "state"],
622+ "isUnique": true
623+ }
624+ },
625+ "foreignKeys": {},
626+ "compositePrimaryKeys": {},
627+ "uniqueConstraints": {},
628+ "checkConstraints": {}
629+ },
630+ "plugin_storage": {
631+ "name": "plugin_storage",
632+ "columns": {
633+ "plugin_id": {
634+ "name": "plugin_id",
635+ "type": "text",
636+ "primaryKey": false,
637+ "notNull": true,
638+ "autoincrement": false
639+ },
640+ "collection": {
641+ "name": "collection",
642+ "type": "text",
643+ "primaryKey": false,
644+ "notNull": true,
645+ "autoincrement": false
646+ },
647+ "key": {
648+ "name": "key",
649+ "type": "text",
650+ "primaryKey": false,
651+ "notNull": true,
652+ "autoincrement": false
653+ },
654+ "data": {
655+ "name": "data",
656+ "type": "text",
657+ "primaryKey": false,
658+ "notNull": true,
659+ "autoincrement": false
660+ },
661+ "created_at": {
662+ "name": "created_at",
663+ "type": "integer",
664+ "primaryKey": false,
665+ "notNull": true,
666+ "autoincrement": false
667+ },
668+ "updated_at": {
669+ "name": "updated_at",
670+ "type": "integer",
671+ "primaryKey": false,
672+ "notNull": true,
673+ "autoincrement": false
674+ },
675+ "row_id": {
676+ "name": "row_id",
677+ "type": "text",
678+ "primaryKey": true,
679+ "notNull": true,
680+ "autoincrement": false
681+ },
682+ "tenant": {
683+ "name": "tenant",
684+ "type": "text",
685+ "primaryKey": false,
686+ "notNull": true,
687+ "autoincrement": false
688+ },
689+ "owner": {
690+ "name": "owner",
691+ "type": "text",
692+ "primaryKey": false,
693+ "notNull": true,
694+ "autoincrement": false
695+ },
696+ "subject": {
697+ "name": "subject",
698+ "type": "text",
699+ "primaryKey": false,
700+ "notNull": true,
701+ "autoincrement": false
702+ }
703+ },
704+ "indexes": {
705+ "plugin_storage_uidx": {
706+ "name": "plugin_storage_uidx",
707+ "columns": ["tenant", "owner", "subject", "plugin_id", "collection", "key"],
708+ "isUnique": true
709+ }
710+ },
711+ "foreignKeys": {},
712+ "compositePrimaryKeys": {},
713+ "uniqueConstraints": {},
714+ "checkConstraints": {}
715+ },
716+ "tool": {
717+ "name": "tool",
718+ "columns": {
719+ "integration": {
720+ "name": "integration",
721+ "type": "text",
722+ "primaryKey": false,
723+ "notNull": true,
724+ "autoincrement": false
725+ },
726+ "connection": {
727+ "name": "connection",
728+ "type": "text",
729+ "primaryKey": false,
730+ "notNull": true,
731+ "autoincrement": false
732+ },
733+ "plugin_id": {
734+ "name": "plugin_id",
735+ "type": "text",
736+ "primaryKey": false,
737+ "notNull": true,
738+ "autoincrement": false
739+ },
740+ "name": {
741+ "name": "name",
742+ "type": "text",
743+ "primaryKey": false,
744+ "notNull": true,
745+ "autoincrement": false
746+ },
747+ "description": {
748+ "name": "description",
749+ "type": "text",
750+ "primaryKey": false,
751+ "notNull": true,
752+ "autoincrement": false
753+ },
754+ "input_schema": {
755+ "name": "input_schema",
756+ "type": "text",
757+ "primaryKey": false,
758+ "notNull": false,
759+ "autoincrement": false
760+ },
761+ "output_schema": {
762+ "name": "output_schema",
763+ "type": "text",
764+ "primaryKey": false,
765+ "notNull": false,
766+ "autoincrement": false
767+ },
768+ "annotations": {
769+ "name": "annotations",
770+ "type": "text",
771+ "primaryKey": false,
772+ "notNull": false,
773+ "autoincrement": false
774+ },
775+ "created_at": {
776+ "name": "created_at",
777+ "type": "integer",
778+ "primaryKey": false,
779+ "notNull": true,
780+ "autoincrement": false
781+ },
782+ "updated_at": {
783+ "name": "updated_at",
784+ "type": "integer",
785+ "primaryKey": false,
786+ "notNull": true,
787+ "autoincrement": false
788+ },
789+ "row_id": {
790+ "name": "row_id",
791+ "type": "text",
792+ "primaryKey": true,
793+ "notNull": true,
794+ "autoincrement": false
795+ },
796+ "tenant": {
797+ "name": "tenant",
798+ "type": "text",
799+ "primaryKey": false,
800+ "notNull": true,
801+ "autoincrement": false
802+ },
803+ "owner": {
804+ "name": "owner",
805+ "type": "text",
806+ "primaryKey": false,
807+ "notNull": true,
808+ "autoincrement": false
809+ },
810+ "subject": {
811+ "name": "subject",
812+ "type": "text",
813+ "primaryKey": false,
814+ "notNull": true,
815+ "autoincrement": false
816+ }
817+ },
818+ "indexes": {
819+ "tool_uidx": {
820+ "name": "tool_uidx",
821+ "columns": ["tenant", "owner", "subject", "integration", "connection", "name"],
822+ "isUnique": true
823+ }
824+ },
825+ "foreignKeys": {},
826+ "compositePrimaryKeys": {},
827+ "uniqueConstraints": {},
828+ "checkConstraints": {}
829+ },
830+ "tool_policy": {
831+ "name": "tool_policy",
832+ "columns": {
833+ "id": {
834+ "name": "id",
835+ "type": "text",
836+ "primaryKey": false,
837+ "notNull": true,
838+ "autoincrement": false
839+ },
840+ "pattern": {
841+ "name": "pattern",
842+ "type": "text",
843+ "primaryKey": false,
844+ "notNull": true,
845+ "autoincrement": false
846+ },
847+ "action": {
848+ "name": "action",
849+ "type": "text",
850+ "primaryKey": false,
851+ "notNull": true,
852+ "autoincrement": false
853+ },
854+ "position": {
855+ "name": "position",
856+ "type": "text",
857+ "primaryKey": false,
858+ "notNull": true,
859+ "autoincrement": false
860+ },
861+ "created_at": {
862+ "name": "created_at",
863+ "type": "integer",
864+ "primaryKey": false,
865+ "notNull": true,
866+ "autoincrement": false
867+ },
868+ "updated_at": {
869+ "name": "updated_at",
870+ "type": "integer",
871+ "primaryKey": false,
872+ "notNull": true,
873+ "autoincrement": false
874+ },
875+ "row_id": {
876+ "name": "row_id",
877+ "type": "text",
878+ "primaryKey": true,
879+ "notNull": true,
880+ "autoincrement": false
881+ },
882+ "tenant": {
883+ "name": "tenant",
884+ "type": "text",
885+ "primaryKey": false,
886+ "notNull": true,
887+ "autoincrement": false
888+ },
889+ "owner": {
890+ "name": "owner",
891+ "type": "text",
892+ "primaryKey": false,
893+ "notNull": true,
894+ "autoincrement": false
895+ },
896+ "subject": {
897+ "name": "subject",
898+ "type": "text",
899+ "primaryKey": false,
900+ "notNull": true,
901+ "autoincrement": false
902+ }
903+ },
904+ "indexes": {
905+ "tool_policy_uidx": {
906+ "name": "tool_policy_uidx",
907+ "columns": ["tenant", "owner", "subject", "id"],
908+ "isUnique": true
909+ }
910+ },
911+ "foreignKeys": {},
912+ "compositePrimaryKeys": {},
913+ "uniqueConstraints": {},
914+ "checkConstraints": {}
915+ }
916+ },
917+ "views": {},
918+ "enums": {},
919+ "_meta": {
920+ "schemas": {},
921+ "tables": {},
922+ "columns": {}
923+ },
924+ "internal": {
925+ "indexes": {}
926+ }
927+}
apps/local/drizzle/meta/_journal.json+7−0
@@ -29,6 +29,13 @@ · 28 unmodified lines
2929 "when": 1783025153772,
3030 "tag": "0003_graceful_the_fallen",
3131 "breakpoints": true
32+ },
33+ {
34+ "idx": 4,
35+ "version": "6",
36+ "when": 1784581228503,
37+ "tag": "0004_cultured_shadowcat",
38+ "breakpoints": true
3239 }
3340 ]
3441 }
apps/local/src/db/executor-schema.ts+1−0
@@ -76,6 +76,7 @@ export const oauth_client = sqliteTable( · 75 unmodified lines
7676 origin_kind: text("origin_kind"),
7777 origin_integration: text("origin_integration"),
7878 origin_issuer: text("origin_issuer"),
79+ origin_redirect_uri: text("origin_redirect_uri"),
7980 created_at: integer("created_at").notNull(),
8081 row_id: text("row_id").primaryKey().notNull(),
8182 tenant: text("tenant").notNull(),
apps/local/src/db/sqlite-fumadb.ts+6−0
@@ -99,6 +99,12 @@ export const createSqliteFumaDb = async <const TTables extends FumaTables>( · 98 unmodified lines
9999 ) {
100100 await client.execute("ALTER TABLE oauth_client ADD COLUMN origin_issuer TEXT");
101101 }
102+ if (
103+ oauthClientColumns.rows.length > 0 &&
104+ !oauthClientColumns.rows.some((column) => column["name"] === "origin_redirect_uri")
105+ ) {
106+ await client.execute("ALTER TABLE oauth_client ADD COLUMN origin_redirect_uri TEXT");
107+ }
102108
103109 const { db, fuma } = createExecutorFumaDb(drizzleDb, {
104110 tables: options.tables,
packages/core/sdk/src/core-schema.ts+9−0
@@ -231,6 +231,15 @@ export const coreTables = defineTables({ · 230 unmodified lines
231231 // `registrableOriginOfUrl`, since no discovered issuer was ever recorded).
232232 // Null for manual clients and legacy rows not yet backfilled.
233233 origin_issuer: nullableTextColumn("origin_issuer"),
234+ // The redirect URI a DCR client registered with the authorization server
235+ // (RFC 7591 `redirect_uris`, always the single flow callback here).
236+ // Strict servers reject an authorize request whose redirect_uri differs
237+ // from the registration, so DCR reuse compares this against the current
238+ // flow callback and re-registers on mismatch. Null for manual clients and
239+ // for DCR rows written before this column existed — treated as matching,
240+ // since re-registering every legacy client on upgrade would churn
241+ // providers for the majority whose callback never changed.
242+ origin_redirect_uri: nullableTextColumn("origin_redirect_uri"),
234243 created_at: dateColumn("created_at"),
235244 },
236245 ["tenant", "owner", "subject", "slug"],
packages/core/sdk/src/oauth-client.ts+5−0
@@ -82,6 +82,11 @@ export type OAuthClientOrigin = · 81 unmodified lines
8282 export type CreateOAuthClientInput = OAuthClient & {
8383 readonly origin?: OAuthClientOrigin;
8484 readonly originIssuer?: string | null;
85+ /** The redirect URI a DCR registration sent as the client's `redirect_uris`
86+ * entry. Persisted so reuse can detect a changed callback (strict servers
87+ * reject an authorize request whose redirect_uri differs from the
88+ * registration). Ignored for manual clients. */
89+ readonly originRedirectUri?: string | null;
8590 };
8691
8792 /** Metadata-only projection of a registered client for listing in the UI.
packages/core/sdk/src/oauth-register-dynamic.test.ts+14−2
@@ -560,6 +560,10 @@ describe("oauth.registerDynamicClient", () => { · 559 unmodified lines
560560 });
561561 yield* server.clearRequests;
562562
563+ const originalSlug = yield* Effect.map(executor.oauth.listClients(), (clients) =>
564+ String(clients[0]!.slug),
565+ );
566+
563567 // Recreated sandbox: same persisted oauth_client rows, NEW callback
564568 // origin. The register call runs again the way the connect flow does.
565569 const recreatedRedirectUri = "https://localhost:6410/api/oauth/callback";
@@ -579,9 +583,17 @@ describe("oauth.registerDynamicClient", () => { · 13 unmodified lines
579583 });
580584
581585 // The changed redirect URI must have minted a FRESH registration whose
582− // redirect_uris carry the recreated callback. Today this is 0: the
583− // stale client is reused and the authorize hop below 400s.
586+ // redirect_uris carry the recreated callback — reusing the stale
587+ // client would make the authorize hop below the reported 400. The new
588+ // client takes a DIFFERENT slug so it does not clobber the stale row
589+ // (existing connections still refresh through the old client_id).
584590 expect(registerRequestCount(yield* server.requests)).toBe(1);
591+ expect(String(slug)).not.toBe(originalSlug);
592+ const slugsAfter = yield* Effect.map(executor.oauth.listClients(), (clients) =>
593+ clients.map((client) => String(client.slug)),
594+ );
595+ expect(slugsAfter).toContain(originalSlug);
596+ expect(slugsAfter).toContain(String(slug));
585597
586598 const started = yield* executor.oauth.start({
587599 owner: "org",
packages/core/sdk/src/oauth-service.ts+53−11
@@ -643,6 +643,10 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 642 unmodified lines
643643 input.origin?.kind === "dynamic_client_registration"
644644 ? (canonicalIssuerUrl(input.originIssuer) ?? null)
645645 : null,
646+ origin_redirect_uri:
647+ input.origin?.kind === "dynamic_client_registration"
648+ ? (input.originRedirectUri ?? null)
649+ : null,
646650 created_at: now,
647651 }),
648652 );
@@ -706,6 +710,9 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 57 unmodified lines
706710 type DcrReuseCandidate = {
707711 readonly slug: OAuthClientSlug;
708712 readonly resource: string | null;
713+ /** Redirect URI the candidate registered with the AS; null for rows
714+ * predating the column (treated as matching any flow callback). */
715+ readonly redirectUri: string | null;
709716 };
710717
711718 // `oauth_client.created_at` is a date column that surfaces as a Date, an ISO
@@ -754,6 +761,8 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 42 unmodified lines
754761 {
755762 slug: OAuthClientSlug.make(String(row.slug)),
756763 resource: row.resource == null ? null : String(row.resource),
764+ redirectUri:
765+ row.origin_redirect_uri == null ? null : String(row.origin_redirect_uri),
757766 createdAt: candidateCreatedAt(row.created_at),
758767 },
759768 ];
@@ -769,13 +778,20 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 9 unmodified lines
769778 (a, b) =>
770779 a.createdAt - b.createdAt || (a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0),
771780 )
772− .map(({ slug, resource }): DcrReuseCandidate => ({ slug, resource }));
781+ .map(
782+ ({ slug, resource, redirectUri }): DcrReuseCandidate => ({
783+ slug,
784+ resource,
785+ redirectUri,
786+ }),
787+ );
773788 }),
774789 );
775790
776791 const decideDcrClientReuse = (
777792 input: RegisterDynamicClientInput,
778793 issuer: string | null,
794+ flowRedirectUri: string | null,
779795 ): Effect.Effect<
780796 {
781797 readonly existingSlug: OAuthClientSlug | null;
@@ -786,12 +802,35 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 4 unmodified lines
786802 Effect.gen(function* () {
787803 const candidates = yield* dcrCandidatesForIssuer(input.owner, issuer);
788804 const resource = input.resource ?? null;
805+ // A candidate is reusable only when the callback it registered with the
806+ // AS still matches the current flow's callback — strict servers reject an
807+ // authorize request whose redirect_uri differs from the registration
808+ // (e.g. the callback origin changed after a sandbox was recreated while
809+ // the persisted client survived). A null stored redirect is a legacy row
810+ // predating the column: treated as matching so an upgrade doesn't
811+ // re-register every client whose callback never changed. A null FLOW
812+ // redirect has nothing to compare against, so it also reuses — the only
813+ // alternative is a fresh registration, which the missing-redirectUri
814+ // guard would fail.
815+ const redirectMatches = (candidate: DcrReuseCandidate): boolean =>
816+ candidate.redirectUri === null ||
817+ flowRedirectUri === null ||
818+ candidate.redirectUri === flowRedirectUri;
819+ // A fresh registration must never take a slug an existing candidate
820+ // holds: `createClient` deletes any colliding (owner, slug) row first,
821+ // which would clobber a client that live connections still refresh
822+ // through (a redirect-mismatched client stays valid for refresh — the
823+ // token grant doesn't involve the redirect URI).
824+ const takenSlugs = new Set(candidates.map((client) => String(client.slug)));
789825 if (resource !== null) {
790826 const matchingResource = candidates.find((client) => client.resource === resource);
791− if (matchingResource) {
827+ if (matchingResource && redirectMatches(matchingResource)) {
792828 return { existingSlug: matchingResource.slug, registrationSlug: matchingResource.slug };
793829 }
794− const slug = dcrClientSlug(issuer, candidates.length > 0 ? resource : null, input.slug);
830+ const slug = uniqueDcrSlug(
831+ dcrClientSlug(issuer, candidates.length > 0 ? resource : null, input.slug),
832+ takenSlugs,
833+ );
795834 return {
796835 existingSlug: null,
797836 registrationSlug: slug,
@@ -803,15 +842,14 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 5 unmodified lines
803842 // resource-less flow (its tokens are bound to that resource), so when only
804843 // resource-scoped candidates exist we register a fresh resource-less client
805844 // rather than silently borrowing one (the old `?? candidates[0]` bug).
806− const reusable = candidates.find((client) => client.resource === null);
845+ const reusable = candidates.find(
846+ (client) => client.resource === null && redirectMatches(client),
847+ );
807848 if (reusable) return { existingSlug: reusable.slug, registrationSlug: reusable.slug };
808849 // Fresh resource-less client. Its slug is the bare `dcr-<host>` base, but
809850 // the FIRST resource-scoped registration for an issuer also takes that base
810− // (dcrClientSlug only suffixes once candidates exist). `createClient`
811− // deletes any row with a colliding (owner, slug) first, so reusing the base
812− // here would silently clobber that resource-scoped client. Dedupe against
813− // the existing candidate slugs so the resource-less client keeps its own row.
814− const takenSlugs = new Set(candidates.map((client) => String(client.slug)));
851+ // (dcrClientSlug only suffixes once candidates exist) — the takenSlugs
852+ // dedupe keeps the resource-less client on its own row.
815853 const slug = uniqueDcrSlug(dcrClientSlug(issuer, null, input.slug), takenSlugs);
816854 return { existingSlug: null, registrationSlug: slug };
817855 });
@@ -821,11 +859,14 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 3 unmodified lines
821859 ): Effect.Effect<OAuthClientSlug, OAuthRegisterDynamicError | StorageFailure> =>
822860 Effect.gen(function* () {
823861 const issuer = canonicalDcrIssuer(input.issuer, input.registrationEndpoint);
824− const reuse = yield* decideDcrClientReuse(input, issuer);
862+ // Resolved before the reuse decision: a persisted client registered with
863+ // a DIFFERENT callback must not be reused (strict servers 400 the
864+ // authorize request), so the reuse lookup compares against this value.
865+ const flowRedirectUri = input.redirectUri ?? redirectUri ?? null;
866+ const reuse = yield* decideDcrClientReuse(input, issuer, flowRedirectUri);
825867 if (reuse.existingSlug !== null) return reuse.existingSlug;
826868
827869 const slug = reuse.registrationSlug;
828− const flowRedirectUri = input.redirectUri ?? redirectUri;
829870 // DCR registers our callback as the client's redirect_uri — fail loudly
830871 // if the executor has none rather than registering a localhost URL.
831872 if (flowRedirectUri == null) {
@@ -888,6 +929,7 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 56 unmodified lines
888929 integration: input.originIntegration ?? null,
889930 },
890931 originIssuer: issuer,
932+ originRedirectUri: flowRedirectUri,
891933 });
892934 return slug;
893935 });

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