From c3619bf48dc3c5d902748c50fb8eaecd4340b14b Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:04:24 +0100 Subject: [PATCH 1/2] feat(webapp): extend admin workers endpoint and add PAT admin helper Adds a GET loader to the admin worker groups endpoint, exposes type, hidden, workloadType, cloudProvider, location, staticIPs, and enableFastPath on creation, and introduces authenticateAdminRequest and requireAdminApiRequest helpers in personalAccessToken.server.ts. The generic authenticateAdminRequest returns a discriminated result so callers can shape failures to fit their context; requireAdminApiRequest is the Remix loader/action wrapper that throws a Response. --- .server-changes/admin-workers-endpoint.md | 6 ++ .../webapp/app/routes/admin.api.v1.workers.ts | 76 +++++++++++++------ .../services/personalAccessToken.server.ts | 55 +++++++++++++- .../worker/workerGroupService.server.ts | 27 ++++++- 4 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 .server-changes/admin-workers-endpoint.md diff --git a/.server-changes/admin-workers-endpoint.md b/.server-changes/admin-workers-endpoint.md new file mode 100644 index 00000000000..34cd6ad70e3 --- /dev/null +++ b/.server-changes/admin-workers-endpoint.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: improvement +--- + +Admin worker groups API: add GET loader and expose more fields on POST. diff --git a/apps/webapp/app/routes/admin.api.v1.workers.ts b/apps/webapp/app/routes/admin.api.v1.workers.ts index b215d8ce223..caa36e5217b 100644 --- a/apps/webapp/app/routes/admin.api.v1.workers.ts +++ b/apps/webapp/app/routes/admin.api.v1.workers.ts @@ -1,9 +1,13 @@ -import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { + type ActionFunctionArgs, + type LoaderFunctionArgs, + json, +} from "@remix-run/server-runtime"; import { tryCatch } from "@trigger.dev/core"; -import { type Project } from "@trigger.dev/database"; +import { type Project, WorkerInstanceGroupType, WorkloadType } from "@trigger.dev/database"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { WorkerGroupService } from "~/v3/services/worker/workerGroupService.server"; const RequestBodySchema = z.object({ @@ -12,34 +16,44 @@ const RequestBodySchema = z.object({ projectId: z.string().optional(), makeDefaultForProject: z.boolean().default(false), removeDefaultFromProject: z.boolean().default(false), + type: z.nativeEnum(WorkerInstanceGroupType).optional(), + hidden: z.boolean().optional(), + workloadType: z.nativeEnum(WorkloadType).optional(), + cloudProvider: z.string().optional(), + location: z.string().optional(), + staticIPs: z.string().optional(), + enableFastPath: z.boolean().optional(), }); -export async function action({ request }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } +export async function loader({ request }: LoaderFunctionArgs) { + await requireAdminApiRequest(request); - const user = await prisma.user.findFirst({ - where: { - id: authenticationResult.userId, - }, + const workerGroups = await prisma.workerInstanceGroup.findMany({ + orderBy: { createdAt: "asc" }, }); - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } + return json({ workerGroups }); +} - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } +export async function action({ request }: ActionFunctionArgs) { + await requireAdminApiRequest(request); try { const rawBody = await request.json(); - const { name, description, projectId, makeDefaultForProject, removeDefaultFromProject } = - RequestBodySchema.parse(rawBody ?? {}); + const { + name, + description, + projectId, + makeDefaultForProject, + removeDefaultFromProject, + type, + hidden, + workloadType, + cloudProvider, + location, + staticIPs, + enableFastPath, + } = RequestBodySchema.parse(rawBody ?? {}); if (removeDefaultFromProject) { if (!projectId) { @@ -74,7 +88,17 @@ export async function action({ request }: ActionFunctionArgs) { }); if (!existingWorkerGroup) { - const { workerGroup, token } = await createWorkerGroup(name, description); + const { workerGroup, token } = await createWorkerGroup({ + name, + description, + type, + hidden, + workloadType, + cloudProvider, + location, + staticIPs, + enableFastPath, + }); if (!makeDefaultForProject) { return json({ @@ -150,9 +174,11 @@ export async function action({ request }: ActionFunctionArgs) { } } -async function createWorkerGroup(name: string | undefined, description: string | undefined) { +async function createWorkerGroup( + options: Parameters[0] +) { const service = new WorkerGroupService(); - return await service.createWorkerGroup({ name, description }); + return await service.createWorkerGroup(options); } async function removeDefaultWorkerGroupFromProject(projectId: string) { diff --git a/apps/webapp/app/services/personalAccessToken.server.ts b/apps/webapp/app/services/personalAccessToken.server.ts index ebe8bc31ff5..cceb576c9d8 100644 --- a/apps/webapp/app/services/personalAccessToken.server.ts +++ b/apps/webapp/app/services/personalAccessToken.server.ts @@ -1,4 +1,4 @@ -import { type PersonalAccessToken } from "@trigger.dev/database"; +import { type PersonalAccessToken, type User } from "@trigger.dev/database"; import { customAlphabet, nanoid } from "nanoid"; import { z } from "zod"; import { prisma } from "~/db.server"; @@ -118,6 +118,59 @@ export async function authenticateApiRequestWithPersonalAccessToken( return authenticatePersonalAccessToken(token); } +export type AdminAuthenticationResult = + | { ok: true; user: User } + | { ok: false; status: 401 | 403; message: string }; + +/** + * Authenticates a request via personal access token and checks the user is + * an admin. Returns a discriminated result so callers can shape the failure + * (throw a Response, wrap in neverthrow, return JSON, etc.) to fit their + * context. See `requireAdminApiRequest` for the Remix loader/action wrapper. + */ +export async function authenticateAdminRequest( + request: Request +): Promise { + const authResult = await authenticateApiRequestWithPersonalAccessToken(request); + + if (!authResult) { + return { ok: false, status: 401, message: "Invalid or Missing API key" }; + } + + const user = await prisma.user.findFirst({ + where: { id: authResult.userId }, + }); + + if (!user) { + return { ok: false, status: 401, message: "Invalid or Missing API key" }; + } + + if (!user.admin) { + return { ok: false, status: 403, message: "You must be an admin to perform this action" }; + } + + return { ok: true, user }; +} + +/** + * Remix loader/action wrapper around `authenticateAdminRequest` that throws + * a Response on failure so routes can `await` without handling the error + * branch. Uses `new Response` directly to avoid coupling this module to + * `@remix-run/server-runtime`. + */ +export async function requireAdminApiRequest(request: Request): Promise { + const result = await authenticateAdminRequest(request); + + if (!result.ok) { + throw new Response(JSON.stringify({ error: result.message }), { + status: result.status, + headers: { "Content-Type": "application/json" }, + }); + } + + return result.user; +} + function getPersonalAccessTokenFromRequest(request: Request) { const rawAuthorization = request.headers.get("Authorization"); diff --git a/apps/webapp/app/v3/services/worker/workerGroupService.server.ts b/apps/webapp/app/v3/services/worker/workerGroupService.server.ts index 6a2c19cf243..fc280e81652 100644 --- a/apps/webapp/app/v3/services/worker/workerGroupService.server.ts +++ b/apps/webapp/app/v3/services/worker/workerGroupService.server.ts @@ -1,4 +1,4 @@ -import { WorkerInstanceGroup, WorkerInstanceGroupType } from "@trigger.dev/database"; +import { WorkerInstanceGroup, WorkerInstanceGroupType, WorkloadType } from "@trigger.dev/database"; import { WithRunEngine } from "../baseService.server"; import { WorkerGroupTokenService } from "./workerGroupTokenService.server"; import { logger } from "~/services/logger.server"; @@ -14,11 +14,25 @@ export class WorkerGroupService extends WithRunEngine { organizationId, name, description, + type, + hidden, + workloadType, + cloudProvider, + location, + staticIPs, + enableFastPath, }: { projectId?: string; organizationId?: string; name?: string; description?: string; + type?: WorkerInstanceGroupType; + hidden?: boolean; + workloadType?: WorkloadType; + cloudProvider?: string; + location?: string; + staticIPs?: string; + enableFastPath?: boolean; }) { if (!name) { name = await this.generateWorkerName({ projectId }); @@ -30,15 +44,24 @@ export class WorkerGroupService extends WithRunEngine { }); const token = await tokenService.createToken(); + const resolvedType = + type ?? (projectId ? WorkerInstanceGroupType.UNMANAGED : WorkerInstanceGroupType.MANAGED); + const workerGroup = await this._prisma.workerInstanceGroup.create({ data: { projectId, organizationId, - type: projectId ? WorkerInstanceGroupType.UNMANAGED : WorkerInstanceGroupType.MANAGED, + type: resolvedType, masterQueue: this.generateMasterQueueName({ projectId, name }), tokenId: token.id, description, name, + hidden, + workloadType, + cloudProvider, + location, + staticIPs, + enableFastPath, }, }); From d8d98356576ca82eba697cc08be32b5fad983884 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:04:35 +0100 Subject: [PATCH 2/2] refactor(webapp): unify admin api route auth via shared helper Replaces duplicated inline PAT + admin checks across 24 admin api routes with the shared requireAdminApiRequest helper. Refactors platform-notifications.ts to compose the generic authenticateAdminRequest with neverthrow instead of duplicating the PAT + admin check. --- ...nts.$environmentId.engine.repair-queues.ts | 23 +--------- ...vironments.$environmentId.engine.report.ts | 23 +--------- ...nments.$environmentId.schedules.recover.ts | 23 +--------- ...dmin.api.v1.environments.$environmentId.ts | 44 ++----------------- .../app/routes/admin.api.v1.feature-flags.ts | 23 +--------- apps/webapp/app/routes/admin.api.v1.gc.ts | 19 +------- .../admin.api.v1.llm-models.$modelId.ts | 20 ++------- .../routes/admin.api.v1.llm-models.missing.ts | 19 +------- .../routes/admin.api.v1.llm-models.reload.ts | 13 +----- .../routes/admin.api.v1.llm-models.seed.ts | 12 +---- .../app/routes/admin.api.v1.llm-models.ts | 20 ++------- ...min.api.v1.migrate-legacy-master-queues.ts | 24 +--------- ...api.v1.orgs.$organizationId.concurrency.ts | 23 +--------- ...gs.$organizationId.environments.staging.ts | 23 +--------- ...i.v1.orgs.$organizationId.feature-flags.ts | 38 ++-------------- ...api.v1.orgs.$organizationId.runs.enable.ts | 23 +--------- .../admin.api.v1.platform-notifications.ts | 25 +++-------- ...i.v1.runs-replication.$batchId.backfill.ts | 24 +--------- ...api.v1.runs-replication.$batchId.cancel.ts | 24 +--------- .../admin.api.v1.runs-replication.backfill.ts | 23 +--------- .../admin.api.v1.runs-replication.create.ts | 24 +--------- .../admin.api.v1.runs-replication.start.ts | 24 +--------- .../admin.api.v1.runs-replication.stop.ts | 24 +--------- .../admin.api.v1.runs-replication.teardown.ts | 24 +--------- .../app/routes/admin.api.v1.snapshot.ts | 19 +------- 25 files changed, 57 insertions(+), 524 deletions(-) diff --git a/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.repair-queues.ts b/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.repair-queues.ts index 30d60197f99..a3fd61546e0 100644 --- a/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.repair-queues.ts +++ b/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.repair-queues.ts @@ -2,7 +2,7 @@ import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import pMap from "p-map"; import { z } from "zod"; import { $replica, prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { determineEngineVersion } from "~/v3/engineVersion.server"; import { engine } from "~/v3/runEngine.server"; @@ -16,26 +16,7 @@ const BodySchema = z.object({ }); export async function action({ request, params }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const parsedParams = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.report.ts b/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.report.ts index 3ea95768991..e37553ea230 100644 --- a/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.report.ts +++ b/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.report.ts @@ -1,7 +1,7 @@ import { json, LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { $replica, prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { determineEngineVersion } from "~/v3/engineVersion.server"; import { engine } from "~/v3/runEngine.server"; @@ -16,26 +16,7 @@ const SearchParamsSchema = z.object({ }); export async function loader({ request, params }: LoaderFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const parsedParams = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.schedules.recover.ts b/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.schedules.recover.ts index a9ada56085c..33c1581a940 100644 --- a/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.schedules.recover.ts +++ b/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.schedules.recover.ts @@ -1,7 +1,7 @@ import { ActionFunctionArgs, json, LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { scheduleEngine } from "~/v3/scheduleEngine.server"; const ParamsSchema = z.object({ @@ -9,26 +9,7 @@ const ParamsSchema = z.object({ }); export async function action({ request, params }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const parsedParams = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.ts b/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.ts index f448c5b5aca..34ea14f9da5 100644 --- a/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.ts +++ b/apps/webapp/app/routes/admin.api.v1.environments.$environmentId.ts @@ -1,7 +1,7 @@ import { ActionFunctionArgs, json, LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { engine } from "~/v3/runEngine.server"; import { updateEnvConcurrencyLimits } from "~/v3/runQueue.server"; @@ -15,26 +15,7 @@ const RequestBodySchema = z.object({ }); export async function action({ request, params }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const parsedParams = ParamsSchema.parse(params); @@ -71,26 +52,7 @@ const SearchParamsSchema = z.object({ }); export async function loader({ request, params }: LoaderFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to get this endpoint" }, { status: 403 }); - } + await requireAdminApiRequest(request); const parsedParams = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.feature-flags.ts b/apps/webapp/app/routes/admin.api.v1.feature-flags.ts index b10c983b14d..92debd43a62 100644 --- a/apps/webapp/app/routes/admin.api.v1.feature-flags.ts +++ b/apps/webapp/app/routes/admin.api.v1.feature-flags.ts @@ -1,30 +1,11 @@ import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { makeSetMultipleFlags } from "~/v3/featureFlags.server"; import { validatePartialFeatureFlags } from "~/v3/featureFlags"; export async function action({ request }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findFirst({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); try { // Parse the request body diff --git a/apps/webapp/app/routes/admin.api.v1.gc.ts b/apps/webapp/app/routes/admin.api.v1.gc.ts index ea63a264acc..fbb5f4c9000 100644 --- a/apps/webapp/app/routes/admin.api.v1.gc.ts +++ b/apps/webapp/app/routes/admin.api.v1.gc.ts @@ -2,8 +2,7 @@ import { type DataFunctionArgs } from "@remix-run/node"; import { PerformanceObserver } from "node:perf_hooks"; import { runInNewContext } from "node:vm"; import v8 from "v8"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; async function waitTillGcFinishes() { let resolver: (value: PerformanceEntry) => void; @@ -36,21 +35,7 @@ async function waitTillGcFinishes() { } export async function loader({ request }: DataFunctionArgs) { - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - throw new Response("You must be an admin to perform this action", { status: 403 }); - } - - const user = await prisma.user.findFirst({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user?.admin) { - throw new Response("You must be an admin to perform this action", { status: 403 }); - } + await requireAdminApiRequest(request); const entry = await waitTillGcFinishes(); diff --git a/apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts b/apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts index 2556dc8267f..e473c2c7273 100644 --- a/apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts +++ b/apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts @@ -1,24 +1,10 @@ import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; - -async function requireAdmin(request: Request) { - const authResult = await authenticateApiRequestWithPersonalAccessToken(request); - if (!authResult) { - throw json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ where: { id: authResult.userId } }); - if (!user?.admin) { - throw json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } - - return user; -} +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; export async function loader({ request, params }: LoaderFunctionArgs) { - await requireAdmin(request); + await requireAdminApiRequest(request); const model = await prisma.llmModel.findUnique({ where: { id: params.modelId }, @@ -69,7 +55,7 @@ const UpdateModelSchema = z.object({ }); export async function action({ request, params }: ActionFunctionArgs) { - await requireAdmin(request); + await requireAdminApiRequest(request); const modelId = params.modelId!; diff --git a/apps/webapp/app/routes/admin.api.v1.llm-models.missing.ts b/apps/webapp/app/routes/admin.api.v1.llm-models.missing.ts index 5ca7077e1cc..9e1e7dcb432 100644 --- a/apps/webapp/app/routes/admin.api.v1.llm-models.missing.ts +++ b/apps/webapp/app/routes/admin.api.v1.llm-models.missing.ts @@ -1,24 +1,9 @@ import { type LoaderFunctionArgs, json } from "@remix-run/server-runtime"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { getMissingLlmModels } from "~/services/admin/missingLlmModels.server"; -async function requireAdmin(request: Request) { - const authResult = await authenticateApiRequestWithPersonalAccessToken(request); - if (!authResult) { - throw json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ where: { id: authResult.userId } }); - if (!user?.admin) { - throw json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } - - return user; -} - export async function loader({ request }: LoaderFunctionArgs) { - await requireAdmin(request); + await requireAdminApiRequest(request); const url = new URL(request.url); const lookbackHours = parseInt(url.searchParams.get("lookbackHours") ?? "24", 10); diff --git a/apps/webapp/app/routes/admin.api.v1.llm-models.reload.ts b/apps/webapp/app/routes/admin.api.v1.llm-models.reload.ts index 747722b352a..26eee6d8434 100644 --- a/apps/webapp/app/routes/admin.api.v1.llm-models.reload.ts +++ b/apps/webapp/app/routes/admin.api.v1.llm-models.reload.ts @@ -1,18 +1,9 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { llmPricingRegistry } from "~/v3/llmPricingRegistry.server"; export async function action({ request }: ActionFunctionArgs) { - const authResult = await authenticateApiRequestWithPersonalAccessToken(request); - if (!authResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ where: { id: authResult.userId } }); - if (!user?.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); if (!llmPricingRegistry) { return json({ error: "LLM cost tracking is disabled" }, { status: 400 }); diff --git a/apps/webapp/app/routes/admin.api.v1.llm-models.seed.ts b/apps/webapp/app/routes/admin.api.v1.llm-models.seed.ts index 32e780d9fb9..ef85d458733 100644 --- a/apps/webapp/app/routes/admin.api.v1.llm-models.seed.ts +++ b/apps/webapp/app/routes/admin.api.v1.llm-models.seed.ts @@ -1,19 +1,11 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { seedLlmPricing, syncLlmCatalog } from "@internal/llm-model-catalog"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { llmPricingRegistry } from "~/v3/llmPricingRegistry.server"; export async function action({ request }: ActionFunctionArgs) { - const authResult = await authenticateApiRequestWithPersonalAccessToken(request); - if (!authResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ where: { id: authResult.userId } }); - if (!user?.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const url = new URL(request.url); const action = url.searchParams.get("action") ?? "seed"; diff --git a/apps/webapp/app/routes/admin.api.v1.llm-models.ts b/apps/webapp/app/routes/admin.api.v1.llm-models.ts index 4e3cc39f47a..7c8a3d8caa0 100644 --- a/apps/webapp/app/routes/admin.api.v1.llm-models.ts +++ b/apps/webapp/app/routes/admin.api.v1.llm-models.ts @@ -1,25 +1,11 @@ import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { generateFriendlyId } from "~/v3/friendlyIdentifiers"; -async function requireAdmin(request: Request) { - const authResult = await authenticateApiRequestWithPersonalAccessToken(request); - if (!authResult) { - throw json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ where: { id: authResult.userId } }); - if (!user?.admin) { - throw json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } - - return user; -} - export async function loader({ request }: LoaderFunctionArgs) { - await requireAdmin(request); + await requireAdminApiRequest(request); const url = new URL(request.url); const page = parseInt(url.searchParams.get("page") ?? "1"); @@ -75,7 +61,7 @@ const CreateModelSchema = z.object({ }); export async function action({ request }: ActionFunctionArgs) { - await requireAdmin(request); + await requireAdminApiRequest(request); if (request.method !== "POST") { return json({ error: "Method not allowed" }, { status: 405 }); diff --git a/apps/webapp/app/routes/admin.api.v1.migrate-legacy-master-queues.ts b/apps/webapp/app/routes/admin.api.v1.migrate-legacy-master-queues.ts index b960287c92a..ad3575fa390 100644 --- a/apps/webapp/app/routes/admin.api.v1.migrate-legacy-master-queues.ts +++ b/apps/webapp/app/routes/admin.api.v1.migrate-legacy-master-queues.ts @@ -1,29 +1,9 @@ import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { engine } from "~/v3/runEngine.server"; export async function action({ request }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); try { await engine.migrateLegacyMasterQueues(); diff --git a/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.concurrency.ts b/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.concurrency.ts index d6eee08f37f..f5346a69075 100644 --- a/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.concurrency.ts +++ b/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.concurrency.ts @@ -1,7 +1,7 @@ import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { marqs } from "~/v3/marqs/index.server"; import { updateEnvConcurrencyLimits } from "~/v3/runQueue.server"; @@ -17,26 +17,7 @@ const RequestBodySchema = z.object({ }); export async function action({ request, params }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const { organizationId } = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.environments.staging.ts b/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.environments.staging.ts index 6a8628f7526..f3c215bfd44 100644 --- a/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.environments.staging.ts +++ b/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.environments.staging.ts @@ -8,7 +8,7 @@ import { import { z } from "zod"; import { prisma } from "~/db.server"; import { createEnvironment } from "~/models/organization.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { updateEnvConcurrencyLimits } from "~/v3/runQueue.server"; const ParamsSchema = z.object({ @@ -19,26 +19,7 @@ const ParamsSchema = z.object({ * It will create a staging environment for all the projects where there isn't one already */ export async function action({ request, params }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const { organizationId } = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.feature-flags.ts b/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.feature-flags.ts index bb0671355bd..513616470a0 100644 --- a/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.feature-flags.ts +++ b/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.feature-flags.ts @@ -1,43 +1,15 @@ import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { validatePartialFeatureFlags } from "~/v3/featureFlags"; const ParamsSchema = z.object({ organizationId: z.string(), }); -async function authenticateAdmin(request: Request) { - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return { error: json({ error: "Invalid or Missing API key" }, { status: 401 }) }; - } - - const user = await prisma.user.findFirst({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return { error: json({ error: "Invalid or Missing API key" }, { status: 401 }) }; - } - - if (!user.admin) { - return { error: json({ error: "You must be an admin to perform this action" }, { status: 403 }) }; - } - - return { user }; -} - export async function loader({ request, params }: LoaderFunctionArgs) { - const authResult = await authenticateAdmin(request); - - if ("error" in authResult) { - return authResult.error; - } + await requireAdminApiRequest(request); const { organizationId } = ParamsSchema.parse(params); @@ -70,11 +42,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { } export async function action({ request, params }: ActionFunctionArgs) { - const authResult = await authenticateAdmin(request); - - if ("error" in authResult) { - return authResult.error; - } + await requireAdminApiRequest(request); const { organizationId } = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.runs.enable.ts b/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.runs.enable.ts index 6b1cf2d9939..d60754f0461 100644 --- a/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.runs.enable.ts +++ b/apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.runs.enable.ts @@ -8,7 +8,7 @@ import { import { z } from "zod"; import { prisma } from "~/db.server"; import { createEnvironment } from "~/models/organization.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { updateEnvConcurrencyLimits } from "~/v3/runQueue.server"; import { PauseEnvironmentService } from "~/v3/services/pauseEnvironment.server"; @@ -24,26 +24,7 @@ const BodySchema = z.object({ * It will enabled/disable runs */ export async function action({ request, params }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const { organizationId } = ParamsSchema.parse(params); const body = BodySchema.safeParse(await request.json()); diff --git a/apps/webapp/app/routes/admin.api.v1.platform-notifications.ts b/apps/webapp/app/routes/admin.api.v1.platform-notifications.ts index 093104bcb05..3798d9fa734 100644 --- a/apps/webapp/app/routes/admin.api.v1.platform-notifications.ts +++ b/apps/webapp/app/routes/admin.api.v1.platform-notifications.ts @@ -1,7 +1,6 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { err, ok, type Result } from "neverthrow"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { authenticateAdminRequest } from "~/services/personalAccessToken.server"; import { createPlatformNotification, type CreatePlatformNotificationInput, @@ -11,24 +10,10 @@ type AdminUser = { id: string; admin: boolean }; type AuthError = { status: number; message: string }; async function authenticateAdmin(request: Request): Promise> { - const authResult = await authenticateApiRequestWithPersonalAccessToken(request); - if (!authResult) { - return err({ status: 401, message: "Invalid or Missing API key" }); - } - - const user = await prisma.user.findUnique({ - where: { id: authResult.userId }, - select: { id: true, admin: true }, - }); - - if (!user?.admin) { - return err({ - status: user ? 403 : 401, - message: user ? "You must be an admin to perform this action" : "Invalid or Missing API key", - }); - } - - return ok(user); + const result = await authenticateAdminRequest(request); + return result.ok + ? ok({ id: result.user.id, admin: result.user.admin }) + : err({ status: result.status, message: result.message }); } export async function action({ request }: ActionFunctionArgs) { diff --git a/apps/webapp/app/routes/admin.api.v1.runs-replication.$batchId.backfill.ts b/apps/webapp/app/routes/admin.api.v1.runs-replication.$batchId.backfill.ts index 105fcaa408f..18427795315 100644 --- a/apps/webapp/app/routes/admin.api.v1.runs-replication.$batchId.backfill.ts +++ b/apps/webapp/app/routes/admin.api.v1.runs-replication.$batchId.backfill.ts @@ -1,7 +1,6 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { adminWorker } from "~/v3/services/adminWorker.server"; const Body = z.object({ @@ -19,26 +18,7 @@ const DEFAULT_BATCH_SIZE = 500; const DEFAULT_DELAY_INTERVAL_MS = 1000; export async function action({ request, params }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const { batchId } = Params.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.runs-replication.$batchId.cancel.ts b/apps/webapp/app/routes/admin.api.v1.runs-replication.$batchId.cancel.ts index 8dfcf9fb85b..b80bfba6b54 100644 --- a/apps/webapp/app/routes/admin.api.v1.runs-replication.$batchId.cancel.ts +++ b/apps/webapp/app/routes/admin.api.v1.runs-replication.$batchId.cancel.ts @@ -1,7 +1,6 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { adminWorker } from "~/v3/services/adminWorker.server"; const Params = z.object({ @@ -9,26 +8,7 @@ const Params = z.object({ }); export async function action({ request, params }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); const { batchId } = Params.parse(params); diff --git a/apps/webapp/app/routes/admin.api.v1.runs-replication.backfill.ts b/apps/webapp/app/routes/admin.api.v1.runs-replication.backfill.ts index 0897c30c21d..c4d17ba875d 100644 --- a/apps/webapp/app/routes/admin.api.v1.runs-replication.backfill.ts +++ b/apps/webapp/app/routes/admin.api.v1.runs-replication.backfill.ts @@ -3,7 +3,7 @@ import { type TaskRun } from "@trigger.dev/database"; import { z } from "zod"; import { prisma } from "~/db.server"; import { logger } from "~/services/logger.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { runsReplicationInstance } from "~/services/runsReplicationInstance.server"; import { FINAL_RUN_STATUSES } from "~/v3/taskStatus"; @@ -14,26 +14,7 @@ const Body = z.object({ const MAX_BATCH_SIZE = 50; export async function action({ request }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); try { const body = await request.json(); diff --git a/apps/webapp/app/routes/admin.api.v1.runs-replication.create.ts b/apps/webapp/app/routes/admin.api.v1.runs-replication.create.ts index 483c2d219a1..9cac56b65ce 100644 --- a/apps/webapp/app/routes/admin.api.v1.runs-replication.create.ts +++ b/apps/webapp/app/routes/admin.api.v1.runs-replication.create.ts @@ -1,6 +1,5 @@ import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { z } from "zod"; import { ClickHouse } from "@internal/clickhouse"; import { env } from "~/env.server"; @@ -29,26 +28,7 @@ const CreateRunReplicationServiceParams = z.object({ type CreateRunReplicationServiceParams = z.infer; export async function action({ request }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); try { const globalService = getRunsReplicationGlobal(); diff --git a/apps/webapp/app/routes/admin.api.v1.runs-replication.start.ts b/apps/webapp/app/routes/admin.api.v1.runs-replication.start.ts index a700c4d4f11..e0c603f5320 100644 --- a/apps/webapp/app/routes/admin.api.v1.runs-replication.start.ts +++ b/apps/webapp/app/routes/admin.api.v1.runs-replication.start.ts @@ -1,30 +1,10 @@ import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { getRunsReplicationGlobal } from "~/services/runsReplicationGlobal.server"; import { runsReplicationInstance } from "~/services/runsReplicationInstance.server"; export async function action({ request }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); try { const globalService = getRunsReplicationGlobal(); diff --git a/apps/webapp/app/routes/admin.api.v1.runs-replication.stop.ts b/apps/webapp/app/routes/admin.api.v1.runs-replication.stop.ts index 1dc53833d86..410a9aeaaba 100644 --- a/apps/webapp/app/routes/admin.api.v1.runs-replication.stop.ts +++ b/apps/webapp/app/routes/admin.api.v1.runs-replication.stop.ts @@ -1,30 +1,10 @@ import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { getRunsReplicationGlobal } from "~/services/runsReplicationGlobal.server"; import { runsReplicationInstance } from "~/services/runsReplicationInstance.server"; export async function action({ request }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); try { const globalService = getRunsReplicationGlobal(); diff --git a/apps/webapp/app/routes/admin.api.v1.runs-replication.teardown.ts b/apps/webapp/app/routes/admin.api.v1.runs-replication.teardown.ts index f4a1223dfcf..8bcf760e72f 100644 --- a/apps/webapp/app/routes/admin.api.v1.runs-replication.teardown.ts +++ b/apps/webapp/app/routes/admin.api.v1.runs-replication.teardown.ts @@ -1,6 +1,5 @@ import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; import { getRunsReplicationGlobal, unregisterRunsReplicationGlobal, @@ -8,26 +7,7 @@ import { import { runsReplicationInstance } from "~/services/runsReplicationInstance.server"; export async function action({ request }: ActionFunctionArgs) { - // Next authenticate the request - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - if (!user.admin) { - return json({ error: "You must be an admin to perform this action" }, { status: 403 }); - } + await requireAdminApiRequest(request); try { const globalService = getRunsReplicationGlobal(); diff --git a/apps/webapp/app/routes/admin.api.v1.snapshot.ts b/apps/webapp/app/routes/admin.api.v1.snapshot.ts index 3b345978f5e..daba88f2a42 100644 --- a/apps/webapp/app/routes/admin.api.v1.snapshot.ts +++ b/apps/webapp/app/routes/admin.api.v1.snapshot.ts @@ -4,8 +4,7 @@ import os from "os"; import path from "path"; import { PassThrough } from "stream"; import v8 from "v8"; -import { prisma } from "~/db.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { requireAdminApiRequest } from "~/services/personalAccessToken.server"; // Format date as yyyy-MM-dd HH_mm_ss_SSS function formatDate(date: Date) { @@ -25,21 +24,7 @@ function formatDate(date: Date) { } export async function loader({ request }: DataFunctionArgs) { - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - throw new Response("You must be an admin to perform this action", { status: 403 }); - } - - const user = await prisma.user.findFirst({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user?.admin) { - throw new Response("You must be an admin to perform this action", { status: 403 }); - } + await requireAdminApiRequest(request); const tempDir = os.tmpdir(); const filepath = path.join(