fix(audit): readable names, single writer, denial rows, redacted args

The activity log recorded Start's hashed function id (a sha256 URL
segment) as the name; the middleware now reads compile-time
serverFnMeta.name with the path segment as fallback. toServerResult
resolves { success:false } instead of throwing, so the middleware logged
failures as successes while toServerResult wrote a duplicate row with its
own dead name parser — the middleware is now the single writer and reads
the envelope's success flag. Admin denials, which threw before the logging
middleware ran, get their own audit row. Arguments are redacted
(phone/otp/token keys) and truncated at 2KB. The admin activities search
binds its filter parameters (likePattern escaping) instead of
interpolating raw input. Adds vitest with node-env tests for the
middleware, redaction, and filter utils, and extends logging coverage to
mutating fns that lacked it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ASmkMee4F5aJ3jnKeKQv
This commit is contained in:
2026-08-23 18:27:53 -07:00
co-authored by Claude Fable 5
parent 33738e9d71
commit f2e2af729a
18 changed files with 691 additions and 67 deletions
@@ -1,6 +1,5 @@
import { logger } from "../../logger";
import { ErrorType, ServerError, ServerResult } from "../types";
import { getRequest } from "@tanstack/react-start/server";
import { isRedirect } from "@tanstack/react-router";
export const createServerError = (
@@ -17,57 +16,20 @@ export const createServerError = (
context,
});
// Audit rows are written by serverFnLoggingMiddleware, which reads the
// returned envelope's success flag — never write them here.
export const toServerResult = async <T>(
serverFn: () => Promise<T>
): Promise<ServerResult<T>> => {
const startTime = Date.now();
try {
const data = await serverFn();
return { success: true, data };
} catch (error) {
if (isRedirect(error) || error instanceof Response) throw error;
const duration = Date.now() - startTime;
logger.error('Server Fn Error', error);
const mappedError = mapKnownError(error);
let fnName = 'unknown';
try {
const request = getRequest();
const url = new URL(request.url);
const functionId = url.searchParams.get('_serverFnId') || url.pathname;
if (functionId.includes('--')) {
const match = functionId.match(/--([^_]+)_/);
fnName = match?.[1] || functionId.split('--')[1]?.split('_')[0] || 'unknown';
} else {
fnName = serverFn.name || 'unknown';
}
} catch {
fnName = serverFn.name || 'unknown';
}
import("../../pocketbase/client")
.then(async ({ pbAdmin }) => {
await pbAdmin.authPromise;
await pbAdmin.createActivity({
name: fnName,
duration,
success: false,
error: mappedError.message,
arguments: {
errorType: mappedError.code,
statusCode: mappedError.statusCode,
userMessage: mappedError.userMessage,
},
});
})
.catch(() => {});
return { success: false, error: mappedError };
return { success: false, error: mapKnownError(error) };
}
};