@better-auth/infra
v0.1.13
Published
Dashboard and analytics plugin for Better Auth
Downloads
136,829
Maintainers
Readme
Better Auth Infrastructure
Infra plugins for Better Auth:
dash()for dashboard/admin APIs, analytics tracking, and infra endpoints.dashClient()for dashboard client actions (including audit log queries).sentinel()for security checks and abuse protection.sentinelClient()for browser fingerprint headers + optional PoW auto-solving.
Installation
npm install @better-auth/infra
# or
pnpm add @better-auth/infra
# or
bun add @better-auth/infraServer Usage
import { betterAuth } from "better-auth";
import { dash, sentinel } from "@better-auth/infra";
export const auth = betterAuth({
// ...your Better Auth config
plugins: [
dash({
apiUrl: process.env.BETTER_AUTH_API_URL,
kvUrl: process.env.BETTER_AUTH_KV_URL,
apiKey: process.env.BETTER_AUTH_API_KEY,
}),
sentinel({
apiUrl: process.env.BETTER_AUTH_API_URL,
kvUrl: process.env.BETTER_AUTH_KV_URL,
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
credentialStuffing: {
enabled: true,
thresholds: { challenge: 3, block: 5 },
},
},
}),
],
});Client Usage
import { createAuthClient } from "better-auth/client";
import { dashClient, sentinelClient } from "@better-auth/infra/client";
export const authClient = createAuthClient({
plugins: [
dashClient(),
sentinelClient({
autoSolveChallenge: true,
}),
],
});
// Resolve user from session or pass explicit user/org context
const auditLogs = await authClient.dash.getAuditLogs({
session: await authClient.getSession().then((r) => r.data),
organizationId: "org_123",
limit: 20,
});Audit Log APIs
dashClient() API
dashClient() adds:
authClient.dash.getAuditLogs(input)
getAuditLogs(input) accepts:
limit?: number(default50, max100)offset?: number(default0)organizationId?: stringidentifier?: stringeventType?: stringuserId?: stringuser?: { id?: string | null }session?: { user?: { id?: string | null } }
The resolved user ID is determined in this order:
input.userIddashClient({ resolveUserId })input.user?.idinput.session?.user?.id
Response shape:
events: DashAuditLog[]total: numberlimit: numberoffset: number
Example:
const session = await authClient.getSession().then((r) => r.data);
const logs = await authClient.dash.getAuditLogs({
session,
organizationId: "org_123",
limit: 50,
offset: 0,
});To fetch all events, keep paginating with offset until events.length < limit.
Filtering
Use getAuditLogs filters directly in the query:
eventType: only return a specific event type (for exampleuser_signed_in)organizationId: scope logs to one organizationidentifier: narrow organization logs to a specific identifieruserId/user/session: resolve which user the logs should be scoped to
Examples:
// 1) Filter by event type
const signIns = await authClient.dash.getAuditLogs({
session,
eventType: "user_signed_in",
limit: 20,
});
// 2) Filter by org + identifier
const orgMemberEvents = await authClient.dash.getAuditLogs({
session,
organizationId: "org_123",
identifier: "[email protected]",
limit: 50,
});
// 3) Combine filters
const orgSignIns = await authClient.dash.getAuditLogs({
session,
organizationId: "org_123",
eventType: "user_signed_in",
limit: 50,
});Search Patterns
getAuditLogs does not currently expose a dedicated full-text search query param.
For text search, fetch pages and filter client-side.
const matchesText = (event: { eventType: string; eventKey: string; eventData: Record<string, unknown> }, query: string) => {
const q = query.toLowerCase().trim();
if (!q) return true;
const haystack = [
event.eventType,
event.eventKey,
JSON.stringify(event.eventData ?? {}),
]
.join(" ")
.toLowerCase();
return haystack.includes(q);
};
const page = await authClient.dash.getAuditLogs({ session, limit: 100, offset: 0 });
const filtered = page.data?.events.filter((event) => matchesText(event, "password")) ?? [];You can apply the same pattern for:
- date range filtering (by
createdAt) - location filtering (by
location.country,location.city, etc.) - multi-field compound filters
Pagination Helper
async function getAllAuditLogs(session: unknown) {
const limit = 100;
let offset = 0;
const all: Array<{
eventType: string;
createdAt: string;
eventData: Record<string, unknown>;
}> = [];
while (true) {
const result = await authClient.dash.getAuditLogs({ session, limit, offset });
const events = result.data?.events ?? [];
all.push(...events);
if (events.length < limit) break;
offset += limit;
}
return all;
}dash() Event Endpoints
The dash() plugin registers these event endpoints:
getUserEventsonGET /events/listgetAuditLogsonGET /events/audit-logsgetEventTypesonGET /events/types
getAuditLogs supports query params:
limit,offset,eventTypeorganizationId,identifieruserId(must match the authenticated session user)
Option Types
DashOptions
apiUrl?: stringkvUrl?: stringapiKey?: stringactivityTracking?: { enabled?: boolean; updateInterval?: number }
dash() no longer accepts sentinel security config.
SentinelOptions
apiUrl?: stringkvUrl?: stringapiKey?: stringsecurity?: SecurityOptions
All security configuration now belongs in sentinel().
DashClientOptions
resolveUserId?: ({ userId, user, session }) => string | undefined
See Audit Log APIs above for full method details.
Migration
If you previously passed security config to dash(), move it to sentinel():
// before
dash({
apiKey: process.env.BETTER_AUTH_API_KEY,
// no longer supported in dash
security: {
credentialStuffing: { enabled: true },
},
});
// after
dash({ apiKey: process.env.BETTER_AUTH_API_KEY });
sentinel({
apiKey: process.env.BETTER_AUTH_API_KEY,
security: {
credentialStuffing: { enabled: true },
},
});Security Notes
- Use
sentinel()to enforce security checks.dash()is focused on telemetry/admin behavior. - Provide
BETTER_AUTH_API_KEY; without it, sentinel cannot securely call infra APIs and will warn at startup. - If you run behind a proxy/CDN, validate your upstream header trust model (
x-forwarded-for, etc.) to avoid spoofed client IP attribution.
