@@ -75,8 +75,11 @@ import { resolveOrgIdFromSlug } from "~/models/organization.server";
7575import { findProjectBySlug } from "~/models/project.server" ;
7676import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server" ;
7777import { ApiKeysPresenter } from "~/presenters/v3/ApiKeysPresenter.server" ;
78- import { FULL_ACCESS_PRESET_ID } from "@trigger.dev/rbac" ;
7978import { canIssueAdditionalApiKeys } from "~/services/additionalApiKeyIssuance.server" ;
79+ import {
80+ validateCreateApiKeyPreset ,
81+ type ApiKeyPreset ,
82+ } from "~/services/apiKeyPresetValidation.server" ;
8083import { rbac } from "~/services/rbac.server" ;
8184import { dashboardAction , dashboardLoader } from "~/services/routeBuilders/dashboardBuilder" ;
8285import { cn } from "~/utils/cn" ;
@@ -88,8 +91,6 @@ const ApiKeySearchParams = z.object({
8891 showRevoked : z . preprocess ( ( value ) => value === "true" || value === true , z . boolean ( ) ) . optional ( ) ,
8992} ) ;
9093
91- type ApiKeyPreset = NonNullable < Awaited < ReturnType < typeof rbac . apiKeyPresets > > > [ number ] ;
92-
9394const CreateApiKeySchema = z . object ( {
9495 action : z . literal ( "create" ) ,
9596 name : z . string ( ) . trim ( ) . min ( 1 ) . max ( 64 ) ,
@@ -115,59 +116,6 @@ const ApiKeyActionSchema = z.discriminatedUnion("action", [
115116 z . object ( { action : z . literal ( "revoke" ) , apiKeyId : z . string ( ) . min ( 1 ) } ) ,
116117] ) ;
117118
118- function validateCreateApiKeyPreset ( {
119- presets,
120- presetId,
121- taskScope,
122- taskIdentifiers,
123- hasTaskParameters,
124- } : {
125- presets : ApiKeyPreset [ ] | null ;
126- presetId ?: string ;
127- taskScope ?: "all" | "selected" ;
128- taskIdentifiers : string [ ] ;
129- hasTaskParameters : boolean ;
130- } ) : { presetId : string ; usesTaskSelection : boolean } {
131- // Always resolves to a concrete preset id. "No preset chosen" means full
132- // access, and saying so here keeps that decision visible at the call site
133- // instead of relying on a default inside prepareApiKeyPolicy.
134- const fullAccess = { presetId : FULL_ACCESS_PRESET_ID , usesTaskSelection : false } ;
135-
136- if ( presets === null ) {
137- if ( presetId !== undefined || hasTaskParameters ) {
138- throw new Error ( "API key access presets are not available" ) ;
139- }
140- return fullAccess ;
141- }
142-
143- if ( ! presetId ) {
144- if ( hasTaskParameters ) {
145- throw new Error ( "A preset is required when selecting tasks" ) ;
146- }
147- return fullAccess ;
148- }
149-
150- const preset = presets . find ( ( candidate ) => candidate . id === presetId ) ;
151- if ( ! preset ) {
152- throw new Error ( "Invalid API key access preset" ) ;
153- }
154- if ( ! preset . available ) {
155- throw new Error ( "This API key access preset is not available on your plan" ) ;
156- }
157-
158- if ( ! preset . usesTaskSelection && hasTaskParameters ) {
159- throw new Error ( "This API key access preset does not support task selection" ) ;
160- }
161- if ( preset . usesTaskSelection && taskScope === "selected" && taskIdentifiers . length === 0 ) {
162- throw new Error ( "Select at least one task" ) ;
163- }
164- if ( preset . usesTaskSelection && taskScope !== "selected" && taskIdentifiers . length > 0 ) {
165- throw new Error ( "Task identifiers require selected task scope" ) ;
166- }
167-
168- return { presetId : preset . id , usesTaskSelection : preset . usesTaskSelection ?? false } ;
169- }
170-
171119type ApiKeyActionData =
172120 | { ok : true ; action : "create" ; apiKey : string }
173121 | { ok : false ; error : string } ;
0 commit comments