diff --git a/.docker/Dockerfile.roomote-api b/.docker/Dockerfile.roomote-api index 00b39cfde9..006a43f487 100644 --- a/.docker/Dockerfile.roomote-api +++ b/.docker/Dockerfile.roomote-api @@ -38,6 +38,7 @@ COPY packages/config-typescript/package.json ./packages/config-typescript/ COPY packages/env/package.json ./packages/env/ COPY packages/db/package.json ./packages/db/ COPY packages/ipc/package.json ./packages/ipc/ +COPY packages/job-auth/package.json ./packages/job-auth/ COPY apps/roomote/package.json ./apps/roomote/ RUN pnpm install @@ -47,6 +48,7 @@ COPY packages/config-typescript ./packages/config-typescript/ COPY packages/env ./packages/env/ COPY packages/db ./packages/db/ COPY packages/ipc ./packages/ipc/ +COPY packages/job-auth ./packages/job-auth/ COPY apps/roomote ./apps/roomote/ WORKDIR /roo/apps/roomote diff --git a/.docker/Dockerfile.roomote-controller b/.docker/Dockerfile.roomote-controller index 3deec55fcd..089f33ee1a 100644 --- a/.docker/Dockerfile.roomote-controller +++ b/.docker/Dockerfile.roomote-controller @@ -43,6 +43,7 @@ COPY packages/config-typescript/package.json ./packages/config-typescript/ COPY packages/env/package.json ./packages/env/ COPY packages/db/package.json ./packages/db/ COPY packages/ipc/package.json ./packages/ipc/ +COPY packages/job-auth/package.json ./packages/job-auth/ COPY apps/roomote/package.json ./apps/roomote/ RUN pnpm install @@ -52,6 +53,7 @@ COPY packages/config-typescript ./packages/config-typescript/ COPY packages/env ./packages/env/ COPY packages/db ./packages/db/ COPY packages/ipc ./packages/ipc/ +COPY packages/job-auth ./packages/job-auth/ COPY apps/roomote ./apps/roomote/ WORKDIR /roo/apps/roomote diff --git a/.docker/Dockerfile.roomote-worker b/.docker/Dockerfile.roomote-worker index 4d0cefc278..bf185aebd9 100644 --- a/.docker/Dockerfile.roomote-worker +++ b/.docker/Dockerfile.roomote-worker @@ -92,6 +92,7 @@ COPY packages/config-typescript/package.json ./packages/config-typescript/ COPY packages/env/package.json ./packages/env/ COPY packages/db/package.json ./packages/db/ COPY packages/ipc/package.json ./packages/ipc/ +COPY packages/job-auth/package.json ./packages/job-auth/ COPY apps/roomote/package.json ./apps/roomote/ RUN pnpm install @@ -101,6 +102,7 @@ COPY packages/config-typescript ./packages/config-typescript/ COPY packages/env ./packages/env/ COPY packages/db ./packages/db/ COPY packages/ipc ./packages/ipc/ +COPY packages/job-auth ./packages/job-auth/ COPY apps/roomote ./apps/roomote/ WORKDIR /roo/apps/roomote diff --git a/.env.development b/.env.development index 8d45226450..53e26dd724 100644 --- a/.env.development +++ b/.env.development @@ -20,6 +20,7 @@ OPENROUTER_API_KEY=encrypted:BA3bYeSEXFA0jnw3lmzLKz53ekkGr0K6sDLN6+nFGsa0TdJrYoK SLACK_API_TOKEN=encrypted:BJyTFw3gugKZr6uaNZaqCH0UpW/mP4S8P1oL5NzeCvoZzSMJEcDrKsW+b+3Ojp0EuqBFI7T8SPUfRMr6ZTSI880+pKYWkmfAAl2jYYE5aw48LGkg+WJTjIW8g2jTEc3PXPYeMzszrJ2Igf8NuyDMLmhI9S9ew5OfhxD6bKwr73WdUKVDJ+MMVBQGIkbrt1PBX30kRuKZq5oqiA== - - FLY_ACCESS_TOKEN="encrypted:BDm2qpf85nXBtq7KENGMiwcsFg3Ln/gRcZoUm5busQ8tRgEDXhlXUB4gSUT+Z/XRvwurBcBmYZhkjkDycNQLME53htE8lMLfBqmPTIWFgHhKVZWtlk3EhC7GZcycuLYKZVFuEsvKxu4kH5JibqUtJ5fRjKEbQG1HXuTvwuwGjSahgZC3I+Q1xb/83+nFmHKAr0ibaLHHO/mbZZ4YLSS91dw/BiVPmhaY2eDpdDvrLkYZVJZ28f+JkYocYyNUdaCnUqlzfpQAa6xBTelk9OaNwm3XnCYlm+VssMQpN6z5iOyBDG4Jr2Wx8eNyg9PC5lOptweYRYh2ho3RYW0gDwYujfFfRpMKGaEnCB2l/++iDWB3LY9p3xh8ysJ59OTMb8+luOHJbZdiwbnOgY4WcaF0nJyPNX8aAq22Z/imvFqb5hFnytg5kD2mn4h6KG36NmldAw/zJeyat8j4KQEZSW9pBv/vXN2UtTWnA+vsgnua5SHaHoa2GHN/8HyODbdNPWv1tWnjOhZGbxa1+MzkX/4w6BS9jNG+6Pe4m0YVZMjX/Bv6B8qIrcKqVouBrsKIjN9BSFdQzqlDLk6GsP25Nu/eTQS8+/fuSbI+mn7eo84xHWtawlgej4ydkNDOBVmUV9xgoST8LGq6H6Gq5zvuNyY8IStglpybN1yQzEP2RT+JNM112qQK5EWuWTEGuMq4C8ZIIO8ETgDgDjVBGuEeOVFIHCpXzrwB9HTWf3W4DO7JKeNRBmbYkNBTbr3AqzqRKa/+aTw4G8YTyn0T++AaYKUv9KZAajtZ6TQKL1bkGOvsqBBzRW1/NCyuRxEt7+r/ffx0zo7qLW6ir2gTZuDoPi8kQIu8RxkGJypg8QYZHGtr3yGXxm8IEjPlCYINx2RWZjFr0RDO6FRqlexCnnnj10n+TZJgeEQb0esGXJd20klhYwngi9BnvIWoOPLPPeC+JHeILVAUgAxKjsspF5ck3I/Td2u/" + +JOB_AUTH_PRIVATE_KEY="encrypted:BM7xBs73k3nh0isBhVELr4LFt2iMiP7XD7knKtEnm7uudQ9DclnL+Hulun8SFFsGzkIp+FyYK47JAMHqyK3NqGHE/93efpACNRNH/YVv0OfUvVw58trPdvdY60hnUrW17z/ae16LlvkBd6roCZxn0GvhgMKPRxlL1B4ZxxtfHfz+yazl0tSsr2t/iMzIhRCtQZ1638PrZJ7ugmsK1lK5g+cQDV91Onth+eVNy6cDX9NAC77iotnzXyOWPyzcxECCSje2jYysAugTTRV2pR/Lnzi506yrWumSv5SpNcDqZjek89CK29nfUJv+UIxxW2dIMIB19ZqjmSU84nIYymVzkwl7kOaDqTIEIMRSzYs/npTWDOGbtB2x49vaD5YeysFiamLlaSzvS46N+uiSrH+8VCujs7HLirOJhtkindVRiV4qfl/eycf9OG2A4jNWhS/uJYcF2QC8KZL1SwKcn7u+FDov1NAYUw25BmeYVvb+KPujsaH1DniqucaxtcGqior1ZjKU5RutbzV5QWknpNAtBD2aVv0HOB7/5bHJpwFl2vaR" +JOB_AUTH_PUBLIC_KEY="encrypted:BOU3vH2HFJjRuORyr3psrDpqaXS1hXo2SxA3N2q4M2ihHgHNnbjbKAV3xYHTo9WQA6XGvRWXpo3C/soV6ukMQGkqmvjyCNPVLN3ZlJJlmbBC1qRTn83dejp4YCqV0eb3JOyOhYVpuCCIcYU3s67MRqywTxST1h1PRo3xykpgm3nY0kfZMmHvL7rTh4ik6uVebDhIY7jDrXRWlj/dia/cyEDzB88cBpmCpJx8uqFmK9889+Le4PTb7hLBIv7KvkzMb4TaB4yqpUFd/bawA0fOX5WC85t2DZ+7+Ddqp7s4x6nsNWjh51t0uYGICdF1ReWI1jZ4nKGjxpXcjfmgcXPnOT6PdjOWfMWfMGq+sE7o1GUdu5GKVwATYe4+NXrcMVsTQKdbtRrl4k1lz46jgAegUemGuBxR4Ro6qxK5xhsKeyaMnbrH8f/X0BZHAGOx" diff --git a/.env.preview b/.env.preview index ea1d6e4a44..7844a02768 100644 --- a/.env.preview +++ b/.env.preview @@ -21,3 +21,6 @@ OPENROUTER_API_KEY=encrypted:BDoXCMz3fV5DXa89cwd77/pZhw6tVIw96LvtyZEFwFQtQV1Qk+Z SLACK_API_TOKEN=encrypted:BEmAqaFftmVNlZdx19KGXBDtJ1E1SFBJrAH52pwhFR00Eq6DJAOJSBLY+9lV4wsDRnhTaR8QBQ5fx4kX6751mqJ/Ldu9n7zYV+NTV5cWowvTh4jD+w7yufbUvNgnqs1CpvGXysaJoyQCQqQlUJ+wxlf+rb3yEN5CMR9y1Es/d+cXF2d73H2VfBDtVcKf1pCPn+kVeQW9XHquiw== FLY_ACCESS_TOKEN="encrypted:BCoSnp5DqB0Td27HYtDwIS/dZfZkyvjsgUz+0XcKdovFMr3/ZRZkjLW8GgBpNIdvxYz+/iS8wGPtnFHmD73Kf1+CU5eKNWUTwRaxoD7GyCTXvWwYnNt55unoNpr51rCWTgQ66M8aQGm85gC+948woiL64i5ozwWezkxbPtk4vOM5dbbeIObzfPoPKOqFDOIdFjfbaEFpd6Yuw2BrxCT9HExiK+IOp2xUNEvB4/w4xbzenDDkkyw+kNx0hcFvkefX2EkuSHRS3nPVKMFVH2Pcfc7HuccAbmjIvlvuKMulyiAyJi7XrkyW46ZN7BCRokYR0j7zIqAhQmNgYgs37tbiq+CvqDHbkOEqGIZZIxV+pzONbJ07moAbDBpBeMJ6WeH6gAURJ3mqa3tCyCEFk8TxHMe5lbfjJGuYe4SakrOTMPOMH451gtw5KdUN6m7XFKfL1+MbklpIy/lVgOWAKiyMlxxUmeDShn4ElMIKEGYxY8gOcI5Akyn/3B3tk1RgutTQ7p3gjya5GCf2hKNnOAAjlRzOfDbnObslQZv/ZG1QQ81VLqpvj0S2zMDITwjuGCey6MT9UXrVs1JYaLPsJHiEKlylWuXRenWBaVz2iRdFugRhx1PbisOPkgrqhU5K/B5d8vpsuKOCX65ty8CkJq3KoW6TzzkO2nAm8LJ10Ihv3y+fGRCgUUmjLmSOzg7yG6a89wklCUW3BS3bDdvgEfBMGcdPH/9BTVOQ5wpXZWB9CX4tvPLlXnLyWqH6VWShsfhRRL0MoQ9PXMD9NK61jHIw8RXB1xAASL8B+NeVthj490ymhUHuaL0nBetS38W+tD6jE5IVAyvaR4tvz/4eJz/uxbvgqCJyd4GFW8kyp6blTziksryr4TlLzS7d1F84WKIjUcNyaG5te/L4GixvnPQ5nxFo2Ib464knuljvjdDRxLiOk4H8k7S45OLx2seSsV+KTGlKeN3S013rpXFnG5APUEvz" + +JOB_AUTH_PRIVATE_KEY="encrypted:BFm76/By4KHU3bILl4tYJOwwHN8xR49C/wcSrh8/0+QAhYgetocntzJ90fYS3+ER2e/Mdh1awOynhbsZ/gpLJC6PmqX+MKzkpv/xJC5AJZTAeyWAKwaL1NhUijAU8mS88/87EvKZCH9Rw/tBGy/7CFrOwipjKQxvuRZjgxmypkV8t6eIEmfGgIbzrjvSyZ6O31FpYliAABeefRpNQo3zd7tV3dX9xtVDt1Hc68vzak0Aw+EJwi+Q7YWvghpcLJxXXo+PYs15Cj9LS8Ww2Ijz/EXO98UIDMN1e947O7hrmTWPC/JVZlGJOctMyNyaX/+rSJP+XepJxkVYeG9jglOLOsIFS+KKiYfnlT3TC5pt9USaAQAsdWvcfT09TN4qYR/eeSfj7mZYNCU4LtIqrFVT65ehntTOokqN7uNSLmSQnzPIO9lhIVcarFz1C9Bx/xE4FaBVKm9agszVcUumJcfktuV/WTxRxgTmhJYbJ08shhrTubWDCxOGdr5NFR+NFN5oMe9KXCOcUjtuHQ8UUSjYwXvZzJOvRAMP0QAv5xzGTlQI" +JOB_AUTH_PUBLIC_KEY="encrypted:BPcQ3GgxlCm77QmmsLm5jzFLzCVRuDJzPC7t3jfMbPYNGMBveYIJvrmnpHCNSJIGCrHi0MtFUypvyjjHgQADNN8GaWpyvifk8jTBtp1ptQq736ilxq/Z76KY96uHf59TQCvPR2pjsu7GK0AqL3wg1MV+30+wZzGNo2JYZYczMdYgrMj72d2qsOPydUgPAkXcwRMigNyPhF8JdOT9G5l1w+qRKjRSFynFIXcP61J9kykeYz3BmCIeKw2QopSYz3ie98o+PTmnyQ3KGgEdJ7IVTmV3AglWMJx6Gj6qo8hMW+pUJU790nj9FMkMHHnAIRvhEX9MmInNu+ysVuowKWfxB3IC8A9ktgk3TOZ4/Ub1EZ3rWGVyR9xVFLSYPUlW2s/PfPA8b+KdSd/QDRpGhJ0rd6pl8r33/HOErwu6Mta/4NHIBR9SRj8SrjCNTPG0" diff --git a/.env.production b/.env.production index c854f7f02b..ef3e58191a 100644 --- a/.env.production +++ b/.env.production @@ -21,3 +21,6 @@ OPENROUTER_API_KEY=encrypted:BLBANC6AI/i5gHIeiBaY5lOogEEahxSLAvsxUpk6Z1FXtfrIEj/ SLACK_API_TOKEN=encrypted:BHQC5Lv0k3VnZ0Ez4TcVvL0meJs8R5ysPFa47U3kkjOJstrtDa26F8Fjvd8/Qr7ztiKdJzGXpr9k4/Tm8mL9jZvG7HNH9D+fVHkyGZ2Mf7B97FdXjiDxA3f+Pifj0aTfM7U58tDrKTLeQ/6UzE3Emcxyg4CxKjAjEaRFOAiQxs1lVAtqzbeF7OCV6GHScpSe4zoAaFXvtQ07FA== FLY_ACCESS_TOKEN="encrypted:BFcS7pt6Evj8OG1khLFJkqzg2MI/pP4g4tP1l85vbsiTL1x1OvjsIqhZvGUm/PkamInYMTVOhWOWHx/s0Iv3F++FfXeWCzRTaaGvCupMgGB5BEEckODT+4GLrsc14NUCT6p+DE/iREzkJFlHjPwj8NergqnUwWmxhNyFDxFK0AZKpZAKSf69/WrAHDN5I907mOm4wUkPag03L9SuhaotCedsv/zqvIfvbr8gxyk7N1vphXaiihVtE+GIaBnHkHjz3eiS/kDtKFPPeZT1lS9yE9T00Bah2hZkSIxpIu4Cd3zAAWYIplqlhDrO32tm89gPSeQfn7ORaHPogK5I8n9ctlKJV8PFO9YoYqaq8TUnkDzvLj7BNk7GpPibodl2mSX8eVspJXCSewKhnbVFA1RNWJ6B3Bqp5n3PmmBaF4n0VCYMzxUHT4UvOfoQGH1GhzVQiQkV0+s8jfD3eRFiovpvOc89WXmuhtGGMwZ0pYrFWg1qTgmUndcfKzob7yzvrlLpJ+kqp6X0c+cO26yOfN+7LfnZt9BeuzzHSEN9+aEWfHv8IK/xqDjT/V8ZoctOGJG/nFFqF7OeYk2Er0AvD+53kwXBCKiBRjx/b2w6s+Z4f71+62alH/Pm62Q3vWGpXIuj8GF7CaHIQD9FH6cegOa7klabrdXjyIETQLPtpKniwVJbaRhEj9p+WOB0dhibZVFX/LtQ8hIt1HuV0TcoB1jLToN835gPuPV5db+Zs0qf3v0lgM4dRM2N3R7d+XdTVZVvfpdJTTNpN9P6AMrHjTHGp2sCDkInymjrhZlpwD3h3XcsnbcGkjTraJsoNsfGGoH1JUyX0AVyF4ec61eFiaFec4RGH2Oxn/rdCq/X4xbqM7ZQhxhhBhkyIMlztRONXYfz8Jqn+f9KK4lxuhg5Ao/xWWyqv5UCeGh2KB32CO1ez3ei9QqD72wyw/NJLLh8XPW1f9TPuFQsfQTG1ftljC2w/XGu" + +JOB_AUTH_PRIVATE_KEY="encrypted:BNv2gXyx2FICPfSCJxDJX0ELwt35/Ie/WR0xUai/k9OqEM8p2rhLkRkde45dioGlEPbETZgWa6PDD9mXCR8qBWh4iZziDEwYd8x1uFTx26BBs1MyWk8WhRhjhQMXi0h8V7inVELiPtMcq40aSJ/2S2yTR/qyNlW0YrhQJO1FeU13n5Eelp5PSvmCVDMu+f5m4m5haiJIzvnXbnETOGqatWWNn3qXY/neyfTLlv21E95ulu2bUP1/4iRw4tf0e5LIf4TLeo3M2tSuUc/s7sELb6hNkBtBZCPkQG1+dHci/6MLrer0LiMF3Pv3fUP9ZRfRu8AnOjHuk6xBq3EshbTE+NQoWSf/47F23lHnY+pU+1cT2W+3L21RvZiVoENWC00/udbyNx5GglZmRY8+pZHUwS0l7FFVFtjXJxQigLtMsCijmmZhfz+JAlOouOUoh26HzvB7dcyHnwsXhGwEiG8wmUi8lFCmFosdMp210SRpfrJ/NywObBHYSUXDyELEpS6+GTeZQDmUs44vQ7v58waDHRQvJXGxIw4zK+s+WGlbHTnF" +JOB_AUTH_PUBLIC_KEY="encrypted:BAWhQhxInT33caEIc2QQ16wSjJ7SqJ2nYc0FEwQboBfajqh9mp2SVwH/A9CbbM+JrVbqYjNamVCf+BdIJVKn4rTrrV64HVMI5T++EfxPmN59sQWgqb4Ig3DXnoo9UanXGccn23dB/k4ZtweoHSPeVXn8XbZ867TPO3QLI1Xr6fLfgxHZXcyvH6lEZlClJjM+SLRLtixkvKb5c2y1JG6cVQ+dHrvxuh10FdCFzfhkEvPHdv09dWpw6OrPfxBEk+dHpjXWO54p/4WThjJ+41Rpqne0pupLwkILaa/xMj364Q/duTKORUDdnDc+Mk9TMgHuj2M5kQGSSXXuNO4shGxupoA2y5HRI9IhjNKQIq2qikUjzzxjvfHs5ESj6DfJsDq5AwiqU6vD7XcQFbJLaUZc8FioN+gEiHnNvCoi6ZSKjvrCggt5afN+lGVNyROe" diff --git a/.env.test b/.env.test index 65e97a051d..864fa1930b 100644 --- a/.env.test +++ b/.env.test @@ -20,3 +20,6 @@ OPENROUTER_API_KEY=encrypted:BJHyYNaUsHiU6By54yUA2lG48W6NFuafGOr6Sc4XDopmTB9WulF SLACK_API_TOKEN=encrypted:BDkskSYEb9SYQLpS0LyyOMgS7pVOyha9agMrS15iQUBoeULTzXbfGxEc+w3oEUa7mFyuRW+gHpo1jqEYicc2Nm9xXawDFsfhoTgXkhUdDlAVsJY/jctWtw97Hb9qLvigRrJWyx7ha5wUMg== FLY_ACCESS_TOKEN="encrypted:BIobJarcf4MpmWDkSjVGjXUH87dNj5rbMqow0/7QNpa1s3eMZeRNoi8oMGn3lBSgPv/hZjCUJJ9t8+5mZNgZk0CrrLUz9W1VxD1hIJwzYlKA6vhR8Ef+A8z6W4H+XBl3zFXPxfvfdy+v" + +JOB_AUTH_PRIVATE_KEY="encrypted:BB5ohcsYq19krr7Rw4CVtS6GcxEbpWPeobkByt0ppr6oU92AgbbdgMdEEyA8IRFpK6H87ud6DdfV4iRnkwART3HhdJJZe06R8MfV6SCxUioz6uwRmagmeJ5/cmnViUihhFiq/NC7qSD/zfRREuhyKXA/Tr1KTllaoQn89PzpfVM42duSRZcSRQ3/vHV62v+MIRFvm2gWqL2mwgr7RtRDpijyzFvSzgerrorHMOPQTYL5uYAcS20lnFEnWzJfDU/hZmqFvyCnxG3u80IlPu+RWN7xLs/WtXZr+5UFP8sgVFRi18+xrNtam3auA163HbC8u0mk3MeYHfKihN35j+2U24oEg+sflFPSm1EfXS+da1wd7GsVfOv/9jRhSrXju1Vg1DI3PSBygiQXpTD23KtOFy5v96RtAGGUeq94Fxti1jTzngaUNxBSCO69L7PL2fo2g2ZML2GlSfSq7R8Q/T7zsjpirbecOLermJnMP0hgrkCTDaqD008iXYte01wVOqgNRuXIXtIqGrsUSf+eGLLP669TqIbNPOSq1tccOtNnPVAH" +JOB_AUTH_PUBLIC_KEY="encrypted:BI7/E85qpiZMVZ79gLNRMETFCBoja4wq1Gw03BAWTct3GoCypOsgm2tYvbPOVpe/XKqFot7WVzGhaWrvx9blzrp1+LPAHcChNoa61CeUMtrNAL5VQETZ/9SJU1zLFWpHlCJuviu8G+DlC0UwfAQBdutcwhlcwP8fQSGGXgSaCcnshlhsX/tuqSnyCOrQ2kERXEFK4QT0ngETFdOzVqO0/QHbk6tL/86iv4XQ7Iz2g9XLCdAWXxcLzRn7RE9BXna4eCPahJI8NZx50E3gtD6IsQj7msL1p20PWauOJsm4futPdPNZe2k8plR48PAxTYRHgRHwHJukEqQcNjL5LdXBW0fMNQRBJLYjgxJfAs0cm6ioYXqj0cliDharBGQ71YMjsihsCPZAsozibddiKT02lEtLQOiHOp27wfDNkSOXvspkaaxjHoB4j6cTSQWb" diff --git a/apps/roomote/package.json b/apps/roomote/package.json index bd8e039968..3610939b75 100644 --- a/apps/roomote/package.json +++ b/apps/roomote/package.json @@ -21,6 +21,7 @@ "@roo-code-cloud/db": "workspace:^", "@roo-code-cloud/env": "workspace:^", "@roo-code-cloud/ipc": "workspace:^", + "@roo-code-cloud/job-auth": "workspace:^", "@roo-code/types": "^1.30.0", "bullmq": "^5.37.0", "cmd-ts": "^0.13.0", diff --git a/apps/roomote/src/lib/runTask.ts b/apps/roomote/src/lib/runTask.ts index 6a38ce9f08..8a631f5c3f 100644 --- a/apps/roomote/src/lib/runTask.ts +++ b/apps/roomote/src/lib/runTask.ts @@ -15,6 +15,9 @@ import { EVALS_SETTINGS, } from '@roo-code/types'; import { IpcClient } from '@roo-code-cloud/ipc'; +import { createJobToken } from '@roo-code-cloud/job-auth'; +import { db, users } from '@roo-code-cloud/db/server'; +import { eq } from 'drizzle-orm'; import type { JobPayload, JobType } from '@roo-code-cloud/db'; @@ -53,6 +56,8 @@ export type RunTaskCallbacks = { type RunTaskOptions = { jobType: T; jobPayload: JobPayload; + jobId?: number; + userId?: string; prompt: string; logger?: Logger; callbacks?: RunTaskCallbacks; @@ -64,6 +69,8 @@ type RunTaskOptions = { export const runTask = async ({ jobType, jobPayload, + jobId, + userId, prompt, logger, callbacks, @@ -80,9 +87,36 @@ export const runTask = async ({ const cancelSignal = controller.signal; const containerized = isFlyMachine() || isDockerContainer(); + let envVars = `ROO_CODE_IPC_SOCKET_PATH=${ipcSocketPath}`; + + // Create JWT token if we have jobId and userId + if (jobId && userId) { + try { + // Get user's org info + const user = await db + .select() + .from(users) + .where(eq(users.id, userId)) + .limit(1); + const orgId = user[0]?.orgId || null; + + const token = await createJobToken( + jobId.toString(), + userId, + orgId, + TIMEOUT, + ); + + envVars += ` ROO_CODE_CLOUD_TOKEN=${token}`; + } catch (error) { + logger?.error('Failed to create job token:', error); + // Continue without token - job will fall back to no auth + } + } + const codeCommand = containerized - ? `ROO_CODE_IPC_SOCKET_PATH=${ipcSocketPath} xvfb-run --auto-servernum --server-num=1 code --wait --log trace --disable-workspace-trust --disable-gpu --disable-lcd-text --no-sandbox --user-data-dir /roo/.vscode --password-store="basic" -n ${workspacePath}` - : `ROO_CODE_IPC_SOCKET_PATH=${ipcSocketPath} code --disable-workspace-trust -n ${workspacePath}`; + ? `${envVars} xvfb-run --auto-servernum --server-num=1 code --wait --log trace --disable-workspace-trust --disable-gpu --disable-lcd-text --no-sandbox --user-data-dir /roo/.vscode --password-store="basic" -n ${workspacePath}` + : `${envVars} code --disable-workspace-trust -n ${workspacePath}`; if (!logger) { logger = new Logger({ diff --git a/apps/web/package.json b/apps/web/package.json index b158e29acd..b8e06113b8 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -32,6 +32,7 @@ "@radix-ui/react-tooltip": "^1.2.7", "@roo-code-cloud/db": "workspace:^", "@roo-code-cloud/env": "workspace:^", + "@roo-code-cloud/job-auth": "workspace:^", "@roo-code/types": "^1.30.0", "@sentry/nextjs": "^9.23.0", "@tailwindcss/postcss": "^4.1.8", diff --git a/apps/web/src/actions/auth.ts b/apps/web/src/actions/auth.ts index 5f193d740f..c73b79a5b4 100644 --- a/apps/web/src/actions/auth.ts +++ b/apps/web/src/actions/auth.ts @@ -7,6 +7,7 @@ import { Env } from '@roo-code-cloud/env'; import { type AuthResult, type ApiAuthResult, isOrgRole } from '@/types'; import { logger } from '@/lib/server'; +import { validateJobToken } from '@roo-code-cloud/job-auth'; // import { // type AgentTokenPayload, // validateAgentToken, @@ -44,64 +45,39 @@ export async function authorize(): Promise { * Validates authentication and authorization for API endpoints. */ export async function authorizeApi( - _request: NextRequest, + request: NextRequest, ): Promise { - return authorize(); + const authHeader = request.headers.get('authorization'); - // const isAgent = request.headers.get('authorization')?.startsWith('Bearer '); + if (!authHeader?.startsWith('Bearer ')) { + return authorize(); // Fall back to Clerk auth + } - // if (!isAgent) { - // return authorize(); - // } + const token = authHeader.slice(7); + if (!token) { + return { + success: false, + error: 'Unauthorized: Malformed authorization header', + }; + } - // const startTime = Date.now(); + // Try job token first + try { + const jobContext = await validateJobToken(token); + return { + success: true, + userType: 'job', + userId: jobContext.userId, + orgId: jobContext.orgId || null, + jobId: jobContext.jobId, + }; + } catch { + // If job token validation fails, try agent token + // (existing agent token code would go here) - // try { - // const authHeader = request.headers.get('authorization'); - - // if (!authHeader?.startsWith('Bearer ')) { - // return { - // success: false, - // error: 'Unauthorized: Missing authorization header', - // }; - // } - - // const token = authHeader.slice(7); - - // if (!token) { - // return { - // success: false, - // error: 'Unauthorized: Malformed authorization header', - // }; - // } - - // let payload: AgentTokenPayload; - - // try { - // payload = await validateAgentToken(token); - // } catch { - // return { success: false, error: 'Unauthorized: Invalid token' }; - // } - - // const { agent_id: userId, org_id: orgId } = payload; - - // updateAgentUsage( - // userId, - // new URL(request.url).pathname, - // request.method, - // 200, - // Date.now() - startTime, - // request.headers.get('user-agent') || undefined, - // ); - - // return { success: true, userType: 'agent', userId, orgId }; - // } catch (error) { - // console.error( - // `authorizeApi: ${error instanceof Error ? error.message : 'Unknown error'}`, - // ); - - // return { success: false, error: 'Unauthorized: Unexpected error' }; - // } + // If both fail, fall back to Clerk + return authorize(); + } } /** diff --git a/apps/web/src/actions/organizationSettings.ts b/apps/web/src/actions/organizationSettings.ts index 4fb54ad922..d1df59256d 100644 --- a/apps/web/src/actions/organizationSettings.ts +++ b/apps/web/src/actions/organizationSettings.ts @@ -9,13 +9,13 @@ import { organizationDefaultSettingsSchema, organizationCloudSettingsSchema, ORGANIZATION_ALLOW_ALL, - ORGANIZATION_DEFAULT, } from '@roo-code/types'; import { AuditLogTargetType, db, orgSettings } from '@roo-code-cloud/db/server'; import { authorize } from './auth'; import { insertAuditLog } from './auditLogs'; +import { getOrganizationSettingsByOrgId } from '@/lib/server/organizationSettings'; export async function getOrganizationSettings(): Promise { const authResult = await authorize(); @@ -24,18 +24,7 @@ export async function getOrganizationSettings(): Promise { throw new Error('Unauthorized'); } - // Organization settings are only available for organizations, not personal accounts - if (!authResult.orgId) { - return ORGANIZATION_DEFAULT; - } - - const settings = await db - .select() - .from(orgSettings) - .where(eq(orgSettings.orgId, authResult.orgId)) - .limit(1); - - return settings[0] || ORGANIZATION_DEFAULT; + return getOrganizationSettingsByOrgId(authResult.orgId); } /** diff --git a/apps/web/src/app/api/organization-settings/route.ts b/apps/web/src/app/api/organization-settings/route.ts index 66ec10a06c..7fc1ca5913 100644 --- a/apps/web/src/app/api/organization-settings/route.ts +++ b/apps/web/src/app/api/organization-settings/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { authorizeApi } from '@/actions/auth'; -import { getOrganizationSettings } from '@/actions/organizationSettings'; +import { getOrganizationSettingsByOrgId } from '@/lib/server/organizationSettings'; export async function GET(request: NextRequest) { try { @@ -14,7 +14,7 @@ export async function GET(request: NextRequest) { ); } - const settings = await getOrganizationSettings(); + const settings = await getOrganizationSettingsByOrgId(authResult.orgId); return NextResponse.json(settings); } catch (error) { diff --git a/apps/web/src/lib/server/organizationSettings.ts b/apps/web/src/lib/server/organizationSettings.ts new file mode 100644 index 0000000000..7f40d1f97c --- /dev/null +++ b/apps/web/src/lib/server/organizationSettings.ts @@ -0,0 +1,29 @@ +import { eq } from 'drizzle-orm'; + +import { + type OrganizationSettings, + ORGANIZATION_DEFAULT, +} from '@roo-code/types'; + +import { db, orgSettings } from '@roo-code-cloud/db/server'; + +/** + * Fetches organization settings by organization ID. + * Returns default settings for personal accounts (null orgId) or when no settings exist. + */ +export async function getOrganizationSettingsByOrgId( + orgId: string | null, +): Promise { + // Organization settings are only available for organizations, not personal accounts + if (!orgId) { + return ORGANIZATION_DEFAULT; + } + + const settings = await db + .select() + .from(orgSettings) + .where(eq(orgSettings.orgId, orgId)) + .limit(1); + + return settings[0] || ORGANIZATION_DEFAULT; +} diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index 8bbdc07b30..be37633e01 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -1,5 +1,6 @@ // import type { NextRequest } from 'next/server'; import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; +import type { NextRequest } from 'next/server'; const isUnprotectedRoute = createRouteMatcher([ '/sign-in(.*)', @@ -10,18 +11,18 @@ const isUnprotectedRoute = createRouteMatcher([ '/', ]); -// const isApiRoute = createRouteMatcher(['/api(.*)']); +const isApiRoute = createRouteMatcher(['/api(.*)']); -// const isAgentRequest = (req: NextRequest) => { -// return req.headers.get('authorization')?.startsWith('Bearer '); -// }; +const isAgentRequest = (req: NextRequest) => { + return req.headers.get('authorization')?.startsWith('Bearer '); +}; export default clerkMiddleware( async (auth, req) => { - // const isUnprotected = - // isUnprotectedRoute(req) || (isApiRoute(req) && isAgentRequest(req)); + const isUnprotected = + isUnprotectedRoute(req) || (isApiRoute(req) && isAgentRequest(req)); - if (!isUnprotectedRoute(req)) { + if (!isUnprotected) { await auth.protect(); } }, diff --git a/apps/web/src/types/api.ts b/apps/web/src/types/api.ts index 9139169be7..fed7a60004 100644 --- a/apps/web/src/types/api.ts +++ b/apps/web/src/types/api.ts @@ -2,7 +2,7 @@ * UserType */ -const userTypes = ['user', 'agent'] as const; +const userTypes = ['user', 'agent', 'job'] as const; export type UserType = (typeof userTypes)[number]; @@ -48,6 +48,18 @@ export type AgentAuthSuccess = { orgId: string; }; +export type JobAuthSuccess = { + success: true; + userType: 'job'; + userId: string; + orgId: string | null; + jobId: string; +}; + export type AuthResult = UserAuthSuccess | AuthError; -export type ApiAuthResult = UserAuthSuccess | AgentAuthSuccess | AuthError; +export type ApiAuthResult = + | UserAuthSuccess + | AgentAuthSuccess + | JobAuthSuccess + | AuthError; diff --git a/packages/db/drizzle/0002_slippery_alex_power.sql b/packages/db/drizzle/0002_slippery_alex_power.sql new file mode 100644 index 0000000000..9b7ebbd342 --- /dev/null +++ b/packages/db/drizzle/0002_slippery_alex_power.sql @@ -0,0 +1,2 @@ +ALTER TABLE "cloud_jobs" ADD COLUMN "user_id" text;--> statement-breakpoint +ALTER TABLE "cloud_jobs" ADD CONSTRAINT "cloud_jobs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/packages/db/drizzle/meta/0002_snapshot.json b/packages/db/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000000..ac3cf2dcaf --- /dev/null +++ b/packages/db/drizzle/meta/0002_snapshot.json @@ -0,0 +1,1075 @@ +{ + "id": "54e49e4d-a4c4-46cb-b7a1-774b7e176b8e", + "prevId": "6bc7f061-a706-48b4-bcd1-4835f74a9505", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent_request_logs": { + "name": "agent_request_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "response_time_ms": { + "name": "response_time_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_request_logs_agent_id_idx": { + "name": "agent_request_logs_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_request_logs_org_id_idx": { + "name": "agent_request_logs_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_request_logs_created_at_idx": { + "name": "agent_request_logs_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_request_logs_agent_id_agents_id_fk": { + "name": "agent_request_logs_agent_id_agents_id_fk", + "tableFrom": "agent_request_logs", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_request_logs_organization_id_organizations_id_fk": { + "name": "agent_request_logs_organization_id_organizations_id_fk", + "tableFrom": "agent_request_logs", + "tableTo": "organizations", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_org_id_idx": { + "name": "agents_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_active_idx": { + "name": "agents_active_idx", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"agents\".\"is_active\" = 1", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_last_used_idx": { + "name": "agents_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_organization_id_organizations_id_fk": { + "name": "agents_organization_id_organizations_id_fk", + "tableFrom": "agents", + "tableTo": "organizations", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_created_by_user_id_users_id_fk": { + "name": "agents_created_by_user_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_value": { + "name": "new_value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "audit_logs_user_id_idx": { + "name": "audit_logs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_logs_organization_id_idx": { + "name": "audit_logs_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_logs_target_idx": { + "name": "audit_logs_target_idx", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_logs_created_at_idx": { + "name": "audit_logs_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "audit_logs_organization_id_organizations_id_fk": { + "name": "audit_logs_organization_id_organizations_id_fk", + "tableFrom": "audit_logs", + "tableTo": "organizations", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_jobs": { + "name": "cloud_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "cloud_jobs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "cloud_jobs_user_id_users_id_fk": { + "name": "cloud_jobs_user_id_users_id_fk", + "tableFrom": "cloud_jobs", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_settings": { + "name": "organization_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "cloud_settings": { + "name": "cloud_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_settings": { + "name": "default_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "allow_list": { + "name": "allow_list", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"allowAll\":true,\"providers\":{}}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_settings_created_at_idx": { + "name": "organization_settings_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_settings_organization_id_organizations_id_fk": { + "name": "organization_settings_organization_id_organizations_id_fk", + "tableFrom": "organization_settings", + "tableTo": "organizations", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity": { + "name": "entity", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organizations_created_at_idx": { + "name": "organizations_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_shares": { + "name": "task_shares", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_shares_share_token_idx": { + "name": "task_shares_share_token_idx", + "columns": [ + { + "expression": "share_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_shares_task_id_idx": { + "name": "task_shares_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_shares_org_id_idx": { + "name": "task_shares_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_shares_expires_at_idx": { + "name": "task_shares_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_shares_created_by_user_id_idx": { + "name": "task_shares_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_shares_visibility_idx": { + "name": "task_shares_visibility_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_shares_organization_id_organizations_id_fk": { + "name": "task_shares_organization_id_organizations_id_fk", + "tableFrom": "task_shares", + "tableTo": "organizations", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "task_shares_created_by_user_id_users_id_fk": { + "name": "task_shares_created_by_user_id_users_id_fk", + "tableFrom": "task_shares", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_shares_share_token_unique": { + "name": "task_shares_share_token_unique", + "nullsNotDistinct": false, + "columns": ["share_token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_role": { + "name": "organization_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity": { + "name": "entity", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_organization_id_idx": { + "name": "users_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_organization_role_idx": { + "name": "users_organization_role_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_organization_id_organizations_id_fk": { + "name": "users_organization_id_organizations_id_fk", + "tableFrom": "users", + "tableTo": "organizations", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index fdb814eb92..f1ce8827b2 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1750703234599, "tag": "0001_noisy_captain_stacy", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1751397021767, + "tag": "0002_slippery_alex_power", + "breakpoints": true } ] } diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index aa53e000c8..6b3c078eed 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -301,6 +301,7 @@ export const cloudJobs = pgTable('cloud_jobs', { result: jsonb('result'), error: text('error'), slackThreadTs: text('slack_thread_ts'), + userId: text('user_id').references(() => users.id), startedAt: timestamp('started_at'), completedAt: timestamp('completed_at'), createdAt: timestamp('created_at').notNull().defaultNow(), diff --git a/packages/env/src/index.ts b/packages/env/src/index.ts index a0f4113bbb..6180e672b1 100644 --- a/packages/env/src/index.ts +++ b/packages/env/src/index.ts @@ -11,6 +11,8 @@ export const Env = createEnv({ CLICKHOUSE_URL: z.string().min(1), CLICKHOUSE_PASSWORD: z.string().min(1), CLERK_SECRET_KEY: z.string().min(1), + JOB_AUTH_PRIVATE_KEY: z.string().min(1), + JOB_AUTH_PUBLIC_KEY: z.string().min(1), }, client: {}, // You need to destructure all the keys manually. @@ -21,5 +23,7 @@ export const Env = createEnv({ CLICKHOUSE_URL: process.env.CLICKHOUSE_URL, CLICKHOUSE_PASSWORD: process.env.CLICKHOUSE_PASSWORD, CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY, + JOB_AUTH_PRIVATE_KEY: process.env.JOB_AUTH_PRIVATE_KEY, + JOB_AUTH_PUBLIC_KEY: process.env.JOB_AUTH_PUBLIC_KEY, }, }); diff --git a/packages/job-auth/eslint.config.mjs b/packages/job-auth/eslint.config.mjs new file mode 100644 index 0000000000..c072244298 --- /dev/null +++ b/packages/job-auth/eslint.config.mjs @@ -0,0 +1,4 @@ +import { config } from "@roo-code-cloud/config-eslint/base" + +/** @type {import("eslint").Linter.Config} */ +export default [...config] diff --git a/packages/job-auth/package.json b/packages/job-auth/package.json new file mode 100644 index 0000000000..62d1e5b913 --- /dev/null +++ b/packages/job-auth/package.json @@ -0,0 +1,24 @@ +{ + "name": "@roo-code-cloud/job-auth", + "version": "0.1.0", + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "lint": "eslint src --ext=ts --max-warnings=0", + "check-types": "tsc --noEmit", + "test": "dotenvx run -f ../../.env.test -- vitest", + "clean": "rimraf .turbo" + }, + "dependencies": { + "jsonwebtoken": "^9.0.0", + "zod": "^3.22.0", + "@roo-code-cloud/env": "workspace:*" + }, + "devDependencies": { + "@types/jsonwebtoken": "^9.0.0", + "@roo-code-cloud/config-eslint": "workspace:^", + "@roo-code-cloud/config-typescript": "workspace:*", + "@types/node": "^24.0.3", + "vitest": "^3.2.4" + } +} diff --git a/packages/job-auth/src/__tests__/index.test.ts b/packages/job-auth/src/__tests__/index.test.ts new file mode 100644 index 0000000000..40c7e6957b --- /dev/null +++ b/packages/job-auth/src/__tests__/index.test.ts @@ -0,0 +1,253 @@ +// pnpm test --filter @roo-code-cloud/job-auth + +import jwt from 'jsonwebtoken'; + +import { + createJobToken, + validateJobToken, + type JobTokenPayload, +} from '../index'; + +describe('job-auth', () => { + const testPrivateKey = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVTWTozeXjKjcNk+g +zTWjJHlt9W3NOY7Wr2egTq3W/2ehRANCAAQdNAio5NZLvXJ2mcNBkhzcAA63g17y +/1uIE2wJYtYN002cxgqqzyWIpZOB0BV9Bm6TIMBbeigHo83EPP4SAuIQ +-----END PRIVATE KEY-----`; + + beforeEach(() => { + // Mock Date.now() to return a consistent timestamp + vi.useFakeTimers(); + vi.setSystemTime(new Date('2023-01-01T00:00:00Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + describe('createJobToken', () => { + it('should create a valid job token', async () => { + const jobId = 'job-123'; + const userId = 'user-456'; + const orgId = 'org-789'; + const timeoutMs = 30 * 60 * 1000; // 30 minutes + + const token = await createJobToken(jobId, userId, orgId, timeoutMs); + + expect(typeof token).toBe('string'); + expect(token.split('.')).toHaveLength(3); // JWT has 3 parts + + // Verify the token can be decoded and has correct structure + const decoded = jwt.decode(token) as JobTokenPayload; + expect(decoded).toEqual({ + iss: 'rcc', + sub: jobId, + exp: 1672531200 + 30 * 60 + 5 * 60, // timeout + grace period + iat: 1672531200, // 2023-01-01T00:00:00Z in seconds + nbf: 1672531200 - 30, // iat - clock skew grace + v: 1, + r: { + u: userId, + o: orgId, + t: 'cj', + }, + }); + }); + + it('should create token without orgId when null', async () => { + const jobId = 'job-123'; + const userId = 'user-456'; + const orgId = null; + const timeoutMs = 30 * 60 * 1000; + + const token = await createJobToken(jobId, userId, orgId, timeoutMs); + const decoded = jwt.decode(token) as JobTokenPayload; + + expect(decoded.r.o).toBeUndefined(); + expect(decoded.r.u).toBe(userId); + }); + + it('should use correct issuer', async () => { + const token = await createJobToken('job-1', 'user-1', null, 1000); + const decoded = jwt.decode(token) as JobTokenPayload; + + expect(decoded.iss).toBe('rcc'); + }); + + it('should set correct expiration time with grace period', async () => { + const timeoutMs = 15 * 60 * 1000; // 15 minutes + const token = await createJobToken('job-1', 'user-1', null, timeoutMs); + const decoded = jwt.decode(token) as JobTokenPayload; + + const expectedExp = decoded.iat + Math.floor(timeoutMs / 1000) + 5 * 60; // timeout + 5 min grace + expect(decoded.exp).toBe(expectedExp); + }); + }); + + describe('validateJobToken', () => { + const validPayload: JobTokenPayload = { + iss: 'rcc', + sub: 'job-123', + exp: Math.floor(Date.now() / 1000) + 15 * 60, + iat: Math.floor(Date.now() / 1000), + nbf: Math.floor(Date.now() / 1000) - 30, + v: 1, + r: { + u: 'user-456', + o: 'org-789', + t: 'cj', + }, + }; + + it('should validate and return context for valid token', async () => { + const token = jwt.sign(validPayload, testPrivateKey, { + algorithm: 'ES256', + }); + const result = await validateJobToken(token); + + expect(result).toEqual({ + jobId: 'job-123', + userId: 'user-456', + orgId: 'org-789', + tokenType: 'cj', + version: 1, + }); + }); + + it('should validate token without orgId', async () => { + const payloadWithoutOrg = { + ...validPayload, + r: { + u: 'user-456', + t: 'cj' as const, + }, + }; + + const token = jwt.sign(payloadWithoutOrg, testPrivateKey, { + algorithm: 'ES256', + }); + const result = await validateJobToken(token); + + expect(result.orgId).toBeUndefined(); + expect(result.userId).toBe('user-456'); + }); + + it('should throw error for invalid JWT signature', async () => { + // Generate a different private key for testing wrong signature + const wrongPrivateKey = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDCtVZ3X8QqSHLcrp +NYfy87xEuVYS3cU3xztnRADyBJKhRANCAASXyME7rnCKJvIN+W9aYEkOjfCUk4Q6 +G4d1mCKQT05YKo5JdhxbGD3gm3lP/U3NhY+GxA6Dy1BTAYy+dR7prjly +-----END PRIVATE KEY-----`; + const tokenWithWrongSecret = jwt.sign(validPayload, wrongPrivateKey, { + algorithm: 'ES256', + }); + + await expect(validateJobToken(tokenWithWrongSecret)).rejects.toThrow( + 'invalid signature', + ); + }); + + it('should throw error for expired JWT', async () => { + const expiredPayload = { + ...validPayload, + exp: Math.floor(Date.now() / 1000) - 3600, // expired 1 hour ago + }; + const expiredToken = jwt.sign(expiredPayload, testPrivateKey, { + algorithm: 'ES256', + }); + + await expect(validateJobToken(expiredToken)).rejects.toThrow( + 'jwt expired', + ); + }); + + it('should throw error for wrong issuer', async () => { + const wrongIssuerPayload = { + ...validPayload, + iss: 'wrong-issuer', + }; + const token = jwt.sign(wrongIssuerPayload, testPrivateKey, { + algorithm: 'ES256', + }); + + await expect(validateJobToken(token)).rejects.toThrow( + 'jwt issuer invalid. expected: rcc', + ); + }); + + it('should throw specific error for invalid token structure', async () => { + const invalidPayload = { + ...validPayload, + r: { + u: '', // Invalid: empty string + o: 'org-789', + t: 'cj', + }, + }; + const token = jwt.sign(invalidPayload, testPrivateKey, { + algorithm: 'ES256', + }); + + await expect(validateJobToken(token)).rejects.toThrow( + 'Invalid job token structure: r.u: User ID is required', + ); + }); + + it('should throw error for wrong token type', async () => { + const wrongTypePayload = { + ...validPayload, + r: { + u: 'user-456', + o: 'org-789', + t: 'agent', // Should be 'cj' + }, + }; + const token = jwt.sign(wrongTypePayload, testPrivateKey, { + algorithm: 'ES256', + }); + + await expect(validateJobToken(token)).rejects.toThrow( + 'Invalid job token structure', + ); + }); + + it('should throw error for wrong version', async () => { + const wrongVersionPayload = { + ...validPayload, + v: 2, // Should be 1 + }; + const token = jwt.sign(wrongVersionPayload, testPrivateKey, { + algorithm: 'ES256', + }); + + await expect(validateJobToken(token)).rejects.toThrow( + 'Invalid job token structure: v: Version must be 1', + ); + }); + + it('should validate token created by createJobToken function', async () => { + const jobId = 'test-job-456'; + const userId = 'test-user-789'; + const orgId = 'test-org-123'; + const timeoutMs = 30 * 60 * 1000; + + const token = await createJobToken(jobId, userId, orgId, timeoutMs); + const validatedContext = await validateJobToken(token); + + expect(validatedContext.jobId).toBe(jobId); + expect(validatedContext.userId).toBe(userId); + expect(validatedContext.orgId).toBe(orgId); + expect(validatedContext.tokenType).toBe('cj'); + expect(validatedContext.version).toBe(1); + }); + + it('should handle malformed JWT', async () => { + const malformedToken = 'not.a.valid.jwt.token'; + + await expect(validateJobToken(malformedToken)).rejects.toThrow( + 'jwt malformed', + ); + }); + }); +}); diff --git a/packages/job-auth/src/index.ts b/packages/job-auth/src/index.ts new file mode 100644 index 0000000000..f708fb5696 --- /dev/null +++ b/packages/job-auth/src/index.ts @@ -0,0 +1,74 @@ +import jwt from 'jsonwebtoken'; +import { Env } from '@roo-code-cloud/env'; +import { + jobTokenPayloadSchema, + type JobTokenPayload, + type JobTokenContext, +} from './types'; + +export * from './types'; + +const ISSUER = 'rcc'; + +export async function createJobToken( + jobId: string, + userId: string, + orgId: string | null, + timeoutMs: number, +): Promise { + const now = Math.floor(Date.now() / 1000); + const gracePeriod = 5 * 60; // 5 minutes + const clockSkewGrace = 30; // 30 seconds + + const payload: JobTokenPayload = { + iss: ISSUER, + sub: jobId, + exp: now + Math.floor(timeoutMs / 1000) + gracePeriod, + iat: now, + nbf: now - clockSkewGrace, + v: 1, + r: { + u: userId, + o: orgId || undefined, + t: 'cj', + }, + }; + + const privateKey = Buffer.from(Env.JOB_AUTH_PRIVATE_KEY, 'base64').toString( + 'utf-8', + ); + return jwt.sign(payload, privateKey, { algorithm: 'ES256' }); +} + +export async function validateJobToken( + token: string, +): Promise { + const publicKey = Buffer.from(Env.JOB_AUTH_PUBLIC_KEY, 'base64').toString( + 'utf-8', + ); + const rawPayload = jwt.verify(token, publicKey, { + algorithms: ['ES256'], + clockTolerance: 60, // 60 seconds clock skew tolerance for tests + ignoreNotBefore: Env.NODE_ENV === 'test', // Ignore nbf in tests due to fake timers + issuer: ISSUER, + }); + + const parseResult = jobTokenPayloadSchema.safeParse(rawPayload); + + if (!parseResult.success) { + const validationErrors = parseResult.error.errors + .map((err) => `${err.path.join('.')}: ${err.message}`) + .join(', '); + throw new Error(`Invalid job token structure: ${validationErrors}`); + } + + const payload = parseResult.data; + + return { + jobId: payload.sub, + userId: payload.r.u, + orgId: payload.r.o, + tokenType: payload.r.t, + version: payload.v, + }; +} diff --git a/packages/job-auth/src/types.ts b/packages/job-auth/src/types.ts new file mode 100644 index 0000000000..bf6138329d --- /dev/null +++ b/packages/job-auth/src/types.ts @@ -0,0 +1,27 @@ +import { z } from 'zod'; + +export const jobTokenPayloadSchema = z.object({ + iss: z.string().min(1, 'Issuer (iss) is required'), + sub: z.string().min(1, 'Subject (sub) is required'), // CloudJob ID + exp: z.number().int().positive('Expiration (exp) must be a positive integer'), + iat: z.number().int().positive('Issued at (iat) must be a positive integer'), + nbf: z.number().int().positive('Not before (nbf) must be a positive integer'), + v: z.literal(1, { errorMap: () => ({ message: 'Version must be 1' }) }), + r: z.object({ + u: z.string().min(1, 'User ID is required'), + o: z.string().optional(), // Organization ID (optional) + t: z.literal('cj', { + errorMap: () => ({ message: 'Token type must be "cj"' }), + }), + }), +}); + +export type JobTokenPayload = z.infer; + +export interface JobTokenContext { + jobId: string; + userId: string; + orgId?: string; + tokenType: 'cj'; + version: number; +} diff --git a/packages/job-auth/tsconfig.json b/packages/job-auth/tsconfig.json new file mode 100644 index 0000000000..49f9f64bcd --- /dev/null +++ b/packages/job-auth/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@roo-code-cloud/config-typescript/base.json", + "compilerOptions": { + "types": ["vitest/globals"] + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/job-auth/vitest.config.ts b/packages/job-auth/vitest.config.ts new file mode 100644 index 0000000000..bfa72f817c --- /dev/null +++ b/packages/job-auth/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + watch: false, + environment: 'node', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 423cf4de16..e478b52af1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: '@roo-code-cloud/ipc': specifier: workspace:^ version: link:../../packages/ipc + '@roo-code-cloud/job-auth': + specifier: workspace:^ + version: link:../../packages/job-auth '@roo-code/types': specifier: ^1.30.0 version: 1.30.0 @@ -209,6 +212,9 @@ importers: '@roo-code-cloud/env': specifier: workspace:^ version: link:../../packages/env + '@roo-code-cloud/job-auth': + specifier: workspace:^ + version: link:../../packages/job-auth '@roo-code/types': specifier: ^1.30.0 version: 1.30.0 @@ -531,6 +537,34 @@ importers: specifier: ^3.2.3 version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + packages/job-auth: + dependencies: + '@roo-code-cloud/env': + specifier: workspace:* + version: link:../env + jsonwebtoken: + specifier: ^9.0.0 + version: 9.0.2 + zod: + specifier: ^3.22.0 + version: 3.25.41 + devDependencies: + '@roo-code-cloud/config-eslint': + specifier: workspace:^ + version: link:../config-eslint + '@roo-code-cloud/config-typescript': + specifier: workspace:* + version: link:../config-typescript + '@types/jsonwebtoken': + specifier: ^9.0.0 + version: 9.0.10 + '@types/node': + specifier: ^24.0.3 + version: 24.0.3 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + packages: '@adobe/css-tools@4.4.3':