Skip to content

Commit b589739

Browse files
committed
fix(webapp): migrate snapshot-url PUT to createActionApiRoute
Address Devin findings on PR #3679: - Migrate the PUT action handler from the deprecated `authenticateApiRequest` to `createActionApiRoute` so it picks up `allowJWT: true` symmetrically with the GET loader. Without this, the SDK's run-scoped JWT (which is how chat.agent workers authenticate against the API) was rejected on every snapshot upload — the PR worked in tests against the dev API key but would have 401'd in real run-scoped contexts. - Both PUT and GET now go through the same builder pattern with the same config (`findResource`, CORS, JWT), eliminating the style drift the initial implementation introduced.
1 parent f5db19a commit b589739

1 file changed

Lines changed: 32 additions & 44 deletions

File tree

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
21
import { json } from "@remix-run/server-runtime";
32
import { z } from "zod";
43
import { $replica } from "~/db.server";
5-
import { authenticateApiRequest } from "~/services/apiAuth.server";
64
import {
75
chatSnapshotStoragePathForSession,
86
resolveSessionByIdOrExternalId,
97
} from "~/services/realtime/sessions.server";
10-
import { createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server";
8+
import {
9+
createActionApiRoute,
10+
createLoaderApiRoute,
11+
} from "~/services/routeBuilders/apiBuilder.server";
1112
import { generatePresignedUrl } from "~/v3/objectStore.server";
1213

1314
const ParamsSchema = z.object({
@@ -22,47 +23,16 @@ function snapshotKey(session: { friendlyId: string; chatSnapshotStoragePath: str
2223
return session.chatSnapshotStoragePath ?? chatSnapshotStoragePathForSession(session.friendlyId);
2324
}
2425

25-
export async function action({ request, params }: ActionFunctionArgs) {
26-
if (request.method.toUpperCase() !== "PUT") {
27-
return json({ error: "Method Not Allowed" }, { status: 405 });
28-
}
29-
30-
const auth = await authenticateApiRequest(request);
31-
if (!auth) {
32-
return json({ error: "Invalid or Missing API key" }, { status: 401 });
33-
}
34-
35-
const parsed = ParamsSchema.parse(params);
36-
const session = await resolveSessionByIdOrExternalId(
37-
$replica,
38-
auth.environment.id,
39-
parsed.sessionId
40-
);
41-
if (!session) {
42-
return json({ error: "Session not found" }, { status: 404 });
43-
}
26+
const routeConfig = {
27+
params: ParamsSchema,
28+
allowJWT: true,
29+
corsStrategy: "all" as const,
30+
findResource: async (params: z.infer<typeof ParamsSchema>, auth: { environment: { id: string } }) =>
31+
resolveSessionByIdOrExternalId($replica, auth.environment.id, params.sessionId),
32+
};
4433

45-
const signed = await generatePresignedUrl(
46-
auth.environment.project.externalRef,
47-
auth.environment.slug,
48-
snapshotKey(session),
49-
"PUT"
50-
);
51-
if (!signed.success) {
52-
return json({ error: `Failed to generate presigned URL: ${signed.error}` }, { status: 500 });
53-
}
54-
55-
return json({ presignedUrl: signed.url });
56-
}
57-
58-
export const loader = createLoaderApiRoute(
59-
{
60-
params: ParamsSchema,
61-
allowJWT: true,
62-
corsStrategy: "all",
63-
findResource: async (params, auth) =>
64-
resolveSessionByIdOrExternalId($replica, auth.environment.id, params.sessionId),
65-
},
34+
export const { action } = createActionApiRoute(
35+
{ ...routeConfig, method: "PUT" },
6636
async ({ authentication, resource: session }) => {
6737
if (!session) {
6838
return json({ error: "Session not found" }, { status: 404 });
@@ -72,7 +42,7 @@ export const loader = createLoaderApiRoute(
7242
authentication.environment.project.externalRef,
7343
authentication.environment.slug,
7444
snapshotKey(session),
75-
"GET"
45+
"PUT"
7646
);
7747
if (!signed.success) {
7848
return json({ error: `Failed to generate presigned URL: ${signed.error}` }, { status: 500 });
@@ -81,3 +51,21 @@ export const loader = createLoaderApiRoute(
8151
return json({ presignedUrl: signed.url });
8252
}
8353
);
54+
55+
export const loader = createLoaderApiRoute(routeConfig, async ({ authentication, resource: session }) => {
56+
if (!session) {
57+
return json({ error: "Session not found" }, { status: 404 });
58+
}
59+
60+
const signed = await generatePresignedUrl(
61+
authentication.environment.project.externalRef,
62+
authentication.environment.slug,
63+
snapshotKey(session),
64+
"GET"
65+
);
66+
if (!signed.success) {
67+
return json({ error: `Failed to generate presigned URL: ${signed.error}` }, { status: 500 });
68+
}
69+
70+
return json({ presignedUrl: signed.url });
71+
});

0 commit comments

Comments
 (0)