Files
triggerdotdev--trigger.dev/apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
2026-07-13 13:32:57 +08:00

108 lines
3.5 KiB
TypeScript

import { z } from "zod";
import { environmentFullTitle } from "~/components/environments/EnvironmentLabel";
import { $replica } from "~/db.server";
import { regenerateApiKey } from "~/models/api-key.server";
import { jsonWithErrorMessage, jsonWithSuccessMessage } from "~/models/message.server";
import { VercelIntegrationRepository } from "~/models/vercelIntegration.server";
import { logger } from "~/services/logger.server";
import { dashboardAction } from "~/services/routeBuilders/dashboardBuilder";
const ParamsSchema = z.object({
environmentId: z.string(),
});
export const action = dashboardAction(
{
params: ParamsSchema,
context: async (params) => {
const environment = await $replica.runtimeEnvironment.findFirst({
where: { id: params.environmentId },
select: { organizationId: true },
});
return environment ? { organizationId: environment.organizationId } : {};
},
// Env-tier write:apiKeys is enforced in the handler — the target
// environment's tier isn't known until we resolve it from the id.
},
async ({ request, params, user, ability }) => {
if (request.method.toUpperCase() !== "POST") {
throw new Response("Method Not Allowed", { status: 405 });
}
const { environmentId } = params;
const environment = await $replica.runtimeEnvironment.findFirst({
where: { id: environmentId },
select: { type: true },
});
if (!environment) {
return jsonWithErrorMessage({ ok: false }, request, "Environment not found");
}
// Gate the regenerate even on a direct POST: a role that can't write
// this tier's API keys can't rotate them. The disabled UI control is
// not the boundary; this check is.
if (!ability.can("write", { type: "apiKeys", envType: environment.type })) {
return jsonWithErrorMessage(
{ ok: false },
request,
"You don't have permission to regenerate API keys for this environment."
);
}
const formData = await request.formData();
const syncToVercel = formData.get("syncToVercel") === "on";
try {
const updatedEnvironment = await regenerateApiKey({ userId: user.id, environmentId });
// Sync the regenerated API key to Vercel only when requested and not for DEVELOPMENT
if (syncToVercel && updatedEnvironment.type !== "DEVELOPMENT") {
await syncApiKeyToVercel(
updatedEnvironment.projectId,
updatedEnvironment.type as "PRODUCTION" | "STAGING" | "PREVIEW",
updatedEnvironment.apiKey
);
}
return jsonWithSuccessMessage(
{ ok: true },
request,
`API keys regenerated for ${environmentFullTitle(updatedEnvironment)} environment`
);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return jsonWithErrorMessage(
{ ok: false },
request,
`API keys could not be regenerated: ${message}`
);
}
}
);
/**
* Sync the API key to Vercel.
* Errors are logged but won't fail the API key regeneration.
*/
async function syncApiKeyToVercel(
projectId: string,
environmentType: "PRODUCTION" | "STAGING" | "PREVIEW" | "DEVELOPMENT",
apiKey: string
): Promise<void> {
const result = await VercelIntegrationRepository.syncSingleApiKeyToVercel({
projectId,
environmentType,
apiKey,
});
if (result.isErr()) {
logger.warn("syncSingleApiKeyToVercel returned failure", {
projectId,
environmentType,
error: result.error.message,
});
}
}