chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import type { RuntimeEnvironmentType } from "@trigger.dev/database";
|
||||
import { isUserActorToken } from "@trigger.dev/rbac";
|
||||
import { rbac } from "~/services/rbac.server";
|
||||
|
||||
type EnvironmentScopedResource = "envvars" | "apiKeys";
|
||||
|
||||
const RESOURCE_LABELS: Record<EnvironmentScopedResource, string> = {
|
||||
envvars: "environment variables",
|
||||
apiKeys: "API keys",
|
||||
};
|
||||
|
||||
/**
|
||||
* Env-tier RBAC for environment-scoped API routes (env vars, and the endpoints
|
||||
* that hand out an environment's secret credentials).
|
||||
*
|
||||
* Machine credentials (an environment's secret/public API key) are already
|
||||
* scoped to a single environment, so they pass through unchanged. A personal
|
||||
* access token (or a delegated user-actor token) carries a user, so enforce
|
||||
* that user's role for the targeted environment tier — e.g. a Developer can't
|
||||
* read deployed env vars or API keys via the API, matching the dashboard
|
||||
* restriction. Blocking the credential read for deployed tiers is also what
|
||||
* stops a restricted role deploying via the CLI (deploy needs the
|
||||
* environment's secret key).
|
||||
*
|
||||
* Returns a `Response` to short-circuit with when access is denied, or
|
||||
* `undefined` when the request may proceed.
|
||||
*/
|
||||
export async function authorizePatEnvironmentAccess({
|
||||
request,
|
||||
authType,
|
||||
organizationId,
|
||||
projectId,
|
||||
envType,
|
||||
resource,
|
||||
action,
|
||||
}: {
|
||||
request: Request;
|
||||
authType: "personalAccessToken" | "organizationAccessToken" | "apiKey";
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
envType: RuntimeEnvironmentType;
|
||||
resource: EnvironmentScopedResource;
|
||||
action: "read" | "write";
|
||||
}): Promise<Response | undefined> {
|
||||
const bearer = request.headers
|
||||
.get("Authorization")
|
||||
?.replace(/^Bearer /, "")
|
||||
.trim();
|
||||
const isUat = !!bearer && isUserActorToken(bearer);
|
||||
|
||||
// Machine creds (apiKey) and org tokens carry no user role to enforce. A
|
||||
// user-actor token carries a user just like a PAT, so it's gated too.
|
||||
if (authType !== "personalAccessToken" && !isUat) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const userAuth = isUat
|
||||
? await rbac.authenticateUserActor(request, { organizationId, projectId })
|
||||
: await rbac.authenticatePat(request, { organizationId, projectId });
|
||||
if (!userAuth.ok) {
|
||||
return json({ error: userAuth.error }, { status: userAuth.status });
|
||||
}
|
||||
|
||||
if (!userAuth.ability.can(action, { type: resource, envType })) {
|
||||
return json(
|
||||
{
|
||||
error: `You don't have permission to access this environment's ${RESOURCE_LABELS[resource]}.`,
|
||||
},
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Env-tier env var access for the env var API routes. */
|
||||
export function authorizeEnvVarApiRequest(opts: {
|
||||
request: Request;
|
||||
authType: "personalAccessToken" | "organizationAccessToken" | "apiKey";
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
envType: RuntimeEnvironmentType;
|
||||
action: "read" | "write";
|
||||
}): Promise<Response | undefined> {
|
||||
return authorizePatEnvironmentAccess({ ...opts, resource: "envvars" });
|
||||
}
|
||||
Reference in New Issue
Block a user