Skip to content

Commit a5d43b0

Browse files
committed
JWT scopes for tags and batches can now access runs that have the tag or are in the batch
- useTaskTrigger can now submit options - auto-generated batch trigger public access tokens no longer need each individual run ID scope
1 parent d67023a commit a5d43b0

20 files changed

+229
-186
lines changed

apps/webapp/app/presenters/v3/ApiRetrieveBatchPresenter.server.ts

-32
This file was deleted.

apps/webapp/app/presenters/v3/ApiRetrieveRunPresenter.server.ts

+36-38
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import assertNever from "assert-never";
1515
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
1616
import { generatePresignedUrl } from "~/v3/r2.server";
1717
import { BasePresenter } from "./basePresenter.server";
18-
import { prisma } from "~/db.server";
18+
import { $replica, prisma } from "~/db.server";
1919

2020
// Build 'select' object
2121
const commonRunSelect = {
@@ -59,48 +59,46 @@ type CommonRelatedRun = Prisma.Result<
5959
"findFirstOrThrow"
6060
>;
6161

62+
type FoundRun = NonNullable<Awaited<ReturnType<typeof ApiRetrieveRunPresenter.findRun>>>;
63+
6264
export class ApiRetrieveRunPresenter extends BasePresenter {
63-
public async call(
64-
friendlyId: string,
65-
env: AuthenticatedEnvironment
66-
): Promise<RetrieveRunResponse | undefined> {
67-
return this.traceWithEnv("call", env, async (span) => {
68-
const taskRun = await this._replica.taskRun.findFirst({
69-
where: {
70-
friendlyId,
71-
runtimeEnvironmentId: env.id,
72-
},
73-
include: {
74-
attempts: true,
75-
lockedToVersion: true,
76-
schedule: true,
77-
tags: true,
78-
batch: {
79-
select: {
80-
id: true,
81-
friendlyId: true,
82-
},
65+
public static async findRun(friendlyId: string, env: AuthenticatedEnvironment) {
66+
return $replica.taskRun.findFirst({
67+
where: {
68+
friendlyId,
69+
runtimeEnvironmentId: env.id,
70+
},
71+
include: {
72+
attempts: true,
73+
lockedToVersion: true,
74+
schedule: true,
75+
tags: true,
76+
batch: {
77+
select: {
78+
id: true,
79+
friendlyId: true,
8380
},
84-
parentTaskRun: {
85-
select: commonRunSelect,
86-
},
87-
rootTaskRun: {
88-
select: commonRunSelect,
89-
},
90-
childRuns: {
91-
select: {
92-
...commonRunSelect,
93-
},
81+
},
82+
parentTaskRun: {
83+
select: commonRunSelect,
84+
},
85+
rootTaskRun: {
86+
select: commonRunSelect,
87+
},
88+
childRuns: {
89+
select: {
90+
...commonRunSelect,
9491
},
9592
},
96-
});
97-
98-
if (!taskRun) {
99-
logger.debug("Task run not found", { friendlyId, envId: env.id });
100-
101-
return undefined;
102-
}
93+
},
94+
});
95+
}
10396

97+
public async call(
98+
taskRun: FoundRun,
99+
env: AuthenticatedEnvironment
100+
): Promise<RetrieveRunResponse | undefined> {
101+
return this.traceWithEnv("call", env, async (span) => {
104102
let $payload: any;
105103
let $payloadPresignedUrl: string | undefined;
106104
let $output: any;
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { json } from "@remix-run/server-runtime";
22
import { z } from "zod";
3-
import { ApiRetrieveBatchPresenter } from "~/presenters/v3/ApiRetrieveBatchPresenter.server";
3+
import { $replica } from "~/db.server";
44
import { createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server";
55

66
const ParamsSchema = z.object({
@@ -12,20 +12,28 @@ export const loader = createLoaderApiRoute(
1212
params: ParamsSchema,
1313
allowJWT: true,
1414
corsStrategy: "all",
15+
findResource: (params, auth) => {
16+
return $replica.batchTaskRun.findFirst({
17+
where: {
18+
friendlyId: params.batchId,
19+
runtimeEnvironmentId: auth.environment.id,
20+
},
21+
});
22+
},
1523
authorization: {
1624
action: "read",
17-
resource: (params) => ({ batch: params.batchId }),
25+
resource: (batch) => ({ batch: batch.friendlyId }),
1826
superScopes: ["read:runs", "read:all", "admin"],
1927
},
2028
},
21-
async ({ params, authentication }) => {
22-
const presenter = new ApiRetrieveBatchPresenter();
23-
const result = await presenter.call(params.batchId, authentication.environment);
24-
25-
if (!result) {
26-
return json({ error: "Batch not found" }, { status: 404 });
27-
}
28-
29-
return json(result);
29+
async ({ resource: batch }) => {
30+
return json({
31+
id: batch.friendlyId,
32+
status: batch.status,
33+
idempotencyKey: batch.idempotencyKey ?? undefined,
34+
createdAt: batch.createdAt,
35+
updatedAt: batch.updatedAt,
36+
runCount: batch.runCount,
37+
});
3038
}
3139
);

apps/webapp/app/routes/api.v1.packets.$.ts

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const loader = createLoaderApiRoute(
4545
params: ParamsSchema,
4646
allowJWT: true,
4747
corsStrategy: "all",
48+
findResource: async () => 1, // This is a dummy function, we don't need to find a resource
4849
},
4950
async ({ params, authentication }) => {
5051
const filename = params["*"];

apps/webapp/app/routes/api.v1.runs.$runParam.reschedule.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,17 @@ export async function action({ request, params }: ActionFunctionArgs) {
6161
return json({ error: "An unknown error occurred" }, { status: 500 });
6262
}
6363

64+
const run = await ApiRetrieveRunPresenter.findRun(
65+
updatedRun.friendlyId,
66+
authenticationResult.environment
67+
);
68+
69+
if (!run) {
70+
return json({ error: "Run not found" }, { status: 404 });
71+
}
72+
6473
const presenter = new ApiRetrieveRunPresenter();
65-
const result = await presenter.call(updatedRun.friendlyId, authenticationResult.environment);
74+
const result = await presenter.call(run, authenticationResult.environment);
6675

6776
if (!result) {
6877
return json({ error: "Run not found" }, { status: 404 });

apps/webapp/app/routes/api.v1.runs.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ export const loader = createLoaderApiRoute(
1212
corsStrategy: "all",
1313
authorization: {
1414
action: "read",
15-
resource: (_, searchParams) => ({ tasks: searchParams["filter[taskIdentifier]"] }),
15+
resource: (_, __, searchParams) => ({ tasks: searchParams["filter[taskIdentifier]"] }),
1616
superScopes: ["read:runs", "read:all", "admin"],
1717
},
18+
findResource: async () => 1, // This is a dummy function, we don't need to find a resource
1819
},
1920
async ({ searchParams, authentication }) => {
2021
const presenter = new ApiRunListPresenter();

apps/webapp/app/routes/api.v1.tasks.batch.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ async function responseHeaders(
133133
const claims = {
134134
sub: environment.id,
135135
pub: true,
136-
scopes: [`read:batch:${batch.id}`].concat(batch.runs.map((r) => `read:runs:${r.id}`)),
136+
scopes: [`read:batch:${batch.id}`],
137137
};
138138

139139
const jwt = await generateJWT({

apps/webapp/app/routes/api.v3.runs.$runId.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,22 @@ export const loader = createLoaderApiRoute(
1212
params: ParamsSchema,
1313
allowJWT: true,
1414
corsStrategy: "all",
15+
findResource: (params, auth) => {
16+
return ApiRetrieveRunPresenter.findRun(params.runId, auth.environment);
17+
},
1518
authorization: {
1619
action: "read",
17-
resource: (params) => ({ runs: params.runId }),
20+
resource: (run) => ({
21+
runs: run.friendlyId,
22+
tags: run.runTags,
23+
batch: run.batch?.friendlyId,
24+
}),
1825
superScopes: ["read:runs", "read:all", "admin"],
1926
},
2027
},
21-
async ({ params, authentication }) => {
28+
async ({ authentication, resource }) => {
2229
const presenter = new ApiRetrieveRunPresenter();
23-
const result = await presenter.call(params.runId, authentication.environment);
30+
const result = await presenter.call(resource, authentication.environment);
2431

2532
if (!result) {
2633
return json(

apps/webapp/app/routes/realtime.v1.batches.$batchId.ts

+10-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { json } from "@remix-run/server-runtime";
21
import { z } from "zod";
32
import { $replica } from "~/db.server";
43
import { realtimeClient } from "~/services/realtimeClientGlobal.server";
@@ -13,24 +12,21 @@ export const loader = createLoaderApiRoute(
1312
params: ParamsSchema,
1413
allowJWT: true,
1514
corsStrategy: "all",
15+
findResource: (params, auth) => {
16+
return $replica.batchTaskRun.findFirst({
17+
where: {
18+
friendlyId: params.batchId,
19+
runtimeEnvironmentId: auth.environment.id,
20+
},
21+
});
22+
},
1623
authorization: {
1724
action: "read",
18-
resource: (params) => ({ batch: params.batchId }),
25+
resource: (batch) => ({ batch: batch.friendlyId }),
1926
superScopes: ["read:runs", "read:all", "admin"],
2027
},
2128
},
22-
async ({ params, authentication, request }) => {
23-
const batchRun = await $replica.batchTaskRun.findFirst({
24-
where: {
25-
friendlyId: params.batchId,
26-
runtimeEnvironmentId: authentication.environment.id,
27-
},
28-
});
29-
30-
if (!batchRun) {
31-
return json({ error: "Batch not found" }, { status: 404 });
32-
}
33-
29+
async ({ authentication, request, resource: batchRun }) => {
3430
return realtimeClient.streamBatch(
3531
request.url,
3632
authentication.environment,

apps/webapp/app/routes/realtime.v1.runs.$runId.ts

+21-13
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,32 @@ export const loader = createLoaderApiRoute(
1313
params: ParamsSchema,
1414
allowJWT: true,
1515
corsStrategy: "all",
16+
findResource: async (params, authentication) => {
17+
return $replica.taskRun.findFirst({
18+
where: {
19+
friendlyId: params.runId,
20+
runtimeEnvironmentId: authentication.environment.id,
21+
},
22+
include: {
23+
batch: {
24+
select: {
25+
friendlyId: true,
26+
},
27+
},
28+
},
29+
});
30+
},
1631
authorization: {
1732
action: "read",
18-
resource: (params) => ({ runs: params.runId }),
33+
resource: (run) => ({
34+
runs: run.friendlyId,
35+
tags: run.runTags,
36+
batch: run.batch?.friendlyId,
37+
}),
1938
superScopes: ["read:runs", "read:all", "admin"],
2039
},
2140
},
22-
async ({ params, authentication, request }) => {
23-
const run = await $replica.taskRun.findFirst({
24-
where: {
25-
friendlyId: params.runId,
26-
runtimeEnvironmentId: authentication.environment.id,
27-
},
28-
});
29-
30-
if (!run) {
31-
return json({ error: "Run not found" }, { status: 404 });
32-
}
33-
41+
async ({ authentication, request, resource: run }) => {
3442
return realtimeClient.streamRun(
3543
request.url,
3644
authentication.environment,

apps/webapp/app/routes/realtime.v1.runs.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ export const loader = createLoaderApiRoute(
1616
searchParams: SearchParamsSchema,
1717
allowJWT: true,
1818
corsStrategy: "all",
19+
findResource: async () => 1, // This is a dummy value, it's not used
1920
authorization: {
2021
action: "read",
21-
resource: (_, searchParams) => searchParams,
22+
resource: (_, __, searchParams) => searchParams,
2223
superScopes: ["read:runs", "read:all", "admin"],
2324
},
2425
},

apps/webapp/app/routes/realtime.v1.streams.$runId.$streamId.ts

+21-13
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,32 @@ export const loader = createLoaderApiRoute(
2424
params: ParamsSchema,
2525
allowJWT: true,
2626
corsStrategy: "all",
27+
findResource: async (params, auth) => {
28+
return $replica.taskRun.findFirst({
29+
where: {
30+
friendlyId: params.runId,
31+
runtimeEnvironmentId: auth.environment.id,
32+
},
33+
include: {
34+
batch: {
35+
select: {
36+
friendlyId: true,
37+
},
38+
},
39+
},
40+
});
41+
},
2742
authorization: {
2843
action: "read",
29-
resource: (params) => ({ runs: params.runId }),
44+
resource: (run) => ({
45+
runs: run.friendlyId,
46+
tags: run.runTags,
47+
batch: run.batch?.friendlyId,
48+
}),
3049
superScopes: ["read:runs", "read:all", "admin"],
3150
},
3251
},
33-
async ({ params, authentication, request }) => {
34-
const run = await $replica.taskRun.findFirst({
35-
where: {
36-
friendlyId: params.runId,
37-
runtimeEnvironmentId: authentication.environment.id,
38-
},
39-
});
40-
41-
if (!run) {
42-
return new Response("Run not found", { status: 404 });
43-
}
44-
52+
async ({ params, request, resource: run }) => {
4553
return realtimeStreams.streamResponse(run.friendlyId, params.streamId, request.signal);
4654
}
4755
);

0 commit comments

Comments
 (0)