feat(webapp): extend admin workers endpoint and unify admin api auth (#3390)

Extends the admin worker groups endpoint with a GET loader and more
fields on POST (type, hidden, workloadType, cloudProvider, location,
staticIPs, enableFastPath), and pulls the PAT + admin check that was
inlined or locally duplicated across every admin.api route into a shared
helper in personalAccessToken.server.ts. The generic
authenticateAdminRequest returns a discriminated result;
requireAdminApiRequest is the thin Remix loader/action wrapper that
throws. The neverthrow-style route (platform-notifications.ts) now
composes the generic helper instead of duplicating the check. Verified
locally against GET (listing) and POST (new fields, invalid enum,
minimal backwards-compat).
This commit is contained in:
nicktrn
2026-04-16 13:46:53 +01:00
committed by GitHub
parent 0c33de88f0
commit 93f2ca6bf4
29 changed files with 193 additions and 552 deletions
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---
Admin worker groups API: add GET loader and expose more fields on POST.
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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
+2 -17
View File
@@ -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();
@@ -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!;
@@ -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);
@@ -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 });
@@ -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";
@@ -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 });
@@ -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();
@@ -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);
@@ -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);
@@ -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);
@@ -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());
@@ -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<Result<AdminUser, AuthError>> {
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) {
@@ -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);
@@ -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);
@@ -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();
@@ -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<typeof CreateRunReplicationServiceParams>;
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();
@@ -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();
@@ -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();
@@ -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();
@@ -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(
+51 -25
View File
@@ -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);
export async function loader({ request }: LoaderFunctionArgs) {
await requireAdminApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
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<WorkerGroupService["createWorkerGroup"]>[0]
) {
const service = new WorkerGroupService();
return await service.createWorkerGroup({ name, description });
return await service.createWorkerGroup(options);
}
async function removeDefaultWorkerGroupFromProject(projectId: string) {
@@ -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<AdminAuthenticationResult> {
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<User> {
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");
@@ -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,
},
});