@@ -2,7 +2,7 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
22import { tryCatch } from "@trigger.dev/core" ;
33import { z } from "zod" ;
44import { prisma } from "~/db.server" ;
5- import { authenticateRequest } from "~/services/apiAuth.server" ;
5+ import { authenticateRequestWithScopedApiKey } from "~/services/apiAuth.server" ;
66import { ArchiveBranchService } from "~/services/archiveBranch.server" ;
77import { logger } from "~/services/logger.server" ;
88import { toBranchableEnvironmentType } from "~/utils/branchableEnvironment" ;
@@ -24,15 +24,25 @@ export async function action({ request, params }: ActionFunctionArgs) {
2424
2525 logger . info ( "Archive branch" , { url : request . url , params } ) ;
2626
27- const authenticationResult = await authenticateRequest ( request , {
27+ const authentication = await authenticateRequestWithScopedApiKey ( request , {
2828 personalAccessToken : true ,
2929 organizationAccessToken : true ,
30- apiKey : false ,
30+ apiKey : {
31+ action : "write" ,
32+ resource : { type : "branches" } ,
33+ allowPreviewParent : true ,
34+ } ,
3135 } ) ;
3236
33- if ( ! authenticationResult ) {
34- return json ( { error : "Invalid or Missing Access Token" } , { status : 401 } ) ;
37+ if ( ! authentication . ok ) {
38+ return json ( { error : authentication . error } , { status : authentication . status } ) ;
3539 }
40+ const authenticationResult = authentication . authentication ;
41+
42+ const apiKeyEnvironment =
43+ authenticationResult . type === "apiKey" && authenticationResult . result . ok
44+ ? authenticationResult . result . environment
45+ : undefined ;
3646
3747 const parsedParams = ParamsSchema . safeParse ( params ) ;
3848
@@ -54,26 +64,52 @@ export async function action({ request, params }: ActionFunctionArgs) {
5464
5565 const { env, branch } = parsed . data ;
5666
67+ // API keys can only archive Preview branches
68+ if (
69+ authenticationResult . type === "apiKey" &&
70+ ( ! apiKeyEnvironment ||
71+ apiKeyEnvironment . type !== "PREVIEW" ||
72+ apiKeyEnvironment . parentEnvironmentId !== null ||
73+ env !== "preview" )
74+ ) {
75+ return json (
76+ { error : "API keys must belong to the parent Preview environment." } ,
77+ { status : 403 }
78+ ) ;
79+ }
80+
81+ // API keys can only act on their own project
82+ if (
83+ authenticationResult . type === "apiKey" &&
84+ apiKeyEnvironment ?. project . externalRef !== projectRef
85+ ) {
86+ return json ( { error : "Project not found" } , { status : 404 } ) ;
87+ }
88+
5789 const environmentType = toBranchableEnvironmentType ( env ) ;
90+
91+ const organizationFilter =
92+ authenticationResult . type === "organizationAccessToken"
93+ ? { id : authenticationResult . result . organizationId }
94+ : authenticationResult . type === "apiKey"
95+ ? { id : apiKeyEnvironment ! . organizationId }
96+ : {
97+ members : {
98+ some : {
99+ userId : authenticationResult . result . userId ,
100+ } ,
101+ } ,
102+ } ;
103+
58104 const environments = await prisma . runtimeEnvironment . findMany ( {
59105 select : {
60106 id : true ,
61107 archivedAt : true ,
62108 } ,
63109 where : {
64- organization :
65- authenticationResult . type === "organizationAccessToken"
66- ? { id : authenticationResult . result . organizationId }
67- : {
68- members : {
69- some : {
70- userId : authenticationResult . result . userId ,
71- } ,
72- } ,
73- } ,
110+ organization : organizationFilter ,
74111 // Dev branches are per-org-member: only the owner may archive their own.
75- ...( authenticationResult . type !== "organizationAccessToken" &&
76- environmentType === "DEVELOPMENT"
112+ ...( authenticationResult . type === "personalAccessToken" && environmentType === "DEVELOPMENT"
77113 ? { orgMember : { userId : authenticationResult . result . userId } }
78114 : { } ) ,
79115 project : {
@@ -91,7 +127,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
91127 const activeEnvironments = environments . filter ( ( env ) => env . archivedAt === null ) ;
92128
93129 if (
94- authenticationResult . type === "organizationAccessToken " &&
130+ authenticationResult . type !== "personalAccessToken " &&
95131 environmentType === "DEVELOPMENT" &&
96132 activeEnvironments . length > 1
97133 ) {
@@ -110,15 +146,21 @@ export async function action({ request, params }: ActionFunctionArgs) {
110146 return json ( { error : "Branch already archived" } , { status : 400 } ) ;
111147 }
112148
149+ let orgFilter :
150+ | { type : "userMembership" ; userId : string }
151+ | { type : "orgId" ; organizationId : string } ;
152+ if ( authenticationResult . type === "personalAccessToken" ) {
153+ orgFilter = { type : "userMembership" , userId : authenticationResult . result . userId } ;
154+ } else if ( authenticationResult . type === "organizationAccessToken" ) {
155+ orgFilter = { type : "orgId" , organizationId : authenticationResult . result . organizationId } ;
156+ } else {
157+ orgFilter = { type : "orgId" , organizationId : apiKeyEnvironment ! . organizationId } ;
158+ }
159+
113160 const service = new ArchiveBranchService ( ) ;
114- const result = await service . call (
115- authenticationResult . type === "organizationAccessToken"
116- ? { type : "orgId" , organizationId : authenticationResult . result . organizationId }
117- : { type : "userMembership" , userId : authenticationResult . result . userId } ,
118- {
119- environmentId : environment . id ,
120- }
121- ) ;
161+ const result = await service . call ( orgFilter , {
162+ environmentId : environment . id ,
163+ } ) ;
122164
123165 if ( result . success ) {
124166 return json ( result ) ;
0 commit comments