Skip to content

Commit 691903c

Browse files
authored
feat(api): accept OATs in preview branch related endpoints (#2517)
Adjusts the authentication in a couple of endpoints to accept OATs too.
1 parent 885ae5e commit 691903c

File tree

6 files changed

+106
-44
lines changed

6 files changed

+106
-44
lines changed

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ export async function action({ request }: ActionFunctionArgs) {
130130
}
131131

132132
const upsertBranchService = new UpsertBranchService();
133-
const result = await upsertBranchService.call(userId, submission.value);
133+
const result = await upsertBranchService.call(
134+
{ type: "userMembership", userId },
135+
submission.value
136+
);
134137

135138
if (result.success) {
136139
if (result.alreadyExisted) {

apps/webapp/app/routes/api.v1.projects.$projectRef.branches.archive.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
22
import { tryCatch } from "@trigger.dev/core";
33
import { z } from "zod";
44
import { prisma } from "~/db.server";
5+
import { authenticateRequest } from "~/services/apiAuth.server";
56
import { ArchiveBranchService } from "~/services/archiveBranch.server";
67
import { logger } from "~/services/logger.server";
7-
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
88

99
const ParamsSchema = z.object({
1010
projectRef: z.string(),
@@ -21,7 +21,12 @@ export async function action({ request, params }: ActionFunctionArgs) {
2121

2222
logger.info("Archive branch", { url: request.url, params });
2323

24-
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
24+
const authenticationResult = await authenticateRequest(request, {
25+
personalAccessToken: true,
26+
organizationAccessToken: true,
27+
apiKey: false,
28+
});
29+
2530
if (!authenticationResult) {
2631
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
2732
}
@@ -50,13 +55,16 @@ export async function action({ request, params }: ActionFunctionArgs) {
5055
archivedAt: true,
5156
},
5257
where: {
53-
organization: {
54-
members: {
55-
some: {
56-
userId: authenticationResult.userId,
57-
},
58-
},
59-
},
58+
organization:
59+
authenticationResult.type === "organizationAccessToken"
60+
? { id: authenticationResult.result.organizationId }
61+
: {
62+
members: {
63+
some: {
64+
userId: authenticationResult.result.userId,
65+
},
66+
},
67+
},
6068
project: {
6169
externalRef: projectRef,
6270
},
@@ -74,9 +82,14 @@ export async function action({ request, params }: ActionFunctionArgs) {
7482
}
7583

7684
const service = new ArchiveBranchService();
77-
const result = await service.call(authenticationResult.userId, {
78-
environmentId: environment.id,
79-
});
85+
const result = await service.call(
86+
authenticationResult.type === "organizationAccessToken"
87+
? { type: "orgId", organizationId: authenticationResult.result.organizationId }
88+
: { type: "userMembership", userId: authenticationResult.result.userId },
89+
{
90+
environmentId: environment.id,
91+
}
92+
);
8093

8194
if (result.success) {
8295
return json(result);

apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { json, LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/server-runtime";
1+
import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/server-runtime";
22
import { tryCatch, UpsertBranchRequestBody } from "@trigger.dev/core/v3";
33
import { z } from "zod";
44
import { prisma } from "~/db.server";
5+
import { authenticateRequest } from "~/services/apiAuth.server";
56
import { logger } from "~/services/logger.server";
67
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
78
import { UpsertBranchService } from "~/services/upsertBranch.server";
@@ -19,7 +20,11 @@ export async function action({ request, params }: ActionFunctionArgs) {
1920

2021
logger.info("project upsert branch", { url: request.url });
2122

22-
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
23+
const authenticationResult = await authenticateRequest(request, {
24+
personalAccessToken: true,
25+
organizationAccessToken: true,
26+
apiKey: false,
27+
});
2328
if (!authenticationResult) {
2429
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
2530
}
@@ -38,13 +43,16 @@ export async function action({ request, params }: ActionFunctionArgs) {
3843
},
3944
where: {
4045
externalRef: projectRef,
41-
organization: {
42-
members: {
43-
some: {
44-
userId: authenticationResult.userId,
45-
},
46-
},
47-
},
46+
organization:
47+
authenticationResult.type === "organizationAccessToken"
48+
? { id: authenticationResult.result.organizationId }
49+
: {
50+
members: {
51+
some: {
52+
userId: authenticationResult.result.userId,
53+
},
54+
},
55+
},
4856
},
4957
});
5058
if (!project) {
@@ -81,11 +89,16 @@ export async function action({ request, params }: ActionFunctionArgs) {
8189
const { branch, env, git } = parsed.data;
8290

8391
const service = new UpsertBranchService();
84-
const result = await service.call(authenticationResult.userId, {
85-
branchName: branch,
86-
parentEnvironmentId: previewEnvironment.id,
87-
git,
88-
});
92+
const result = await service.call(
93+
authenticationResult.type === "organizationAccessToken"
94+
? { type: "orgId", organizationId: authenticationResult.result.organizationId }
95+
: { type: "userMembership", userId: authenticationResult.result.userId },
96+
{
97+
branchName: branch,
98+
parentEnvironmentId: previewEnvironment.id,
99+
git,
100+
}
101+
);
89102

90103
if (!result.success) {
91104
return json({ error: result.error }, { status: 400 });

apps/webapp/app/routes/resources.branches.archive.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ export async function action({ request }: ActionFunctionArgs) {
3737

3838
const archiveBranchService = new ArchiveBranchService();
3939

40-
const result = await archiveBranchService.call(userId, submission.value);
40+
const result = await archiveBranchService.call(
41+
{ type: "userMembership", userId },
42+
{
43+
environmentId: submission.value.environmentId,
44+
}
45+
);
4146

4247
if (result.success) {
4348
return redirectWithSuccessMessage(

apps/webapp/app/services/archiveBranch.server.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,34 @@ export class ArchiveBranchService {
1010
this.#prismaClient = prismaClient;
1111
}
1212

13-
public async call(userId: string, { environmentId }: { environmentId: string }) {
13+
public async call(
14+
// The orgFilter approach is not ideal but we need to keep it this way for now because of how the service is used in routes and api endpoints.
15+
// Currently authorization checks are spread across the controller/route layer and the service layer. Often we check in multiple places for org/project membership.
16+
// Ideally we would take care of both the authentication and authorization checks in the controllers and routes.
17+
// That would unify how we handle authorization and org/project membership checks. Also it would make the service layer queries simpler.
18+
orgFilter:
19+
| { type: "userMembership"; userId: string }
20+
| { type: "orgId"; organizationId: string },
21+
{
22+
environmentId,
23+
}: {
24+
environmentId: string;
25+
}
26+
) {
1427
try {
1528
const environment = await this.#prismaClient.runtimeEnvironment.findFirstOrThrow({
1629
where: {
1730
id: environmentId,
18-
organization: {
19-
members: {
20-
some: {
21-
userId: userId,
22-
},
23-
},
24-
},
31+
organization:
32+
orgFilter.type === "userMembership"
33+
? {
34+
members: {
35+
some: {
36+
userId: orgFilter.userId,
37+
},
38+
},
39+
}
40+
: { id: orgFilter.organizationId },
2541
},
2642
include: {
2743
organization: {

apps/webapp/app/services/upsertBranch.server.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,16 @@ export class UpsertBranchService {
1414
this.#prismaClient = prismaClient;
1515
}
1616

17-
public async call(userId: string, { parentEnvironmentId, branchName, git }: CreateBranchOptions) {
17+
public async call(
18+
// The orgFilter approach is not ideal but we need to keep it this way for now because of how the service is used in routes and api endpoints.
19+
// Currently authorization checks are spread across the controller/route layer and the service layer. Often we check in multiple places for org/project membership.
20+
// Ideally we would take care of both the authentication and authorization checks in the controllers and routes.
21+
// That would unify how we handle authorization and org/project membership checks. Also it would make the service layer queries simpler.
22+
orgFilter:
23+
| { type: "userMembership"; userId: string }
24+
| { type: "orgId"; organizationId: string },
25+
{ parentEnvironmentId, branchName, git }: CreateBranchOptions
26+
) {
1827
const sanitizedBranchName = sanitizeBranchName(branchName);
1928
if (!sanitizedBranchName) {
2029
return {
@@ -34,13 +43,16 @@ export class UpsertBranchService {
3443
const parentEnvironment = await this.#prismaClient.runtimeEnvironment.findFirst({
3544
where: {
3645
id: parentEnvironmentId,
37-
organization: {
38-
members: {
39-
some: {
40-
userId: userId,
41-
},
42-
},
43-
},
46+
organization:
47+
orgFilter.type === "userMembership"
48+
? {
49+
members: {
50+
some: {
51+
userId: orgFilter.userId,
52+
},
53+
},
54+
}
55+
: { id: orgFilter.organizationId },
4456
},
4557
include: {
4658
organization: {

0 commit comments

Comments
 (0)