diff --git a/apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts b/apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts index 2582e9df1..bf393a9d8 100644 --- a/apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts +++ b/apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts @@ -11,6 +11,7 @@ import { prisma } from "~/db.server"; import { env } from "~/env.server"; import { ApiAuthenticationResultSuccess, getOneTimeUseToken } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; +import { extractJwtSigningSecretKey } from "~/services/realtime/jwtAuth.server"; import { determineRealtimeStreamsVersion } from "~/services/realtime/v1StreamsGlobal.server"; import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { resolveIdempotencyKeyTTL } from "~/utils/idempotencyKeys.server"; @@ -85,7 +86,7 @@ const { action, loader } = createActionApiRoute( isCached: false, }), buildResponseHeaders: async (responseBody, cachedEntity) => { - return await responseHeaders(cachedEntity, authentication, triggerClient); + return await responseHeaders(cachedEntity, authentication); }, }); @@ -140,7 +141,12 @@ const { action, loader } = createActionApiRoute( await saveRequestIdempotency(requestIdempotencyKey, "trigger", result.run.id); - const $responseHeaders = await responseHeaders(result.run, authentication, triggerClient); + const $responseHeaders = await responseHeaders(result.run, authentication); + + logger.debug("responseHeaders authentication", { + authentication, + responseHeaders: $responseHeaders, + }); return json( { @@ -170,39 +176,26 @@ const { action, loader } = createActionApiRoute( async function responseHeaders( run: Pick, - authentication: ApiAuthenticationResultSuccess, - triggerClient?: string | null + authentication: ApiAuthenticationResultSuccess ): Promise> { const { environment, realtime } = authentication; - const claimsHeader = JSON.stringify({ + const claims = { sub: environment.id, pub: true, + scopes: [`read:runs:${run.friendlyId}`], realtime, + }; + + const jwt = await internal_generateJWT({ + secretKey: extractJwtSigningSecretKey(environment), + payload: claims, + expirationTime: "1h", }); - if (triggerClient === "browser") { - const claims = { - sub: environment.id, - pub: true, - scopes: [`read:runs:${run.friendlyId}`], - realtime, - }; - - const jwt = await internal_generateJWT({ - secretKey: environment.apiKey, - payload: claims, - expirationTime: "1h", - }); - - return { - "x-trigger-jwt-claims": claimsHeader, - "x-trigger-jwt": jwt, - }; - } - return { - "x-trigger-jwt-claims": claimsHeader, + "x-trigger-jwt-claims": JSON.stringify(claims), + "x-trigger-jwt": jwt, }; } diff --git a/apps/webapp/app/routes/api.v1.tasks.batch.ts b/apps/webapp/app/routes/api.v1.tasks.batch.ts index d21d277c4..4a10ade0e 100644 --- a/apps/webapp/app/routes/api.v1.tasks.batch.ts +++ b/apps/webapp/app/routes/api.v1.tasks.batch.ts @@ -17,6 +17,7 @@ import { import { OutOfEntitlementError } from "~/v3/services/triggerTask.server"; import { HeadersSchema } from "./api.v1.tasks.$taskId.trigger"; import { determineRealtimeStreamsVersion } from "~/services/realtime/v1StreamsGlobal.server"; +import { extractJwtSigningSecretKey } from "~/services/realtime/jwtAuth.server"; const { action, loader } = createActionApiRoute( { @@ -163,7 +164,7 @@ async function responseHeaders( }; const jwt = await generateJWT({ - secretKey: environment.apiKey, + secretKey: extractJwtSigningSecretKey(environment), payload: claims, expirationTime: "1h", }); diff --git a/apps/webapp/app/routes/api.v2.tasks.batch.ts b/apps/webapp/app/routes/api.v2.tasks.batch.ts index 252439b7b..02cbb594c 100644 --- a/apps/webapp/app/routes/api.v2.tasks.batch.ts +++ b/apps/webapp/app/routes/api.v2.tasks.batch.ts @@ -19,6 +19,7 @@ import { BatchProcessingStrategy } from "~/v3/services/batchTriggerV3.server"; import { OutOfEntitlementError } from "~/v3/services/triggerTask.server"; import { HeadersSchema } from "./api.v1.tasks.$taskId.trigger"; import { determineRealtimeStreamsVersion } from "~/services/realtime/v1StreamsGlobal.server"; +import { extractJwtSigningSecretKey } from "~/services/realtime/jwtAuth.server"; const { action, loader } = createActionApiRoute( { @@ -178,7 +179,7 @@ async function responseHeaders( }; const jwt = await generateJWT({ - secretKey: environment.apiKey, + secretKey: extractJwtSigningSecretKey(environment), payload: claims, expirationTime: "1h", }); diff --git a/apps/webapp/app/services/apiAuth.server.ts b/apps/webapp/app/services/apiAuth.server.ts index aac0ecb6a..989aeef8e 100644 --- a/apps/webapp/app/services/apiAuth.server.ts +++ b/apps/webapp/app/services/apiAuth.server.ts @@ -236,6 +236,8 @@ async function authenticateApiKeyWithFailure( case "PUBLIC_JWT": { const validationResults = await validatePublicJwtKey(result.apiKey); + logger.debug("validatePublicJwtKey", { validationResults }); + if (!validationResults.ok) { return validationResults; } diff --git a/apps/webapp/app/services/realtime/jwtAuth.server.ts b/apps/webapp/app/services/realtime/jwtAuth.server.ts index d5950d99d..4f94575a7 100644 --- a/apps/webapp/app/services/realtime/jwtAuth.server.ts +++ b/apps/webapp/app/services/realtime/jwtAuth.server.ts @@ -2,6 +2,7 @@ import { json } from "@remix-run/server-runtime"; import { validateJWT } from "@trigger.dev/core/v3/jwt"; import { findEnvironmentById } from "~/models/runtimeEnvironment.server"; import { AuthenticatedEnvironment } from "../apiAuth.server"; +import { logger } from "../logger.server"; export type ValidatePublicJwtKeySuccess = { ok: true; @@ -38,6 +39,8 @@ export async function validatePublicJwtKey(token: string): Promise { + const handle = await tasks.trigger(taskId, { foo: "bar" }); + console.log(`Task triggered. Run ID: ${handle.id}`); + + let tokenToUse = token; + if (handle.publicAccessToken) { + console.log("Received publicAccessToken in handle, using it for realtime."); + console.log(`Public Access Token: ${handle.publicAccessToken}`); + tokenToUse = handle.publicAccessToken; + } else { + console.log("Using initial token for subsequent requests."); + } + + // 3. Access Run details (Simulating Realtime/Read access) + // If the token changed (which it might if the API returns a specific read-only token), we should use that. + + if (tokenToUse !== token) { + await auth.withAuth( + { accessToken: tokenToUse, baseURL: apiUrl, previewBranch: branch }, + async () => { + console.log(`Subscribing to run ${handle.id} with new token...`); + for await (const run of runs.subscribeToRun(handle.id)) { + console.log(`Run update received. Status: ${run.status}`); + break; + } + } + ); + } else { + console.log(`Subscribing to run ${handle.id} with initial token...`); + for await (const run of runs.subscribeToRun(handle.id)) { + console.log(`Run update received. Status: ${run.status}`); + break; + } + } + + console.log("Realtime/Read access verified."); + } + ); + } catch (error) { + console.error("Error:", error); + } +} + +main(); diff --git a/references/issue-2687/src/trigger/task.ts b/references/issue-2687/src/trigger/task.ts new file mode 100644 index 000000000..990a1b46b --- /dev/null +++ b/references/issue-2687/src/trigger/task.ts @@ -0,0 +1,9 @@ +import { task } from "@trigger.dev/sdk/v3"; + +export const myTask = task({ + id: "issue-2687-task", + run: async (payload: any) => { + console.log("Task running with payload:", payload); + return { message: "Hello World" }; + }, +}); diff --git a/references/issue-2687/trigger.config.ts b/references/issue-2687/trigger.config.ts new file mode 100644 index 000000000..0a3d35cb3 --- /dev/null +++ b/references/issue-2687/trigger.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "@trigger.dev/sdk/v3"; + +export default defineConfig({ + project: process.env.TRIGGER_PROJECT_REF!, + logLevel: "log", + maxDuration: 3600, + retries: { + enabledInDev: true, + default: { + maxAttempts: 3, + minTimeoutInMs: 1000, + maxTimeoutInMs: 10000, + factor: 2, + randomize: true, + }, + }, +});