mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Enhance client analytics event validation and type handling
- Introduced a new validation function, `isValidClientAnalyticsEventType`, to ensure client analytics events do not use server-only event types. - Updated the analytics validation logic to differentiate between client and server event types, improving clarity and security in event handling. - Adjusted documentation to reflect the new validation rules for client-sent analytics events. These changes strengthen the integrity of analytics event processing and enhance the overall structure of event type management.
This commit is contained in:
parent
f93b14c2c9
commit
df06834cd7
@ -1,4 +1,4 @@
|
||||
import { isValidPublicAnalyticsEventType, PUBLIC_STACK_ANALYTICS_EVENT_TYPE_LIST, UUID_RE } from "@/lib/analytics-validation";
|
||||
import { isValidClientAnalyticsEventType, isValidPublicAnalyticsEventType, PUBLIC_STACK_ANALYTICS_EVENT_TYPE_LIST, UUID_RE } from "@/lib/analytics-validation";
|
||||
import { insertAnalyticsEvents } from "@/lib/events";
|
||||
import { findRecentSessionReplay } from "@/lib/session-replays";
|
||||
import { getPrismaClientForTenancy } from "@/prisma-client";
|
||||
@ -69,6 +69,9 @@ export const POST = createSmartRouteHandler({
|
||||
if (auth.type === "client" && body.events.some((event) => event.user_id != null || event.team_id != null)) {
|
||||
throw new StatusError(StatusError.BadRequest, "Client analytics events cannot override user_id or team_id");
|
||||
}
|
||||
if (auth.type === "client" && body.events.some((event) => !isValidClientAnalyticsEventType(event.event_type))) {
|
||||
throw new StatusError(StatusError.BadRequest, "Client analytics events cannot use server-only event types");
|
||||
}
|
||||
|
||||
const projectId = auth.tenancy.project.id;
|
||||
const branchId = auth.tenancy.branchId;
|
||||
|
||||
@ -12,9 +12,16 @@ export const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[089ab][0-9a-f
|
||||
|
||||
export const CUSTOM_ANALYTICS_NAME_RE = /^[A-Za-z0-9._:-]+$/;
|
||||
|
||||
const PUBLIC_STACK_ANALYTICS_EVENT_TYPE_SET = new Set<string>(AUTO_CAPTURED_ANALYTICS_EVENT_TYPES);
|
||||
/** $-prefixed event types that browser clients may send. */
|
||||
const CLIENT_ANALYTICS_EVENT_TYPE_SET = new Set<string>(AUTO_CAPTURED_ANALYTICS_EVENT_TYPES);
|
||||
|
||||
export const PUBLIC_STACK_ANALYTICS_EVENT_TYPE_LIST = AUTO_CAPTURED_ANALYTICS_EVENT_TYPES
|
||||
/** $-prefixed event types that only server/admin auth may send. */
|
||||
const SERVER_ONLY_ANALYTICS_EVENT_TYPES = ["$request"] as const;
|
||||
|
||||
const ALL_PUBLIC_ANALYTICS_EVENT_TYPES = [...AUTO_CAPTURED_ANALYTICS_EVENT_TYPES, ...SERVER_ONLY_ANALYTICS_EVENT_TYPES];
|
||||
const ALL_PUBLIC_ANALYTICS_EVENT_TYPE_SET = new Set<string>(ALL_PUBLIC_ANALYTICS_EVENT_TYPES);
|
||||
|
||||
export const PUBLIC_STACK_ANALYTICS_EVENT_TYPE_LIST = ALL_PUBLIC_ANALYTICS_EVENT_TYPES
|
||||
.map((eventType) => `"${eventType}"`)
|
||||
.join(", ");
|
||||
|
||||
@ -38,10 +45,17 @@ export function isValidCustomAnalyticsName(
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-built validator for event_type.
|
||||
* Validates event_type for client auth (browser auto-captured + custom names).
|
||||
*/
|
||||
export function isValidClientAnalyticsEventType(eventType: string | undefined): boolean {
|
||||
return isValidCustomAnalyticsName(eventType, CLIENT_ANALYTICS_EVENT_TYPE_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates event_type for server/admin auth (all public types + custom names).
|
||||
*/
|
||||
export function isValidPublicAnalyticsEventType(eventType: string | undefined): boolean {
|
||||
return isValidCustomAnalyticsName(eventType, PUBLIC_STACK_ANALYTICS_EVENT_TYPE_SET);
|
||||
return isValidCustomAnalyticsName(eventType, ALL_PUBLIC_ANALYTICS_EVENT_TYPE_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -40,8 +40,10 @@ export type AnalyticsBatchSpan = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Auto-captured (Stack-managed) analytics event types that clients are allowed to send.
|
||||
* These are the only `$`-prefixed event types permitted from the client.
|
||||
* Auto-captured (Stack-managed) analytics event types that browser clients are
|
||||
* allowed to send. These are the only `$`-prefixed event types permitted from
|
||||
* client auth. Server-only types like `$request` are validated separately on
|
||||
* the backend.
|
||||
*
|
||||
* Span types have no public `$`-prefixed types — all `$`-prefixed spans
|
||||
* ($session-replay, $session-replay-segment) are created server-side only.
|
||||
@ -59,7 +61,6 @@ export const AUTO_CAPTURED_ANALYTICS_EVENT_TYPES = [
|
||||
"$copy",
|
||||
"$paste",
|
||||
"$error",
|
||||
"$request",
|
||||
] as const;
|
||||
export type AutoCapturedAnalyticsEventType = typeof AUTO_CAPTURED_ANALYTICS_EVENT_TYPES[number];
|
||||
|
||||
|
||||
@ -10,6 +10,9 @@ export type AnalyticsReplayLinkOptions = {
|
||||
|
||||
const autoCapturedAnalyticsEventTypeSet = new Set<string>(AUTO_CAPTURED_ANALYTICS_EVENT_TYPES);
|
||||
|
||||
/** Server-only $-prefixed event types (e.g. $request from middleware). Not client-sendable. */
|
||||
const serverOnlyAnalyticsEventTypes = new Set<string>(["$request"]);
|
||||
|
||||
export function assertValidAnalyticsEventName(
|
||||
eventType: string,
|
||||
options: { allowAutoCapturedReservedType?: boolean } = {},
|
||||
@ -17,7 +20,7 @@ export function assertValidAnalyticsEventName(
|
||||
if (!eventType) {
|
||||
throw new StackAssertionError("Analytics event type must not be empty");
|
||||
}
|
||||
if (options.allowAutoCapturedReservedType && autoCapturedAnalyticsEventTypeSet.has(eventType)) {
|
||||
if (options.allowAutoCapturedReservedType && (autoCapturedAnalyticsEventTypeSet.has(eventType) || serverOnlyAnalyticsEventTypes.has(eventType))) {
|
||||
return;
|
||||
}
|
||||
if (eventType.startsWith("$")) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user