+
+ Are you sure you want to regenerate the keys
+ for this environment?
+
+
+ <>
+ This will temporarily break any live jobs in the
+ {title} Environment until the new API keys
+ are set in the relevant environment variables.
+ >
+
+
+
+ To confirm, please enter the text:{" "}
+ {randomWord}
+
+ setConfirmationText(e.target.value)}
+ />
+
+
+
+ );
+};
diff --git a/apps/webapp/app/models/api-key.server.ts b/apps/webapp/app/models/api-key.server.ts
new file mode 100644
index 0000000000..a54fdae9a1
--- /dev/null
+++ b/apps/webapp/app/models/api-key.server.ts
@@ -0,0 +1,101 @@
+import { RuntimeEnvironmentType, type RuntimeEnvironment } from "@trigger.dev/database";
+import { prisma } from "~/db.server";
+import { customAlphabet } from "nanoid";
+
+const apiKeyId = customAlphabet(
+ "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ 12
+);
+
+type RegenerateAPIKeyInput = {
+ userId: string;
+ environmentId: string;
+};
+
+export async function regenerateApiKey({ userId, environmentId }: RegenerateAPIKeyInput) {
+ const environment = await prisma.runtimeEnvironment.findUnique({
+ where: {
+ id: environmentId,
+ },
+ include: {
+ organization: true,
+ project: true,
+ },
+ });
+
+ if (!environment) {
+ throw new Error("Environment does not exist");
+ }
+
+ // check if the user is part of the org
+ const organization = await prisma.organization.findFirst({
+ where: {
+ id: environment.organization.id,
+ members: { some: { userId } },
+ },
+ });
+
+ if (!organization) {
+ throw new Error("User does not have permission to regenerate API key");
+ }
+
+ // check if it is the user's dev environment
+ if (environment.type === RuntimeEnvironmentType.DEVELOPMENT) {
+ if (!environment.orgMemberId) {
+ throw new Error("User does not have permission to regenerate API key");
+ }
+
+ const orgMember = await prisma.orgMember.findFirst({
+ where: {
+ organizationId: organization.id,
+ userId: userId,
+ id: environment.orgMemberId,
+ },
+ });
+
+ if (!orgMember) {
+ throw new Error("User does not have permission to regenerate API key");
+ }
+ }
+
+ // generate and store new keys
+ const newApiKey = createApiKeyForEnv(environment.type);
+ const newPkApiKey = createPkApiKeyForEnv(environment.type);
+
+ const updatedEnviroment = await prisma.runtimeEnvironment.update({
+ data: {
+ apiKey: newApiKey,
+ pkApiKey: newPkApiKey,
+ },
+ where: {
+ id: environmentId,
+ },
+ });
+
+ return updatedEnviroment;
+}
+
+export function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
+ return `tr_${envSlug(envType)}_${apiKeyId(20)}`;
+}
+
+export function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
+ return `pk_${envSlug(envType)}_${apiKeyId(20)}`;
+}
+
+export function envSlug(environmentType: RuntimeEnvironment["type"]) {
+ switch (environmentType) {
+ case "DEVELOPMENT": {
+ return "dev";
+ }
+ case "PRODUCTION": {
+ return "prod";
+ }
+ case "STAGING": {
+ return "stg";
+ }
+ case "PREVIEW": {
+ return "prev";
+ }
+ }
+}
diff --git a/apps/webapp/app/models/organization.server.ts b/apps/webapp/app/models/organization.server.ts
index 2daf0791a5..df0475fbe4 100644
--- a/apps/webapp/app/models/organization.server.ts
+++ b/apps/webapp/app/models/organization.server.ts
@@ -9,6 +9,7 @@ import { customAlphabet } from "nanoid";
import slug from "slug";
import { prisma, PrismaClientOrTransaction } from "~/db.server";
import { createProject } from "./project.server";
+import { createApiKeyForEnv, createPkApiKeyForEnv, envSlug } from "./api-key.server";
export type { Organization };
@@ -157,28 +158,3 @@ export async function createEnvironment(
},
});
}
-
-function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
- return `tr_${envSlug(envType)}_${apiKeyId(20)}`;
-}
-
-function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
- return `pk_${envSlug(envType)}_${apiKeyId(20)}`;
-}
-
-function envSlug(environmentType: RuntimeEnvironment["type"]) {
- switch (environmentType) {
- case "DEVELOPMENT": {
- return "dev";
- }
- case "PRODUCTION": {
- return "prod";
- }
- case "STAGING": {
- return "stg";
- }
- case "PREVIEW": {
- return "prev";
- }
- }
-}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx
index 521fb993a9..0f22d6869c 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx
@@ -44,6 +44,7 @@ import { requestUrl } from "~/utils/requestUrl.server";
import { RuntimeEnvironmentType } from "../../../../../packages/database/src";
import { ConfigureEndpointSheet } from "./ConfigureEndpointSheet";
import { FirstEndpointSheet } from "./FirstEndpointSheet";
+import { RegenerateApiKeyModal } from "~/components/environments/RegenerateApiKeyModal";
export const loader = async ({ request, params }: LoaderArgs) => {
const userId = await requireUserId(request);
@@ -148,11 +149,17 @@ export default function Page() {