Skip to content

Commit 05f401f

Browse files
ericallamTrigger.dev RepoOps
authored andcommitted
feat(webapp): record admin API calls, dashboard actions and admin PAT mints through an admin audit plugin
Adds an optional admin audit plugin contract to the webapp. When `ADMIN_AUDIT_ENABLED` is set and a plugin is installed, the webapp records every admin API request, every admin dashboard mutation and every personal access token minted by an admin: the actor (user and token id, never the token value), source IP, method, path, status, duration and a redacted summary of the request body. ```ts import type { AdminAuditPlugin } from "@trigger.dev/plugins"; const plugin: AdminAuditPlugin = { create: ({ appOrigin }) => ({ record: (event) => console.log(appOrigin, event.activity, event.actor.userId, event.http?.path), flush: async () => {}, }), }; export default plugin; ``` Without a plugin, or with the flag unset, nothing is recorded. Mono-RevId: fc34ea9e9a51db019358d4e9f84f3763bc16a0fe
1 parent 16c0f43 commit 05f401f

57 files changed

Lines changed: 1042 additions & 52 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/webapp/app/env.server.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2826,6 +2826,9 @@ const EnvironmentSchema = z
28262826
// login path.
28272827
SSO_DATABASE_WRITER_CONNECTION_LIMIT: z.coerce.number().int().default(2),
28282828
SSO_DATABASE_READER_CONNECTION_LIMIT: z.coerce.number().int().default(5),
2829+
2830+
/** Records admin API calls, admin dashboard mutations and admin PAT minting through the admin audit plugin. */
2831+
ADMIN_AUDIT_ENABLED: BoolEnv.default(false),
28292832
// Emit a console.log when the SSO fallback is selected because no
28302833
// plugin is installed. Default off so OSS deployments stay quiet.
28312834
SSO_LOG_FALLBACK: BoolEnv.default(false),

‎apps/webapp/app/routes/account.tokens/route.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
import { SimpleTooltip } from "~/components/primitives/Tooltip";
3838
import { redirectWithSuccessMessage } from "~/models/message.server";
3939
import { prisma } from "~/db.server";
40+
import { extractClientIp } from "~/utils/extractClientIp.server";
4041
import { rbac } from "~/services/rbac.server";
4142
import {
4243
type CreatedPersonalAccessToken,
@@ -212,6 +213,10 @@ export const action: ActionFunction = async ({ request }) => {
212213
name: submission.value.tokenName,
213214
userId,
214215
roleId: submittedRoleId,
216+
auditSource: {
217+
ip: extractClientIp(request.headers.get("x-forwarded-for")),
218+
userAgent: request.headers.get("user-agent"),
219+
},
215220
});
216221

217222
return json({ ...submission.reply(), payload: { token: tokenResult } });

‎apps/webapp/app/routes/admin.api.v1.deployments.template-builds.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withAdminAudit } from "~/services/adminAudit/adminAudit.server";
12
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
23
import { json } from "@remix-run/server-runtime";
34
import { tryCatch } from "@trigger.dev/core/v3";
@@ -12,7 +13,7 @@ const BodySchema = z.object({
1213
force: z.boolean().default(false),
1314
});
1415

15-
export async function action({ request }: ActionFunctionArgs) {
16+
async function handleAction({ request }: ActionFunctionArgs) {
1617
await requireAdminApiRequest(request);
1718

1819
if (request.method.toUpperCase() !== "POST") {
@@ -43,3 +44,5 @@ export async function action({ request }: ActionFunctionArgs) {
4344

4445
return json({ results });
4546
}
47+
48+
export const action = withAdminAudit(handleAction);

‎apps/webapp/app/routes/admin.api.v1.environments.$environmentId.burst-factor.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withAdminAudit } from "~/services/adminAudit/adminAudit.server";
12
import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
23
import { z } from "zod";
34
import { prisma } from "~/db.server";
@@ -13,7 +14,7 @@ const RequestBodySchema = z.object({
1314
burstFactor: z.number().positive(),
1415
});
1516

16-
export async function action({ request, params }: ActionFunctionArgs) {
17+
async function handleAction({ request, params }: ActionFunctionArgs) {
1718
await requireAdminApiRequest(request);
1819

1920
const { environmentId } = ParamsSchema.parse(params);
@@ -31,3 +32,5 @@ export async function action({ request, params }: ActionFunctionArgs) {
3132

3233
return json({ success: true });
3334
}
35+
36+
export const action = withAdminAudit(handleAction);

‎apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.repair-queues.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withAdminAudit } from "~/services/adminAudit/adminAudit.server";
12
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
23
import { json } from "@remix-run/server-runtime";
34
import pMap from "p-map";
@@ -17,7 +18,7 @@ const BodySchema = z.object({
1718
queues: z.array(z.string()).default([]),
1819
});
1920

20-
export async function action({ request, params }: ActionFunctionArgs) {
21+
async function handleAction({ request, params }: ActionFunctionArgs) {
2122
await requireAdminApiRequest(request);
2223

2324
const parsedParams = ParamsSchema.parse(params);
@@ -86,3 +87,5 @@ export async function action({ request, params }: ActionFunctionArgs) {
8687

8788
return json({ environment: repairEnvironmentResults, queues: repairResults });
8889
}
90+
91+
export const action = withAdminAudit(handleAction);

‎apps/webapp/app/routes/admin.api.v1.environments.$environmentId.engine.report.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withAdminAudit } from "~/services/adminAudit/adminAudit.server";
12
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
23
import { json } from "@remix-run/server-runtime";
34
import { z } from "zod";
@@ -21,7 +22,7 @@ const SearchParamsSchema = z.object({
2122
.optional(),
2223
});
2324

24-
export async function loader({ request, params }: LoaderFunctionArgs) {
25+
async function handleLoader({ request, params }: LoaderFunctionArgs) {
2526
await requireAdminApiRequest(request);
2627

2728
const parsedParams = ParamsSchema.parse(params);
@@ -80,3 +81,5 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
8081

8182
return json(report);
8283
}
84+
85+
export const loader = withAdminAudit(handleLoader);

‎apps/webapp/app/routes/admin.api.v1.environments.$environmentId.schedules.recover.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withAdminAudit } from "~/services/adminAudit/adminAudit.server";
12
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
23
import { json } from "@remix-run/server-runtime";
34
import { z } from "zod";
@@ -9,7 +10,7 @@ const ParamsSchema = z.object({
910
environmentId: z.string(),
1011
});
1112

12-
export async function action({ request, params }: ActionFunctionArgs) {
13+
async function handleAction({ request, params }: ActionFunctionArgs) {
1314
await requireAdminApiRequest(request);
1415

1516
const parsedParams = ParamsSchema.parse(params);
@@ -38,3 +39,5 @@ export async function action({ request, params }: ActionFunctionArgs) {
3839
results,
3940
});
4041
}
42+
43+
export const action = withAdminAudit(handleAction);

‎apps/webapp/app/routes/admin.api.v1.environments.$environmentId.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withAdminAudit } from "~/services/adminAudit/adminAudit.server";
12
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime";
23
import { json } from "@remix-run/server-runtime";
34
import { z } from "zod";
@@ -17,7 +18,7 @@ const RequestBodySchema = z.object({
1718
orgMaximumConcurrencyLimit: z.number(),
1819
});
1920

20-
export async function action({ request, params }: ActionFunctionArgs) {
21+
async function handleAction({ request, params }: ActionFunctionArgs) {
2122
await requireAdminApiRequest(request);
2223

2324
const parsedParams = ParamsSchema.parse(params);
@@ -61,7 +62,7 @@ const SearchParamsSchema = z.object({
6162
queue: z.string().optional(),
6263
});
6364

64-
export async function loader({ request, params }: LoaderFunctionArgs) {
65+
async function handleLoader({ request, params }: LoaderFunctionArgs) {
6566
await requireAdminApiRequest(request);
6667

6768
const parsedParams = ParamsSchema.parse(params);
@@ -109,3 +110,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
109110

110111
return json({ id: environment.id, concurrencyLimit, currentConcurrency });
111112
}
113+
114+
export const loader = withAdminAudit(handleLoader);
115+
export const action = withAdminAudit(handleAction);

‎apps/webapp/app/routes/admin.api.v1.feature-flags.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withAdminAudit } from "~/services/adminAudit/adminAudit.server";
12
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
23
import { json } from "@remix-run/server-runtime";
34
import { prisma } from "~/db.server";
@@ -11,7 +12,7 @@ import {
1112
} from "~/v3/featureFlags.server";
1213
import { validatePartialFeatureFlags } from "~/v3/featureFlags";
1314

14-
export async function action({ request }: ActionFunctionArgs) {
15+
async function handleAction({ request }: ActionFunctionArgs) {
1516
await requireAdminApiRequest(request);
1617

1718
try {
@@ -55,3 +56,5 @@ export async function action({ request }: ActionFunctionArgs) {
5556
);
5657
}
5758
}
59+
60+
export const action = withAdminAudit(handleAction);

‎apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { withAdminAudit } from "~/services/adminAudit/adminAudit.server";
12
import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
23
import { z } from "zod";
34
import { prisma } from "~/db.server";
45
import { requireAdminApiRequest } from "~/services/personalAccessToken.server";
56

6-
export async function loader({ request, params }: LoaderFunctionArgs) {
7+
async function handleLoader({ request, params }: LoaderFunctionArgs) {
78
await requireAdminApiRequest(request);
89

910
const model = await prisma.llmModel.findUnique({
@@ -55,7 +56,7 @@ const UpdateModelSchema = z.object({
5556
.optional(),
5657
});
5758

58-
export async function action({ request, params }: ActionFunctionArgs) {
59+
async function handleAction({ request, params }: ActionFunctionArgs) {
5960
await requireAdminApiRequest(request);
6061

6162
const modelId = params.modelId!;
@@ -162,3 +163,6 @@ export async function action({ request, params }: ActionFunctionArgs) {
162163

163164
return json({ model: updated });
164165
}
166+
167+
export const loader = withAdminAudit(handleLoader);
168+
export const action = withAdminAudit(handleAction);

0 commit comments

Comments
 (0)