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
5Re-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 @@
1ALTER 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
78 "when": 1783660669921,78 "when": 1783660669921,
79 "tag": "0010_ndjson_output_arrays",79 "tag": "0010_ndjson_output_arrays",
80 "breakpoints": true80 "breakpoints": true
81 },
82 {
83 "idx": 11,
84 "version": "7",
85 "when": 1784581232732,
86 "tag": "0011_tense_caretaker",
87 "breakpoints": true
81 }88 }
82 ]89 ]
83}90}
apps/cloud/src/db/executor-schema.ts+1−0
@@ -86,6 +86,7 @@ export const oauth_client = pgTable( · 85 unmodified lines
86 origin_kind: text("origin_kind"),86 origin_kind: text("origin_kind"),
87 origin_integration: text("origin_integration"),87 origin_integration: text("origin_integration"),
88 origin_issuer: text("origin_issuer"),88 origin_issuer: text("origin_issuer"),
89 origin_redirect_uri: text("origin_redirect_uri"),
89 created_at: timestamp("created_at").notNull(),90 created_at: timestamp("created_at").notNull(),
90 row_id: varchar("row_id", { length: 255 })91 row_id: varchar("row_id", { length: 255 })
91 .primaryKey()92 .primaryKey()
apps/local/drizzle/0004_cultured_shadowcat.sql+1−0
@@ -0,0 +1 @@
1ALTER 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
29 "when": 1783025153772,29 "when": 1783025153772,
30 "tag": "0003_graceful_the_fallen",30 "tag": "0003_graceful_the_fallen",
31 "breakpoints": true31 "breakpoints": true
32 },
33 {
34 "idx": 4,
35 "version": "6",
36 "when": 1784581228503,
37 "tag": "0004_cultured_shadowcat",
38 "breakpoints": true
32 }39 }
33 ]40 ]
34}41}
apps/local/src/db/executor-schema.ts+1−0
@@ -76,6 +76,7 @@ export const oauth_client = sqliteTable( · 75 unmodified lines
76 origin_kind: text("origin_kind"),76 origin_kind: text("origin_kind"),
77 origin_integration: text("origin_integration"),77 origin_integration: text("origin_integration"),
78 origin_issuer: text("origin_issuer"),78 origin_issuer: text("origin_issuer"),
79 origin_redirect_uri: text("origin_redirect_uri"),
79 created_at: integer("created_at").notNull(),80 created_at: integer("created_at").notNull(),
80 row_id: text("row_id").primaryKey().notNull(),81 row_id: text("row_id").primaryKey().notNull(),
81 tenant: text("tenant").notNull(),82 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
99 ) {99 ) {
100 await client.execute("ALTER TABLE oauth_client ADD COLUMN origin_issuer TEXT");100 await client.execute("ALTER TABLE oauth_client ADD COLUMN origin_issuer TEXT");
101 }101 }
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 }
102 108
103 const { db, fuma } = createExecutorFumaDb(drizzleDb, {109 const { db, fuma } = createExecutorFumaDb(drizzleDb, {
104 tables: options.tables,110 tables: options.tables,
packages/core/sdk/src/core-schema.ts+9−0
@@ -231,6 +231,15 @@ export const coreTables = defineTables({ · 230 unmodified lines
231 // `registrableOriginOfUrl`, since no discovered issuer was ever recorded).231 // `registrableOriginOfUrl`, since no discovered issuer was ever recorded).
232 // Null for manual clients and legacy rows not yet backfilled.232 // Null for manual clients and legacy rows not yet backfilled.
233 origin_issuer: nullableTextColumn("origin_issuer"),233 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"),
234 created_at: dateColumn("created_at"),243 created_at: dateColumn("created_at"),
235 },244 },
236 ["tenant", "owner", "subject", "slug"],245 ["tenant", "owner", "subject", "slug"],
packages/core/sdk/src/oauth-client.ts+5−0
@@ -82,6 +82,11 @@ export type OAuthClientOrigin = · 81 unmodified lines
82export type CreateOAuthClientInput = OAuthClient & {82export type CreateOAuthClientInput = OAuthClient & {
83 readonly origin?: OAuthClientOrigin;83 readonly origin?: OAuthClientOrigin;
84 readonly originIssuer?: string | null;84 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;
85};90};
86 91
87/** Metadata-only projection of a registered client for listing in the UI.92/** 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
560 });560 });
561 yield* server.clearRequests;561 yield* server.clearRequests;
562 562
563 const originalSlug = yield* Effect.map(executor.oauth.listClients(), (clients) =>
564 String(clients[0]!.slug),
565 );
566
563 // Recreated sandbox: same persisted oauth_client rows, NEW callback567 // Recreated sandbox: same persisted oauth_client rows, NEW callback
564 // origin. The register call runs again the way the connect flow does.568 // origin. The register call runs again the way the connect flow does.
565 const recreatedRedirectUri = "https://localhost:6410/api/oauth/callback";569 const recreatedRedirectUri = "https://localhost:6410/api/oauth/callback";
@@ -579,9 +583,17 @@ describe("oauth.registerDynamicClient", () => { · 13 unmodified lines
579 });583 });
580 584
581 // The changed redirect URI must have minted a FRESH registration whose585 // The changed redirect URI must have minted a FRESH registration whose
582 // redirect_uris carry the recreated callback. Today this is 0: the586 // redirect_uris carry the recreated callback — reusing the stale
583 // stale client is reused and the authorize hop below 400s.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).
584 expect(registerRequestCount(yield* server.requests)).toBe(1);590 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));
585 597
586 const started = yield* executor.oauth.start({598 const started = yield* executor.oauth.start({
587 owner: "org",599 owner: "org",
packages/core/sdk/src/oauth-service.ts+53−11
@@ -643,6 +643,10 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 642 unmodified lines
643 input.origin?.kind === "dynamic_client_registration"643 input.origin?.kind === "dynamic_client_registration"
644 ? (canonicalIssuerUrl(input.originIssuer) ?? null)644 ? (canonicalIssuerUrl(input.originIssuer) ?? null)
645 : null,645 : null,
646 origin_redirect_uri:
647 input.origin?.kind === "dynamic_client_registration"
648 ? (input.originRedirectUri ?? null)
649 : null,
646 created_at: now,650 created_at: now,
647 }),651 }),
648 );652 );
@@ -706,6 +710,9 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 57 unmodified lines
706 type DcrReuseCandidate = {710 type DcrReuseCandidate = {
707 readonly slug: OAuthClientSlug;711 readonly slug: OAuthClientSlug;
708 readonly resource: string | null;712 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;
709 };716 };
710 717
711 // `oauth_client.created_at` is a date column that surfaces as a Date, an ISO718 // `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
754 {761 {
755 slug: OAuthClientSlug.make(String(row.slug)),762 slug: OAuthClientSlug.make(String(row.slug)),
756 resource: row.resource == null ? null : String(row.resource),763 resource: row.resource == null ? null : String(row.resource),
764 redirectUri:
765 row.origin_redirect_uri == null ? null : String(row.origin_redirect_uri),
757 createdAt: candidateCreatedAt(row.created_at),766 createdAt: candidateCreatedAt(row.created_at),
758 },767 },
759 ];768 ];
@@ -769,13 +778,20 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 9 unmodified lines
769 (a, b) =>778 (a, b) =>
770 a.createdAt - b.createdAt || (a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0),779 a.createdAt - b.createdAt || (a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0),
771 )780 )
772 .map(({ slug, resource }): DcrReuseCandidate => ({ slug, resource }));781 .map(
782 ({ slug, resource, redirectUri }): DcrReuseCandidate => ({
783 slug,
784 resource,
785 redirectUri,
786 }),
787 );
773 }),788 }),
774 );789 );
775 790
776 const decideDcrClientReuse = (791 const decideDcrClientReuse = (
777 input: RegisterDynamicClientInput,792 input: RegisterDynamicClientInput,
778 issuer: string | null,793 issuer: string | null,
794 flowRedirectUri: string | null,
779 ): Effect.Effect<795 ): Effect.Effect<
780 {796 {
781 readonly existingSlug: OAuthClientSlug | null;797 readonly existingSlug: OAuthClientSlug | null;
@@ -786,12 +802,35 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 4 unmodified lines
786 Effect.gen(function* () {802 Effect.gen(function* () {
787 const candidates = yield* dcrCandidatesForIssuer(input.owner, issuer);803 const candidates = yield* dcrCandidatesForIssuer(input.owner, issuer);
788 const resource = input.resource ?? null;804 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)));
789 if (resource !== null) {825 if (resource !== null) {
790 const matchingResource = candidates.find((client) => client.resource === resource);826 const matchingResource = candidates.find((client) => client.resource === resource);
791 if (matchingResource) {827 if (matchingResource && redirectMatches(matchingResource)) {
792 return { existingSlug: matchingResource.slug, registrationSlug: matchingResource.slug };828 return { existingSlug: matchingResource.slug, registrationSlug: matchingResource.slug };
793 }829 }
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 );
795 return {834 return {
796 existingSlug: null,835 existingSlug: null,
797 registrationSlug: slug,836 registrationSlug: slug,
@@ -803,15 +842,14 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 5 unmodified lines
803 // resource-less flow (its tokens are bound to that resource), so when only842 // resource-less flow (its tokens are bound to that resource), so when only
804 // resource-scoped candidates exist we register a fresh resource-less client843 // resource-scoped candidates exist we register a fresh resource-less client
805 // rather than silently borrowing one (the old `?? candidates[0]` bug).844 // 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 );
807 if (reusable) return { existingSlug: reusable.slug, registrationSlug: reusable.slug };848 if (reusable) return { existingSlug: reusable.slug, registrationSlug: reusable.slug };
808 // Fresh resource-less client. Its slug is the bare `dcr-<host>` base, but849 // Fresh resource-less client. Its slug is the bare `dcr-<host>` base, but
809 // the FIRST resource-scoped registration for an issuer also takes that base850 // the FIRST resource-scoped registration for an issuer also takes that base
810 // (dcrClientSlug only suffixes once candidates exist). `createClient`851 // (dcrClientSlug only suffixes once candidates exist) — the takenSlugs
811 // deletes any row with a colliding (owner, slug) first, so reusing the base852 // dedupe keeps the resource-less client on its own row.
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)));
815 const slug = uniqueDcrSlug(dcrClientSlug(issuer, null, input.slug), takenSlugs);853 const slug = uniqueDcrSlug(dcrClientSlug(issuer, null, input.slug), takenSlugs);
816 return { existingSlug: null, registrationSlug: slug };854 return { existingSlug: null, registrationSlug: slug };
817 });855 });
@@ -821,11 +859,14 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 3 unmodified lines
821 ): Effect.Effect<OAuthClientSlug, OAuthRegisterDynamicError | StorageFailure> =>859 ): Effect.Effect<OAuthClientSlug, OAuthRegisterDynamicError | StorageFailure> =>
822 Effect.gen(function* () {860 Effect.gen(function* () {
823 const issuer = canonicalDcrIssuer(input.issuer, input.registrationEndpoint);861 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);
825 if (reuse.existingSlug !== null) return reuse.existingSlug;867 if (reuse.existingSlug !== null) return reuse.existingSlug;
826 868
827 const slug = reuse.registrationSlug;869 const slug = reuse.registrationSlug;
828 const flowRedirectUri = input.redirectUri ?? redirectUri;
829 // DCR registers our callback as the client's redirect_uri — fail loudly870 // DCR registers our callback as the client's redirect_uri — fail loudly
830 // if the executor has none rather than registering a localhost URL.871 // if the executor has none rather than registering a localhost URL.
831 if (flowRedirectUri == null) {872 if (flowRedirectUri == null) {
@@ -888,6 +929,7 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => { · 56 unmodified lines
888 integration: input.originIntegration ?? null,929 integration: input.originIntegration ?? null,
889 },930 },
890 originIssuer: issuer,931 originIssuer: issuer,
932 originRedirectUri: flowRedirectUri,
891 });933 });
892 return slug;934 return slug;
893 });935 });

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