@checodo/better-auth-audit-log 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../package.json","../src/utils/method.ts","../src/hooks/database.ts","../src/hooks/paths.ts","../../plugin-utils/src/admin.ts","../../plugin-utils/src/options.ts","../src/options.ts","../src/record-event.ts","../src/chain.ts","../src/store.ts","../src/utils/redact.ts","../src/utils/severity.ts","../src/recorder.ts","../../plugin-utils/src/pagination.ts","../src/routes/events.ts","../src/schema.ts","../src/memory.ts","../src/index.ts"],"sourcesContent":["","/**\n * How a user signed in or up, from the endpoint that did it:\n * `/sign-in/email` → `email`, `/callback/github` → `github`,\n * `/magic-link/verify` → `magic-link`, `/two-factor/verify-totp` → `two-factor`.\n */\nexport function signInMethod(\n path: string | undefined,\n params?: Record<string, unknown> | undefined,\n body?: Record<string, unknown> | undefined,\n): string | null {\n // OAuth callbacks (`/callback/:id`, generic OAuth's `/oauth2/callback/:providerId`).\n const provider = params?.id ?? params?.providerId;\n if (typeof provider === \"string\" && provider) return provider;\n if (!path) return null;\n // `/sign-in/social` with an ID token signs in without a redirect.\n if (path === \"/sign-in/social\" && typeof body?.provider === \"string\") return body.provider;\n const [first, second] = path.split(\"/\").filter(Boolean);\n if ((first === \"sign-in\" || first === \"sign-up\") && second) return second;\n return first ?? null;\n}\n","import type { AuthContext, BetterAuthOptions, GenericEndpointContext } from \"@better-auth/core\";\nimport { getCurrentAdapter } from \"@better-auth/core/context\";\nimport type { EventDetails, Recorder } from \"../recorder\";\nimport { signInMethod } from \"../utils/method\";\n\n/** Endpoints that verify an email address (not OAuth sign-ins, which rewrite the flag). */\nconst VERIFY_EMAIL_PATHS = new Set([\"/verify-email\", \"/email-otp/verify-email\"]);\n\n/**\n * How long a new user's id is remembered, so the account created with the\n * user (same request, milliseconds later) isn't recorded as a link.\n */\nconst NEW_USER_TTL = 10_000;\n\ntype Context = GenericEndpointContext | null | undefined;\ninterface UpdateFlags {\n emailChanged: string | null;\n emailVerified: boolean;\n /** The new `twoFactorEnabled` (two-factor plugin), if this update changes it. */\n twoFactor: boolean | null;\n /** The user whose `twoFactorEnabled` it changes. */\n twoFactorUserId: string | null;\n}\n\n/**\n * The user an update is for, as far as the request tells: the target of\n * the admin plugin's `updateUser`, otherwise the signed-in user (the\n * two-factor plugin changes the user's own setting).\n */\nfunction updatedUserId(context: GenericEndpointContext): string | null {\n if (context.path === \"/admin/update-user\") {\n const userId = (context.body as { userId?: unknown } | undefined)?.userId;\n return typeof userId === \"string\" ? userId : null;\n }\n return context.context.session?.user.id ?? null;\n}\n\n/**\n * Events about users and accounts, recorded however they happen (any\n * endpoint, any plugin, server-side calls): sign-ups, deleted users,\n * linked accounts, changed and verified emails, two-factor turned on or off.\n */\nexport function createDatabaseHooks(\n authContext: AuthContext,\n recorder: Recorder,\n): BetterAuthOptions[\"databaseHooks\"] {\n const contextFor = (context: Context) => context?.context ?? authContext;\n const headersOf = (context: Context) => context?.request?.headers ?? context?.headers ?? null;\n /** The signed-in user acting, e.g. an admin; `fallback` when nobody is signed in. */\n const actor = (context: Context, fallback: string) =>\n context?.context.session?.user.id ?? fallback;\n\n const newUsers = new Map<string, number>();\n const rememberNewUser = (userId: string) => {\n const now = Date.now();\n for (const [id, at] of newUsers) if (now - at > NEW_USER_TTL) newUsers.delete(id);\n newUsers.set(userId, now);\n };\n const wasJustCreated = (userId: string) => {\n const at = newUsers.get(userId);\n newUsers.delete(userId);\n return at !== undefined && Date.now() - at <= NEW_USER_TTL;\n };\n\n // `user.update.after` gets the updated user but not the changed fields, so\n // `before` notes them per request, in order (one request can update twice).\n const pendingUpdates = new WeakMap<object, UpdateFlags[]>();\n\n const record = (context: Context, details: EventDetails) =>\n recorder.record(contextFor(context), details, headersOf(context));\n\n return {\n user: {\n create: {\n after: async (user, context) => {\n rememberNewUser(user.id);\n await record(context, {\n type: \"sign-up\",\n userId: user.id,\n actorUserId: actor(context, user.id),\n metadata: {\n method: signInMethod(\n context?.path,\n context?.params as Record<string, unknown> | undefined,\n context?.body as Record<string, unknown> | undefined,\n ),\n },\n });\n },\n },\n update: {\n before: async (data, context) => {\n if (!context) return;\n const flags: UpdateFlags = {\n emailChanged: null,\n emailVerified: false,\n twoFactor: null,\n twoFactorUserId: null,\n };\n try {\n if (typeof data.email === \"string\") {\n // better-auth sometimes writes an unchanged email back; emails\n // are unique, so a user that already has it means unchanged.\n const email = data.email.toLowerCase();\n const adapter = await getCurrentAdapter(contextFor(context).adapter);\n const owner = await adapter.findOne({\n model: \"user\",\n where: [{ field: \"email\", value: email }],\n });\n if (!owner) flags.emailChanged = email;\n }\n flags.emailVerified =\n !flags.emailChanged &&\n data.emailVerified === true &&\n VERIFY_EMAIL_PATHS.has(context.path ?? \"\");\n // Turned on (after the first verified code) or off: compare with\n // the stored value of the user it is for, not the session's,\n // which belongs to an admin or can be a stale cookie cache.\n const twoFactorUserId =\n typeof data.twoFactorEnabled === \"boolean\" ? updatedUserId(context) : null;\n if (twoFactorUserId) {\n const adapter = await getCurrentAdapter(contextFor(context).adapter);\n const stored = await adapter.findOne<{ twoFactorEnabled?: unknown }>({\n model: \"user\",\n where: [{ field: \"id\", value: twoFactorUserId }],\n });\n if (stored && (stored.twoFactorEnabled === true) !== data.twoFactorEnabled) {\n flags.twoFactor = data.twoFactorEnabled as boolean;\n flags.twoFactorUserId = twoFactorUserId;\n }\n }\n } catch (error) {\n contextFor(context).logger.error(\"[audit-log] failed to inspect a user update\", error);\n }\n const queue = pendingUpdates.get(context) ?? [];\n queue.push(flags);\n pendingUpdates.set(context, queue);\n },\n after: async (user, context) => {\n if (!context || !user) return;\n const flags = pendingUpdates.get(context)?.shift();\n if (!flags) return;\n // A stale entry (another plugin cancelled an update) doesn't match.\n if (flags.emailChanged && user.email?.toLowerCase() === flags.emailChanged) {\n await record(context, {\n type: \"email-changed\",\n userId: user.id,\n actorUserId: actor(context, user.id),\n metadata: { email: flags.emailChanged },\n });\n } else if (flags.emailVerified) {\n await record(context, {\n type: \"email-verified\",\n userId: user.id,\n actorUserId: user.id,\n });\n }\n const twoFactorEnabled = (user as { twoFactorEnabled?: unknown }).twoFactorEnabled;\n if (\n flags.twoFactor !== null &&\n user.id === flags.twoFactorUserId &&\n twoFactorEnabled === flags.twoFactor\n ) {\n await record(context, {\n type: flags.twoFactor ? \"two-factor-enabled\" : \"two-factor-disabled\",\n userId: user.id,\n actorUserId: actor(context, user.id),\n });\n }\n },\n },\n delete: {\n after: async (user, context) => {\n await record(context, {\n type: \"user-deleted\",\n userId: user.id,\n actorUserId: actor(context, user.id),\n });\n },\n },\n },\n account: {\n create: {\n after: async (account, context) => {\n // The first account of a new user is part of the sign-up.\n if (wasJustCreated(account.userId)) return;\n await record(context, {\n type: \"account-linked\",\n userId: account.userId,\n actorUserId: actor(context, account.userId),\n metadata: { providerId: account.providerId },\n });\n },\n },\n },\n };\n}\n","import type {\n BetterAuthPlugin,\n GenericEndpointContext,\n HookEndpointContext,\n} from \"@better-auth/core\";\nimport { isAPIError } from \"@better-auth/core/utils/is-api-error\";\nimport { createAuthMiddleware, getSessionFromCtx } from \"better-auth/api\";\nimport type { EventDetails, Recorder } from \"../recorder\";\nimport type { ResolvedAuditLogOptions } from \"../types\";\nimport { signInMethod } from \"../utils/method\";\n\ntype Body = Record<string, unknown>;\ntype Session = NonNullable<GenericEndpointContext[\"context\"][\"session\"]>;\n/** The context hooks get: an endpoint context with `context.returned`. */\ntype HookContext = Parameters<Parameters<typeof createAuthMiddleware>[0]>[0];\n\n/** Where the before-hook leaves what it looked up for the after-hook. */\nconst SUBJECT_KEY = \"auditLogSubjectUserId\";\n\n/** Endpoints whose failures are failed sign-ins. Only these: they check a credential. */\nconst SIGN_IN_PATHS = new Set([\n \"/sign-in/email\",\n \"/sign-in/username\",\n \"/sign-in/phone-number\",\n \"/sign-in/email-otp\",\n \"/two-factor/verify-totp\",\n \"/two-factor/verify-otp\",\n \"/two-factor/verify-backup-code\",\n]);\n\n/**\n * Endpoints that set a new session cookie for a user who is already signed\n * in (rotation, impersonation): not sign-ins. Most of them also load the\n * session, which rules them out anyway; this list covers the rest.\n */\nconst NOT_SIGN_IN_PATHS = new Set([\n \"/change-password\",\n \"/update-user\",\n \"/update-session\",\n \"/change-email\",\n \"/two-factor/enable\",\n \"/two-factor/disable\",\n \"/admin/impersonate-user\",\n \"/admin/stop-impersonating\",\n \"/multi-session/set-active\",\n]);\n\ninterface PathEvent {\n type: string;\n /**\n * The user the event is about, when it isn't the signed-in user: the\n * admin's target, or a user looked up by the before-hook.\n */\n subject?: ((body: Body, returned: unknown, lookedUp: string | undefined) => unknown) | undefined;\n /** The organization the event is about; defaults to the session's active organization. */\n organization?: ((body: Body, returned: unknown) => unknown) | undefined;\n metadata?: ((body: Body, returned: unknown) => Record<string, unknown>) | undefined;\n}\n\nconst target = (body: Body) => body.userId;\nconst lookedUp = (_body: Body, _returned: unknown, userId: string | undefined) => userId;\n\n/** A field of the endpoint's result, if it succeeded (`returned` is an error otherwise). */\nfunction field(returned: unknown, ...path: string[]): unknown {\n let value: unknown = isAPIError(returned) ? undefined : returned;\n for (const key of path) {\n value = value && typeof value === \"object\" ? (value as Body)[key] : undefined;\n }\n return value;\n}\n\n/** Endpoints whose subject is the user of a password reset, looked up before the reset. */\nconst PASSWORD_RESET_PATHS = new Set([\n \"/reset-password\",\n \"/email-otp/reset-password\",\n \"/phone-number/reset-password\",\n]);\n\nconst passwordReset: PathEvent = { type: \"password-reset\", subject: lookedUp };\n\n/**\n * Organization plugin endpoints. The organization comes from the result,\n * else the body. `addMember` has no path (server-only), so hooks never see\n * it: apps that call it record their own event.\n */\nconst ORGANIZATION_EVENTS: Record<string, PathEvent> = {\n \"/organization/create\": {\n type: \"organization.created\",\n organization: (_body, returned) => field(returned, \"id\"),\n metadata: (body) => ({ name: body.name ?? null, slug: body.slug ?? null }),\n },\n \"/organization/update\": {\n type: \"organization.updated\",\n organization: (body, returned) => field(returned, \"id\") ?? body.organizationId,\n metadata: (body) => ({\n fields: body.data && typeof body.data === \"object\" ? Object.keys(body.data) : [],\n }),\n },\n \"/organization/delete\": {\n type: \"organization.deleted\",\n organization: (body) => body.organizationId,\n },\n \"/organization/invite-member\": {\n type: \"organization.invitation-created\",\n organization: (body, returned) => field(returned, \"organizationId\") ?? body.organizationId,\n metadata: (body, returned) => ({\n invitationId: field(returned, \"id\") ?? null,\n email: typeof body.email === \"string\" ? body.email.toLowerCase() : null,\n role: body.role ?? null,\n }),\n },\n \"/organization/accept-invitation\": {\n type: \"organization.invitation-accepted\",\n organization: (_body, returned) => field(returned, \"invitation\", \"organizationId\"),\n metadata: (body, returned) => ({\n invitationId: body.invitationId ?? null,\n memberId: field(returned, \"member\", \"id\") ?? null,\n role: field(returned, \"member\", \"role\") ?? null,\n }),\n },\n \"/organization/reject-invitation\": {\n type: \"organization.invitation-rejected\",\n organization: (_body, returned) => field(returned, \"invitation\", \"organizationId\"),\n metadata: (body) => ({ invitationId: body.invitationId ?? null }),\n },\n \"/organization/cancel-invitation\": {\n type: \"organization.invitation-canceled\",\n organization: (_body, returned) => field(returned, \"organizationId\"),\n metadata: (body, returned) => ({\n invitationId: body.invitationId ?? null,\n email: field(returned, \"email\") ?? null,\n }),\n },\n \"/organization/remove-member\": {\n type: \"organization.member-removed\",\n subject: (_body, returned) => field(returned, \"member\", \"userId\"),\n organization: (body, returned) =>\n field(returned, \"member\", \"organizationId\") ?? body.organizationId,\n metadata: (_body, returned) => ({ memberId: field(returned, \"member\", \"id\") ?? null }),\n },\n \"/organization/update-member-role\": {\n type: \"organization.member-role-changed\",\n subject: (_body, returned) => field(returned, \"userId\"),\n organization: (body, returned) => field(returned, \"organizationId\") ?? body.organizationId,\n metadata: (body) => ({ memberId: body.memberId ?? null, role: body.role ?? null }),\n },\n \"/organization/leave\": {\n type: \"organization.member-left\",\n organization: (body) => body.organizationId,\n },\n};\n\n/** Endpoints recorded by path. Admin actions are recorded on success and on failure. */\nconst PATH_EVENTS: Record<string, PathEvent> = {\n \"/sign-out\": { type: \"sign-out\" },\n \"/revoke-session\": { type: \"session-revoked\", metadata: () => ({ scope: \"session\" }) },\n \"/revoke-sessions\": { type: \"session-revoked\", metadata: () => ({ scope: \"all\" }) },\n \"/revoke-other-sessions\": { type: \"session-revoked\", metadata: () => ({ scope: \"others\" }) },\n \"/change-password\": {\n type: \"password-changed\",\n metadata: (body) => ({ revokeOtherSessions: body.revokeOtherSessions === true }),\n },\n \"/reset-password\": passwordReset,\n \"/email-otp/reset-password\": passwordReset,\n \"/phone-number/reset-password\": passwordReset,\n \"/unlink-account\": {\n type: \"account-unlinked\",\n metadata: (body) => ({ providerId: body.providerId }),\n },\n \"/admin/create-user\": {\n type: \"admin.create-user\",\n subject: (_body, returned) => (returned as { user?: { id?: unknown } } | null)?.user?.id,\n metadata: (body) => ({ role: body.role ?? null }),\n },\n \"/admin/update-user\": {\n type: \"admin.update-user\",\n subject: target,\n metadata: (body) => ({\n fields: body.data && typeof body.data === \"object\" ? Object.keys(body.data) : [],\n }),\n },\n \"/admin/set-role\": {\n type: \"admin.set-role\",\n subject: target,\n metadata: (body) => ({ role: body.role }),\n },\n \"/admin/set-user-password\": { type: \"admin.set-user-password\", subject: target },\n \"/admin/ban-user\": {\n type: \"admin.ban-user\",\n subject: target,\n metadata: (body) => ({ reason: body.banReason ?? null, expiresIn: body.banExpiresIn ?? null }),\n },\n \"/admin/unban-user\": { type: \"admin.unban-user\", subject: target },\n \"/admin/impersonate-user\": { type: \"admin.impersonate-user\", subject: target },\n // The session is the impersonated one; the actor is the admin behind it.\n \"/admin/stop-impersonating\": { type: \"admin.stop-impersonating\" },\n \"/admin/revoke-user-session\": { type: \"admin.revoke-user-session\", subject: lookedUp },\n \"/admin/revoke-user-sessions\": { type: \"admin.revoke-user-sessions\", subject: target },\n \"/admin/remove-user\": { type: \"admin.remove-user\", subject: target },\n ...ORGANIZATION_EVENTS,\n};\n\nconst asString = (value: unknown): string | null =>\n typeof value === \"string\" && value !== \"\" ? value : null;\n\n/**\n * Looks up, before the endpoint runs, what is gone afterwards: the\n * session (sign-out, revoke), the user of a password reset (by token, or by\n * the email or phone number of an OTP reset), or of a session an admin\n * revokes.\n */\nasync function lookUpSubject(ctx: GenericEndpointContext): Promise<string | undefined> {\n const body = (ctx.body ?? {}) as Body;\n if (ctx.path === \"/reset-password\") {\n const token = asString(body.token) ?? asString((ctx.query as Body | undefined)?.token);\n if (!token) return undefined;\n const verification = await ctx.context.internalAdapter.findVerificationValue(\n `reset-password:${token}`,\n );\n return verification?.value;\n }\n if (ctx.path === \"/email-otp/reset-password\") {\n const email = asString(body.email)?.trim().toLowerCase();\n if (!email) return undefined;\n return (await ctx.context.internalAdapter.findUserByEmail(email))?.user.id;\n }\n if (ctx.path === \"/phone-number/reset-password\") {\n const phone = asString(body.phoneNumber)?.trim();\n if (!phone) return undefined;\n const user = await ctx.context.adapter\n .findOne<{ id: string }>({ model: \"user\", where: [{ field: \"phoneNumber\", value: phone }] })\n .catch(() => null);\n return user?.id;\n }\n if (ctx.path === \"/admin/revoke-user-session\") {\n const token = asString(body.sessionToken);\n if (!token) return undefined;\n return (await ctx.context.internalAdapter.findSession(token))?.user.id;\n }\n return undefined;\n}\n\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst PHONE = /^\\+?[0-9 ()-]{5,20}$/;\n\n/**\n * Who a failed sign-in was for. People type their password into the\n * email or username field, so the input is only kept when it is clearly\n * an identifier: a well-formed email or phone number, or the username of\n * an existing user.\n */\nasync function failedSignInSubject(\n ctx: GenericEndpointContext,\n body: Body,\n): Promise<{ userId: string | null; identifier: string | null }> {\n const email = asString(body.email)?.trim().toLowerCase();\n if (email) {\n if (!EMAIL.test(email) || email.length > 255) return { userId: null, identifier: null };\n const user = await ctx.context.internalAdapter.findUserByEmail(email);\n return { userId: user?.user.id ?? null, identifier: email };\n }\n const username = asString(body.username)?.trim().toLowerCase();\n if (username) {\n // The username plugin stores usernames lowercased.\n const user = await ctx.context.adapter\n .findOne<{ id: string }>({ model: \"user\", where: [{ field: \"username\", value: username }] })\n .catch(() => null);\n return user ? { userId: user.id, identifier: username } : { userId: null, identifier: null };\n }\n const phone = asString(body.phoneNumber)?.trim();\n if (phone && PHONE.test(phone)) {\n const user = await ctx.context.adapter\n .findOne<{ id: string }>({ model: \"user\", where: [{ field: \"phoneNumber\", value: phone }] })\n .catch(() => null);\n return { userId: user?.id ?? null, identifier: phone };\n }\n return { userId: null, identifier: null };\n}\n\nfunction failureOf(returned: unknown): { failed: boolean; errorCode: string | null } {\n if (!isAPIError(returned) || returned.statusCode < 400) return { failed: false, errorCode: null };\n const code = (returned.body as { code?: unknown } | undefined)?.code;\n return { failed: true, errorCode: typeof code === \"string\" ? code : returned.status.toString() };\n}\n\nfunction sessionDetails(session: Session | null | undefined): Partial<EventDetails> {\n if (!session) return {};\n const { id, activeOrganizationId } = session.session as {\n id: string;\n activeOrganizationId?: unknown;\n };\n return { sessionId: id, organizationId: asString(activeOrganizationId) };\n}\n\nexport function createPathHooks(\n options: ResolvedAuditLogOptions,\n recorder: Recorder,\n): NonNullable<BetterAuthPlugin[\"hooks\"]> {\n const eventFor = (path: string | undefined): PathEvent | undefined => {\n if (!path) return undefined;\n if (Object.hasOwn(options.paths, path)) return { type: options.paths[path] as string };\n return Object.hasOwn(PATH_EVENTS, path) ? PATH_EVENTS[path] : undefined;\n };\n\n return {\n before: [\n {\n matcher: (context: HookEndpointContext) => {\n const event = eventFor(context.path);\n return !!event && recorder.isRecorded(event.type);\n },\n handler: createAuthMiddleware(async (ctx) => {\n try {\n // Loads the session into `ctx.context.session`, where the\n // after-hook finds it even once the endpoint deleted it.\n await getSessionFromCtx(ctx, { disableRefresh: true });\n const subject = await lookUpSubject(ctx);\n if (subject) (ctx.context as unknown as Record<string, unknown>)[SUBJECT_KEY] = subject;\n } catch (error) {\n ctx.context.logger.error(\"[audit-log] failed to prepare an event\", error);\n }\n }),\n },\n ],\n after: [\n {\n matcher: (context: HookEndpointContext) => !!context.path,\n handler: createAuthMiddleware(async (ctx) => {\n try {\n await afterEndpoint(ctx);\n } catch (error) {\n // An after-hook that throws would fail a request that succeeded.\n ctx.context.logger.error(\"[audit-log] failed to record an event\", error);\n }\n }),\n },\n ],\n };\n\n async function afterEndpoint(ctx: HookContext) {\n const { path } = ctx;\n const body = (ctx.body ?? {}) as Body;\n const headers = ctx.request?.headers ?? ctx.headers ?? null;\n const { failed, errorCode } = failureOf(ctx.context.returned);\n const session = ctx.context.session;\n\n // A sign-in, by any method: a new session for a request that had none.\n const created = ctx.context.newSession;\n if (\n !failed &&\n created &&\n !session &&\n !NOT_SIGN_IN_PATHS.has(path) &&\n !(created.session as { impersonatedBy?: unknown }).impersonatedBy\n ) {\n await recorder.record(\n ctx.context,\n {\n type: \"sign-in\",\n userId: created.user.id,\n actorUserId: created.user.id,\n ...sessionDetails(created),\n metadata: {\n method: signInMethod(path, ctx.params as Body | undefined, body),\n },\n },\n headers,\n );\n }\n\n if (failed && SIGN_IN_PATHS.has(path)) {\n const { userId, identifier } = await failedSignInSubject(ctx, body);\n await recorder.record(\n ctx.context,\n {\n type: \"sign-in\",\n status: \"failure\",\n errorCode,\n userId,\n metadata: {\n method: signInMethod(path),\n ...(identifier ? { identifier } : {}),\n },\n },\n headers,\n );\n return;\n }\n\n const event = eventFor(path);\n if (!event) return;\n const subject = (ctx.context as unknown as Record<string, unknown>)[SUBJECT_KEY] as\n | string\n | undefined;\n let actorUserId = session?.user.id ?? null;\n let userId = event.subject\n ? asString(event.subject(body, ctx.context.returned, subject))\n : actorUserId;\n if (path === \"/admin/stop-impersonating\" && session) {\n actorUserId =\n asString((session.session as { impersonatedBy?: unknown }).impersonatedBy) ??\n session.user.id;\n userId = session.user.id;\n }\n // Self-service events of a password reset have no session: the user acts.\n if (PASSWORD_RESET_PATHS.has(path)) actorUserId = userId;\n if (!userId && !actorUserId) return;\n\n const details = sessionDetails(session);\n const organizationId = event.organization\n ? asString(event.organization(body, ctx.context.returned))\n : null;\n await recorder.record(\n ctx.context,\n {\n type: event.type,\n status: failed ? \"failure\" : \"success\",\n errorCode,\n userId,\n actorUserId,\n ...details,\n organizationId: organizationId ?? details.organizationId ?? null,\n metadata: event.metadata ? event.metadata(body, ctx.context.returned) : null,\n },\n headers,\n );\n }\n}\n","/** Who counts as an admin of a plugin: the `adminRoles` and `adminUserIds` options. */\nexport interface AdminOptions {\n /**\n * Users with one of these roles (the `role` field of better-auth's\n * `admin` plugin) count as admins. Defaults to `[\"admin\"]`.\n */\n adminRoles?: readonly string[] | undefined;\n /** Users with these ids count as admins, whatever their role. */\n adminUserIds?: readonly string[] | undefined;\n}\n\nexport interface ResolvedAdminOptions {\n adminRoles: readonly string[];\n adminUserIds: readonly string[];\n}\n\n/** Applies the defaults of `AdminOptions`. */\nexport function resolveAdminOptions(options: AdminOptions): ResolvedAdminOptions {\n return {\n adminRoles: options.adminRoles ?? [\"admin\"],\n adminUserIds: options.adminUserIds ?? [],\n };\n}\n\n/**\n * Whether `user` is listed in `adminUserIds`, or has one of `adminRoles`\n * in the admin plugin's `role` field, which holds several roles\n * comma-separated (`\"user, admin\"`).\n */\nexport function isAdmin(user: { id: string; role?: unknown }, options: ResolvedAdminOptions) {\n if (options.adminUserIds.includes(user.id)) return true;\n if (typeof user.role !== \"string\") return false;\n return user.role.split(\",\").some((role) => options.adminRoles.includes(role.trim()));\n}\n","/**\n * Returns a function that throws an option error prefixed with the plugin\n * id, `[referral] \"codeLength\" must be ...`, for validating options at\n * plugin creation.\n */\nexport function createFail(pluginId: string): (message: string) => never {\n return (message) => {\n throw new Error(`[${pluginId}] ${message}`);\n };\n}\n","import { resolveAdminOptions } from \"@repo/plugin-utils/admin\";\nimport { createFail } from \"@repo/plugin-utils/options\";\nimport type { AuditEventSeverity, AuditLogOptions, ResolvedAuditLogOptions } from \"./types\";\n\n/** Longest event type. */\nexport const MAX_TYPE_LENGTH = 64;\nconst SEVERITIES: readonly AuditEventSeverity[] = [\"low\", \"medium\", \"high\", \"critical\"];\n\nconst fail = createFail(\"audit-log\");\n\nexport function isValidEventType(type: string): boolean {\n return type.length <= MAX_TYPE_LENGTH && /^[a-z0-9][a-z0-9._-]*$/i.test(type);\n}\n\n/**\n * Compiles `events` patterns into a matcher: exact types, or `group.*`\n * for every type starting with `group.`.\n */\nexport function compileEventFilter(patterns: readonly string[]): (type: string) => boolean {\n const exact = new Set<string>();\n const prefixes: string[] = [];\n for (const pattern of patterns) {\n if (pattern.endsWith(\".*\") && isValidEventType(pattern.slice(0, -2))) {\n prefixes.push(pattern.slice(0, -1));\n } else if (isValidEventType(pattern)) {\n exact.add(pattern);\n } else {\n fail(`Invalid event pattern ${JSON.stringify(pattern)} in \"events\".`);\n }\n }\n return (type) => exact.has(type) || prefixes.some((prefix) => type.startsWith(prefix));\n}\n\n/** Resolves defaults and validates the options once, at plugin creation. */\nexport function resolveOptions(options: AuditLogOptions): ResolvedAuditLogOptions {\n const paths = { ...options.paths };\n for (const [path, type] of Object.entries(paths)) {\n if (!path.startsWith(\"/\")) fail(`Path ${JSON.stringify(path)} in \"paths\" must start with \"/\".`);\n if (!isValidEventType(type)) {\n fail(\n `Invalid event type ${JSON.stringify(type)} in \"paths\": use letters, digits, \".\", \"-\", and \"_\", up to ${MAX_TYPE_LENGTH} characters.`,\n );\n }\n }\n\n const severity = { ...options.severity };\n for (const [type, value] of Object.entries(severity)) {\n if (!SEVERITIES.includes(value)) {\n fail(\n `Invalid severity ${JSON.stringify(value)} for \"${type}\": use ${SEVERITIES.join(\", \")}.`,\n );\n }\n }\n\n let retention: ResolvedAuditLogOptions[\"retention\"];\n if (options.retention) {\n const { days, sweepInterval = 3600 } = options.retention;\n if (!(typeof days === \"number\" && days > 0 && Number.isFinite(days))) {\n fail(`\"retention.days\" must be a positive number, got ${days}.`);\n }\n if (!(typeof sweepInterval === \"number\" && sweepInterval > 0)) {\n fail(`\"retention.sweepInterval\" must be a positive number of seconds, got ${sweepInterval}.`);\n }\n retention = { days, sweepInterval };\n }\n\n let hashChain: ResolvedAuditLogOptions[\"hashChain\"] = null;\n if (options.hashChain) {\n if (options.storage) {\n fail('\"hashChain\" needs the default database storage; remove \"storage\" or \"hashChain\".');\n }\n const secret = typeof options.hashChain === \"object\" ? options.hashChain.secret : undefined;\n if (secret !== undefined && secret.length < 32) {\n fail('\"hashChain.secret\" must be at least 32 characters.');\n }\n hashChain = { secret };\n }\n\n return {\n events: options.events ? compileEventFilter(options.events) : null,\n paths,\n severity,\n captureIp: options.captureIp ?? true,\n captureUserAgent: options.captureUserAgent ?? true,\n beforeLog: options.beforeLog,\n onEvent: options.onEvent,\n storage: options.storage,\n retention,\n hashChain,\n ...resolveAdminOptions(options),\n };\n}\n","import type { GenericEndpointContext } from \"@better-auth/core\";\nimport {\n getCurrentAdapter,\n runWithAdapter,\n tryGetCurrentAuthEndpointContext,\n} from \"@better-auth/core/context\";\nimport type { EventDetails, Recorder } from \"./recorder\";\n\n/** What other plugins pass to `recordEvent`. */\nexport type PluginEventDetails = Omit<EventDetails, \"ipAddress\" | \"userAgent\">;\n\n/**\n * Records an event for another plugin, through\n * `ctx.context.getPlugin(\"audit-log\")?.recordEvent(...)`, the way official\n * plugins reach each other (`anonymous` calls `twoFactor` like this).\n *\n * Fills in what the request knows: the signed-in user as actor (unless\n * `actorUserId` is given), the session, its active organization, the IP\n * address, and the user agent. Never throws.\n *\n * Inside a database transaction (a database hook while a user is created),\n * the event is written outside it once the transaction ends: an event\n * about something that was refused, like a blocked sign-up, must survive\n * the rollback.\n */\nexport function createRecordEvent(recorder: Recorder) {\n return async (\n details: PluginEventDetails,\n endpoint: GenericEndpointContext | null | undefined = tryGetCurrentAuthEndpointContext() as\n | GenericEndpointContext\n | undefined,\n ): Promise<void> => {\n if (!endpoint || !recorder.isRecorded(details.type)) return;\n const ctx = endpoint.context;\n const session = ctx.session;\n const event: EventDetails = {\n actorUserId: session?.user.id ?? null,\n sessionId: session?.session.id ?? null,\n organizationId:\n ((session?.session as { activeOrganizationId?: string | null } | undefined)\n ?.activeOrganizationId as string | null | undefined) ?? null,\n ...details,\n };\n const headers = endpoint.request?.headers ?? endpoint.headers ?? null;\n\n try {\n if ((await getCurrentAdapter(ctx.adapter)) === ctx.adapter) {\n await recorder.record(ctx, event, headers);\n return;\n }\n // In a transaction: not awaited, so the write waits for the\n // transaction instead of the transaction waiting for the write.\n ctx.runInBackground(runWithAdapter(ctx.adapter, () => recorder.record(ctx, event, headers)));\n } catch (error) {\n ctx.logger.error(`[audit-log] failed to record a \"${details.type}\" event`, error);\n }\n };\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport { getCurrentAdapter, queueAfterTransactionHook } from \"@better-auth/core/context\";\nimport type { AuditEvent } from \"./types\";\n\n/** The `previousHash` of the first link. */\nexport const GENESIS_HASH = \"0\".repeat(64);\n/** How often a link is retried when another writer took the sequence number. */\nconst MAX_APPEND_ATTEMPTS = 20;\n/** Links read per query while verifying. */\nconst VERIFY_PAGE_SIZE = 500;\n/** Most problems one verification reports. */\nexport const MAX_REPORTED_PROBLEMS = 100;\n\n/** A link of the chain, as stored in the `auditChain` table. */\nexport interface AuditChainLink {\n id: string;\n /** 1, 2, 3, ...: unique, so concurrent writers can't fork the chain. */\n sequence: number;\n eventId: string;\n previousHash: string;\n hash: string;\n /** The event's `createdAt`, for retention. */\n createdAt: Date;\n}\n\nexport type ChainProblem =\n /** The event's content or the link changed after it was written. */\n | \"hash-mismatch\"\n /** The link's event was deleted. */\n | \"missing-event\"\n /** Links between this one and the previous were deleted. */\n | \"sequence-gap\"\n /** The link doesn't continue the previous link's hash. */\n | \"broken-link\";\n\nexport interface ChainVerification {\n /** Whether every link checks out and every event since the first link has one. */\n valid: boolean;\n /** How many links were checked. */\n checked: number;\n /** The first remaining link; older ones were deleted by retention. `null` without links. */\n firstSequence: number | null;\n lastSequence: number | null;\n /** The newest hash. Store it elsewhere now and then to detect deleted newest events. */\n lastHash: string | null;\n /** Up to 100 problems, oldest first. */\n problems: { sequence: number; eventId: string; problem: ChainProblem }[];\n /** Events stored since the first link that have no link (written around the chain). */\n unchainedEvents: number;\n}\n\ntype Context = Pick<AuthContext, \"adapter\" | \"logger\" | \"secret\">;\ntype Adapter = AuthContext[\"adapter\"];\n\ntype HmacKey = Awaited<ReturnType<typeof crypto.subtle.importKey>>;\nconst keys = new Map<string, Promise<HmacKey>>();\nfunction hmacKey(secret: string): Promise<HmacKey> {\n let key = keys.get(secret);\n if (!key) {\n key = crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n keys.set(secret, key);\n }\n return key;\n}\n\n/**\n * Everything the hash covers, in a fixed order. `createdAt` is in whole\n * seconds, because some databases store dates with less precision than\n * JavaScript, and the hash must match what is read back.\n */\nfunction canonical(event: AuditEvent, sequence: number, previousHash: string): string {\n return JSON.stringify([\n 1, // format version\n sequence,\n previousHash,\n event.id,\n event.type,\n event.status,\n event.severity,\n event.userId ?? null,\n event.actorUserId ?? null,\n event.sessionId ?? null,\n event.organizationId ?? null,\n event.ipAddress ?? null,\n event.userAgent ?? null,\n event.errorCode ?? null,\n event.metadata ? JSON.stringify(event.metadata) : null,\n Math.floor(new Date(event.createdAt).getTime() / 1000),\n ]);\n}\n\nexport async function linkHash(\n secret: string,\n event: AuditEvent,\n sequence: number,\n previousHash: string,\n): Promise<string> {\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n await hmacKey(secret),\n new TextEncoder().encode(canonical(event, sequence, previousHash)),\n );\n return Array.from(new Uint8Array(signature), (byte) => byte.toString(16).padStart(2, \"0\")).join(\n \"\",\n );\n}\n\nasync function latestLink(adapter: Adapter): Promise<AuditChainLink | null> {\n const [link] = await adapter.findMany<AuditChainLink>({\n model: \"auditChain\",\n limit: 1,\n sortBy: { field: \"sequence\", direction: \"desc\" },\n });\n return link ?? null;\n}\n\nexport function createChain(secretOption: string | undefined) {\n const secretFor = (ctx: Context) => secretOption ?? ctx.secret;\n\n /**\n * Adds a link for `event`. Concurrent writers compete for the next\n * sequence number through its unique index; the loser reads the new\n * latest link and tries again.\n */\n async function append(ctx: Context, event: AuditEvent): Promise<void> {\n const adapter = ctx.adapter;\n for (let attempt = 0; attempt < MAX_APPEND_ATTEMPTS; attempt++) {\n const previous = await latestLink(adapter);\n const sequence = (previous?.sequence ?? 0) + 1;\n const previousHash = previous?.hash ?? GENESIS_HASH;\n const hash = await linkHash(secretFor(ctx), event, sequence, previousHash);\n try {\n await adapter.create<Omit<AuditChainLink, \"id\">, AuditChainLink>({\n model: \"auditChain\",\n data: { sequence, eventId: event.id, previousHash, hash, createdAt: event.createdAt },\n });\n return;\n } catch (error) {\n // Not taken: a real failure.\n const taken = await adapter.findOne<AuditChainLink>({\n model: \"auditChain\",\n where: [{ field: \"sequence\", value: sequence }],\n });\n if (!taken) throw error;\n await new Promise((resolve) => setTimeout(resolve, Math.random() * 5 * (attempt + 1)));\n }\n }\n throw new Error(`[audit-log] could not chain event ${event.id}: too many concurrent writers`);\n }\n\n /**\n * `append`, but never inside a database transaction: a failed write\n * there (a taken sequence number) would abort the transaction on some\n * databases, and with it the sign-up being recorded. Inside one, the\n * link is added after the commit, and dropped with a rollback like its\n * event. Errors are logged: the event is stored, and verification\n * reports it as unchained.\n */\n async function link(ctx: Context, event: AuditEvent): Promise<void> {\n const run = async () => {\n try {\n await append(ctx, event);\n } catch (error) {\n ctx.logger.error(\"[audit-log] failed to chain an event\", error);\n }\n };\n if ((await getCurrentAdapter(ctx.adapter)) === ctx.adapter) await run();\n else await queueAfterTransactionHook(run);\n }\n\n /** Deletes links of events created before `date` (retention). */\n async function deleteOlderThan(ctx: Pick<Context, \"adapter\">, date: Date): Promise<number> {\n return ctx.adapter.deleteMany({\n model: \"auditChain\",\n where: [{ field: \"createdAt\", value: date, operator: \"lt\" }],\n });\n }\n\n /** Re-computes every link, oldest first, against the stored events. */\n async function verify(\n ctx: Context,\n readEvents: (ids: string[]) => Promise<Map<string, AuditEvent>>,\n ): Promise<ChainVerification> {\n const { adapter } = ctx;\n const secret = secretFor(ctx);\n const problems: ChainVerification[\"problems\"] = [];\n const report = (link: AuditChainLink, problem: ChainProblem) => {\n if (problems.length < MAX_REPORTED_PROBLEMS) {\n problems.push({ sequence: link.sequence, eventId: link.eventId, problem });\n }\n };\n\n let checked = 0;\n let first: AuditChainLink | null = null;\n let previous: AuditChainLink | null = null;\n let problemCount = 0;\n for (;;) {\n const links: AuditChainLink[] = await adapter.findMany<AuditChainLink>({\n model: \"auditChain\",\n where: previous ? [{ field: \"sequence\", value: previous.sequence, operator: \"gt\" }] : [],\n limit: VERIFY_PAGE_SIZE,\n sortBy: { field: \"sequence\", direction: \"asc\" },\n });\n if (links.length === 0) break;\n const events = await readEvents(links.map((link) => link.eventId));\n\n for (const link of links) {\n checked++;\n first ??= link;\n if (previous) {\n if (link.sequence !== previous.sequence + 1) {\n report(link, \"sequence-gap\");\n problemCount++;\n } else if (link.previousHash !== previous.hash) {\n report(link, \"broken-link\");\n problemCount++;\n }\n }\n const event = events.get(link.eventId);\n if (!event) {\n report(link, \"missing-event\");\n problemCount++;\n } else if (\n (await linkHash(secret, event, link.sequence, link.previousHash)) !== link.hash\n ) {\n report(link, \"hash-mismatch\");\n problemCount++;\n }\n previous = link;\n }\n if (links.length < VERIFY_PAGE_SIZE) break;\n }\n\n let unchainedEvents = 0;\n if (first && previous) {\n // Every event from the first linked one on should have a link.\n const firstEvent = (await readEvents([first.eventId])).get(first.eventId);\n if (firstEvent) {\n const stored = await adapter.count({\n model: \"auditEvent\",\n where: [{ field: \"createdAt\", value: firstEvent.createdAt, operator: \"gte\" }],\n });\n unchainedEvents = Math.max(0, stored - checked);\n }\n }\n\n return {\n valid: problemCount === 0 && unchainedEvents === 0,\n checked,\n firstSequence: first?.sequence ?? null,\n lastSequence: previous?.sequence ?? null,\n lastHash: previous?.hash ?? null,\n problems,\n unchainedEvents,\n };\n }\n\n return { link, verify, deleteOlderThan };\n}\n\nexport type Chain = ReturnType<typeof createChain>;\n","import type { AuthContext } from \"@better-auth/core\";\nimport { getCurrentAdapter } from \"@better-auth/core/context\";\nimport type { Where } from \"@better-auth/core/db/adapter\";\nimport type { AuditEvent, AuditEventInput, AuditEventQuery, AuditLogStorage } from \"./types\";\n\ntype Row = Omit<AuditEvent, \"metadata\"> & { metadata: string | null };\n\nfunction toEvent(row: Row): AuditEvent {\n let metadata: Record<string, unknown> | null = null;\n if (row.metadata) {\n try {\n metadata = JSON.parse(row.metadata);\n } catch {\n metadata = null;\n }\n }\n return {\n id: row.id,\n type: row.type,\n status: row.status,\n severity: row.severity,\n userId: row.userId ?? null,\n actorUserId: row.actorUserId ?? null,\n sessionId: row.sessionId ?? null,\n organizationId: row.organizationId ?? null,\n ipAddress: row.ipAddress ?? null,\n userAgent: row.userAgent ?? null,\n errorCode: row.errorCode ?? null,\n metadata,\n createdAt: new Date(row.createdAt),\n };\n}\n\nfunction filters(query: Omit<AuditEventQuery, \"limit\" | \"offset\">): Where[] {\n const where: Where[] = [];\n if (query.userId) where.push({ field: \"userId\", value: query.userId });\n if (query.actorUserId) where.push({ field: \"actorUserId\", value: query.actorUserId });\n if (query.type) where.push({ field: \"type\", value: query.type });\n if (query.status) where.push({ field: \"status\", value: query.status });\n if (query.from) where.push({ field: \"createdAt\", value: query.from, operator: \"gte\" });\n if (query.to) where.push({ field: \"createdAt\", value: query.to, operator: \"lt\" });\n return where;\n}\n\n/**\n * The default storage: the plugin's `auditEvent` table, through\n * better-auth's adapter. Inside a transaction (database hooks), writes go\n * through the transaction's adapter, so SQLite doesn't wait on itself.\n */\nexport function databaseStorage(\n baseAdapter: AuthContext[\"adapter\"],\n): AuditLogStorage & { readByIds: (ids: string[]) => Promise<Map<string, AuditEvent>> } {\n const adapter = () => getCurrentAdapter(baseAdapter);\n return {\n async write(event: AuditEventInput) {\n const row = await (await adapter()).create<Omit<Row, \"id\">, Row>({\n model: \"auditEvent\",\n data: { ...event, metadata: event.metadata ? JSON.stringify(event.metadata) : null },\n });\n return toEvent(row);\n },\n async read(query) {\n const db = await adapter();\n const where = filters(query);\n const [rows, total] = await Promise.all([\n db.findMany<Row>({\n model: \"auditEvent\",\n where,\n limit: query.limit,\n offset: query.offset,\n sortBy: { field: \"createdAt\", direction: \"desc\" },\n }),\n db.count({ model: \"auditEvent\", where }),\n ]);\n return { events: rows.map(toEvent), total };\n },\n async readByIds(ids) {\n if (ids.length === 0) return new Map();\n const rows = await (await adapter()).findMany<Row>({\n model: \"auditEvent\",\n where: [{ field: \"id\", value: ids, operator: \"in\" }],\n });\n return new Map(rows.map((row) => [row.id, toEvent(row)]));\n },\n async deleteOlderThan(date) {\n return (await adapter()).deleteMany({\n model: \"auditEvent\",\n where: [{ field: \"createdAt\", value: date, operator: \"lt\" }],\n });\n },\n };\n}\n","/** Keys whose values never reach the log, whatever their nesting or case. */\nconst SECRET_KEY =\n /password|passphrase|secret|token|^otp$|backup.?codes?|api.?key|authorization|cookie/i;\nconst REDACTED = \"[redacted]\";\nconst MAX_DEPTH = 5;\n\n/**\n * Copies metadata with secret-looking values replaced, so a careless\n * `recordAuditEvent` call can't put a password or token in the log.\n */\nexport function redact(value: unknown, depth = 0): unknown {\n if (depth > MAX_DEPTH) return REDACTED;\n if (Array.isArray(value)) return value.map((item) => redact(item, depth + 1));\n if (value instanceof Date) return value.toISOString();\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [\n key,\n SECRET_KEY.test(key) ? REDACTED : redact(item, depth + 1),\n ]),\n );\n }\n return value;\n}\n","import type { AuditEventSeverity, AuditEventStatus } from \"../types\";\n\nconst BUILT_IN: Record<string, AuditEventSeverity> = {\n \"sign-up\": \"low\",\n \"sign-in\": \"low\",\n \"sign-out\": \"low\",\n \"email-verified\": \"low\",\n \"session-revoked\": \"medium\",\n \"account-linked\": \"medium\",\n \"account-unlinked\": \"medium\",\n \"email-changed\": \"medium\",\n \"password-changed\": \"medium\",\n \"password-reset\": \"medium\",\n \"user-deleted\": \"high\",\n \"two-factor-enabled\": \"medium\",\n \"two-factor-disabled\": \"high\",\n \"organization.deleted\": \"high\",\n \"organization.member-removed\": \"medium\",\n \"organization.member-role-changed\": \"high\",\n \"admin.set-role\": \"critical\",\n \"admin.set-user-password\": \"critical\",\n \"admin.impersonate-user\": \"critical\",\n};\n\n/**\n * The severity of an event: the `severity` option, then the built-in\n * table. Admin actions are `high` unless listed; failed sign-ins `medium`;\n * anything else `low`.\n */\nexport function severityOf(\n type: string,\n status: AuditEventStatus,\n overrides: Readonly<Record<string, AuditEventSeverity>>,\n): AuditEventSeverity {\n const override = overrides[type];\n if (override) return override;\n if (type === \"sign-in\" && status === \"failure\") return \"medium\";\n return BUILT_IN[type] ?? (type.startsWith(\"admin.\") ? \"high\" : \"low\");\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport { getIP } from \"@better-auth/core/utils/ip\";\nimport { createChain } from \"./chain\";\nimport { databaseStorage } from \"./store\";\nimport type {\n AuditEvent,\n AuditEventInput,\n AuditEventSeverity,\n AuditEventStatus,\n AuditLogStorage,\n ResolvedAuditLogOptions,\n} from \"./types\";\nimport { redact } from \"./utils/redact\";\nimport { severityOf } from \"./utils/severity\";\n\n/** Longest JSON-encoded `metadata`. Longer metadata from hooks is replaced by `{ truncated: true }`. */\nexport const MAX_METADATA_LENGTH = 4000;\nconst MAX_USER_AGENT_LENGTH = 512;\n\ntype Context = Pick<\n AuthContext,\n \"adapter\" | \"logger\" | \"options\" | \"secret\" | \"runInBackground\" | \"runInBackgroundOrAwait\"\n>;\n\n/** What hooks and `recordAuditEvent` know about an event; the rest is filled in. */\nexport interface EventDetails {\n type: string;\n status?: AuditEventStatus | undefined;\n severity?: AuditEventSeverity | undefined;\n userId?: string | null | undefined;\n actorUserId?: string | null | undefined;\n sessionId?: string | null | undefined;\n organizationId?: string | null | undefined;\n ipAddress?: string | null | undefined;\n userAgent?: string | null | undefined;\n errorCode?: string | null | undefined;\n metadata?: Record<string, unknown> | null | undefined;\n}\n\nexport type Recorder = ReturnType<typeof createRecorder>;\n\nexport function createRecorder(options: ResolvedAuditLogOptions) {\n let lastSweep = 0;\n const chain = options.hashChain ? createChain(options.hashChain.secret) : null;\n let lastCreatedAt = 0;\n /**\n * The current time, but never the same as or earlier than the previous\n * event's: events recorded in the same millisecond (a sign-up and its\n * sign-in) still list in the order they happened.\n */\n const nextTimestamp = () => {\n lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);\n return new Date(lastCreatedAt);\n };\n\n const storageFor = (ctx: Pick<Context, \"adapter\">): AuditLogStorage =>\n options.storage ?? databaseStorage(ctx.adapter);\n\n const isRecorded = (type: string) => !options.events || options.events(type);\n\n function build(ctx: Context, details: EventDetails, headers?: Headers | null): AuditEventInput {\n const status = details.status ?? \"success\";\n let metadata = details.metadata ? (redact(details.metadata) as Record<string, unknown>) : null;\n if (metadata && JSON.stringify(metadata).length > MAX_METADATA_LENGTH) {\n metadata = { truncated: true };\n }\n const ipAddress =\n details.ipAddress ?? (options.captureIp && headers ? getIP(headers, ctx.options) : null);\n const userAgent =\n details.userAgent ?? (options.captureUserAgent ? (headers?.get(\"user-agent\") ?? null) : null);\n return {\n type: details.type,\n status,\n severity: details.severity ?? severityOf(details.type, status, options.severity),\n userId: details.userId ?? null,\n actorUserId: details.actorUserId ?? null,\n sessionId: details.sessionId ?? null,\n organizationId: details.organizationId ?? null,\n ipAddress,\n userAgent: userAgent ? userAgent.slice(0, MAX_USER_AGENT_LENGTH) : null,\n errorCode: details.errorCode ?? null,\n metadata,\n createdAt: nextTimestamp(),\n };\n }\n\n /** Deletes events created before `date`, and their links. */\n async function deleteOlderThan(ctx: Pick<Context, \"adapter\">, date: Date): Promise<number> {\n const deleted = await storageFor(ctx).deleteOlderThan(date);\n if (chain) await chain.deleteOlderThan(ctx, date);\n return deleted;\n }\n\n /** Starts a retention sweep in the background, at most once per `sweepInterval`. */\n function maybeSweep(ctx: Context) {\n if (!options.retention) return;\n const now = Date.now();\n if (now - lastSweep < options.retention.sweepInterval * 1000) return;\n lastSweep = now;\n const cutoff = new Date(now - options.retention.days * 86_400_000);\n const sweep = deleteOlderThan(ctx, cutoff).catch((error) =>\n ctx.logger.error(\"[audit-log] retention sweep failed\", error),\n );\n // Not awaited: a slow sweep must not slow down the request.\n ctx.runInBackground(sweep);\n }\n\n /**\n * Builds, filters, and stores an event, then calls `onEvent`. Returns\n * `null` if the event isn't recorded (`events`, `beforeLog`). Storage\n * errors are thrown; `onEvent` errors are logged.\n */\n async function recordNow(\n ctx: Context,\n details: EventDetails,\n headers?: Headers | null,\n ): Promise<AuditEvent | null> {\n if (!isRecorded(details.type)) return null;\n let event: AuditEventInput | null = build(ctx, details, headers);\n if (options.beforeLog) {\n try {\n event = await options.beforeLog(event);\n } catch (error) {\n ctx.logger.error(\"[audit-log] beforeLog threw an error; the event was dropped\", error);\n return null;\n }\n if (!event) return null;\n }\n const stored = await storageFor(ctx).write(event);\n if (chain) await chain.link(ctx, stored);\n if (options.onEvent) {\n try {\n await options.onEvent(stored);\n } catch (error) {\n ctx.logger.error(\"[audit-log] onEvent threw an error\", error);\n }\n }\n maybeSweep(ctx);\n return stored;\n }\n\n /**\n * `recordNow` for hooks: never throws, and runs in the background when\n * better-auth's `advanced.backgroundTasks` is configured.\n */\n async function record(\n ctx: Context,\n details: EventDetails,\n headers?: Headers | null,\n ): Promise<void> {\n if (!isRecorded(details.type)) return;\n const task = recordNow(ctx, details, headers).then(\n () => undefined,\n (error) => ctx.logger.error(`[audit-log] failed to record a \"${details.type}\" event`, error),\n );\n await ctx.runInBackgroundOrAwait(task);\n }\n\n return { record, recordNow, storageFor, isRecorded, deleteOlderThan, chain };\n}\n","import * as z from \"zod\";\n\n/**\n * The `limit` and `offset` fields of a list endpoint's query, coerced from\n * query strings. Spread into the endpoint's `z.object({ ... })`.\n */\nexport function paginationQuery({\n defaultLimit,\n maxLimit,\n}: {\n defaultLimit: number;\n maxLimit: number;\n}) {\n return {\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .max(maxLimit)\n .default(defaultLimit)\n .meta({\n description: `How many to return, at most ${maxLimit}. Defaults to ${defaultLimit}`,\n }),\n offset: z.coerce\n .number()\n .int()\n .min(0)\n .default(0)\n .meta({ description: \"How many to skip. Defaults to 0\" }),\n };\n}\n","import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { isAdmin } from \"@repo/plugin-utils/admin\";\nimport { paginationQuery } from \"@repo/plugin-utils/pagination\";\nimport { APIError, createAuthEndpoint, sessionMiddleware } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport { AUDIT_LOG_ERROR_CODES } from \"../error-codes\";\nimport { isValidEventType, MAX_TYPE_LENGTH } from \"../options\";\nimport type { Recorder } from \"../recorder\";\nimport { MAX_METADATA_LENGTH } from \"../recorder\";\nimport { databaseStorage } from \"../store\";\nimport type { ResolvedAuditLogOptions } from \"../types\";\n\n/** Most events one list request returns. */\nexport const MAX_PAGE_SIZE = 100;\n\nconst statusSchema = z.enum([\"success\", \"failure\"]);\nconst severitySchema = z.enum([\"low\", \"medium\", \"high\", \"critical\"]);\nconst typeSchema = z\n .string()\n .max(MAX_TYPE_LENGTH)\n .refine(isValidEventType, { message: \"Invalid event type\" });\nconst idSchema = z.string().min(1).max(255);\n\nexport function createEventEndpoints(options: ResolvedAuditLogOptions, recorder: Recorder) {\n return {\n listAuditEvents: createAuthEndpoint(\n \"/audit-log/list-events\",\n {\n method: \"GET\",\n use: [sessionMiddleware],\n query: z.optional(\n z.object({\n userId: idSchema\n .optional()\n .meta({ description: \"Events about this user. Admins only, except for yourself\" }),\n actorUserId: idSchema\n .optional()\n .meta({ description: \"Events done by this user. Admins only\" }),\n type: typeSchema.optional().meta({ description: \"Only events of this type\" }),\n status: statusSchema.optional().meta({ description: \"Only events with this status\" }),\n from: z.coerce.date().optional().meta({ description: \"Events at or after this time\" }),\n to: z.coerce.date().optional().meta({ description: \"Events before this time\" }),\n ...paginationQuery({ defaultLimit: 20, maxLimit: MAX_PAGE_SIZE }),\n }),\n ),\n metadata: {\n openapi: {\n description: \"List audit events, newest first: your own, or any user's for admins\",\n },\n },\n },\n async (ctx) => {\n const me = ctx.context.session.user;\n const query = ctx.query ?? { limit: 20, offset: 0 };\n const admin = isAdmin(me, options);\n if (!admin && ((query.userId && query.userId !== me.id) || query.actorUserId)) {\n throw APIError.from(\n \"FORBIDDEN\",\n AUDIT_LOG_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_OTHER_USERS_EVENTS,\n );\n }\n const { events, total } = await recorder.storageFor(ctx.context).read({\n userId: admin ? query.userId : me.id,\n actorUserId: query.actorUserId,\n type: query.type,\n status: query.status,\n from: query.from,\n to: query.to,\n limit: query.limit ?? 20,\n offset: query.offset ?? 0,\n });\n // Users see where and how they did something, but not the IP address,\n // user agent, or session of someone else acting on their account\n // (an admin, or whoever failed to sign in as them).\n return ctx.json({\n events: admin\n ? events\n : events.map((event) =>\n event.actorUserId === me.id\n ? event\n : { ...event, ipAddress: null, userAgent: null, sessionId: null },\n ),\n total,\n });\n },\n ),\n /**\n * Server only (no path, so not reachable over HTTP): records an event\n * of your own, e.g. `auth.api.recordAuditEvent({ body: { type:\n * \"billing.plan-changed\", userId } })`.\n */\n recordAuditEvent: createAuthEndpoint(\n {\n method: \"POST\",\n body: z.object({\n type: typeSchema.meta({ description: \"The event type, e.g. `billing.plan-changed`\" }),\n status: statusSchema.optional(),\n severity: severitySchema.optional(),\n userId: idSchema.optional().meta({ description: \"The user the event is about\" }),\n actorUserId: idSchema.optional().meta({ description: \"The user who did it\" }),\n organizationId: idSchema.optional(),\n errorCode: z.string().max(255).optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n }),\n metadata: {\n openapi: { description: \"Record an audit event. Server only\" },\n },\n },\n async (ctx) => {\n const { metadata } = ctx.body;\n if (metadata && JSON.stringify(metadata).length > MAX_METADATA_LENGTH) {\n throw APIError.from(\"BAD_REQUEST\", AUDIT_LOG_ERROR_CODES.METADATA_TOO_LARGE);\n }\n const event = await recorder.recordNow(\n ctx.context,\n ctx.body,\n ctx.request?.headers ?? ctx.headers,\n );\n return ctx.json({ event });\n },\n ),\n /**\n * Server only: deletes old events, for a cron job. Uses `retention.days`\n * unless `days` is passed.\n */\n deleteExpiredAuditEvents: createAuthEndpoint(\n {\n method: \"POST\",\n body: z.optional(\n z.object({\n days: z\n .number()\n .positive()\n .optional()\n .meta({ description: \"Delete events older than this\" }),\n }),\n ),\n metadata: {\n openapi: {\n description: \"Delete audit events older than the retention period. Server only\",\n },\n },\n },\n async (ctx) => {\n const days = ctx.body?.days ?? options.retention?.days;\n if (!days) {\n throw APIError.from(\"BAD_REQUEST\", AUDIT_LOG_ERROR_CODES.RETENTION_NOT_CONFIGURED);\n }\n const deleted = await recorder.deleteOlderThan(\n ctx.context,\n new Date(Date.now() - days * 86_400_000),\n );\n return ctx.json({ deleted });\n },\n ),\n /**\n * Server only: re-computes the hash chain and reports changed, deleted,\n * and unchained events. Reads every link, so run it from a job, not on\n * every request.\n */\n verifyAuditChain: createAuthEndpoint(\n {\n method: \"POST\",\n metadata: {\n openapi: {\n description: \"Verify the audit log's hash chain. Server only\",\n },\n },\n },\n async (ctx) => {\n const { chain } = recorder;\n if (!chain) {\n throw APIError.from(\"BAD_REQUEST\", AUDIT_LOG_ERROR_CODES.HASH_CHAIN_NOT_ENABLED);\n }\n const storage = databaseStorage(ctx.context.adapter);\n return ctx.json(await chain.verify(ctx.context, (ids) => storage.readByIds(ids)));\n },\n ),\n } satisfies BetterAuthPlugin[\"endpoints\"];\n}\n","import type { BetterAuthPluginDBSchema } from \"@better-auth/core/db\";\n\nexport const auditLogSchema = {\n /**\n * One row per event. User ids are plain columns, not foreign keys: the\n * log must outlive the users it mentions, `user-deleted` included.\n */\n auditEvent: {\n fields: {\n /** `sign-in`, `admin.ban-user`, or an app-defined type. */\n type: {\n type: \"string\",\n required: true,\n index: true,\n },\n /** \"success\" | \"failure\" */\n status: {\n type: \"string\",\n required: true,\n },\n /** \"low\" | \"medium\" | \"high\" | \"critical\" */\n severity: {\n type: \"string\",\n required: true,\n },\n /** The user the event is about. */\n userId: {\n type: \"string\",\n required: false,\n index: true,\n },\n /** The user who did it: the user themselves, or an admin. */\n actorUserId: {\n type: \"string\",\n required: false,\n index: true,\n },\n sessionId: {\n type: \"string\",\n required: false,\n },\n /** The session's active organization (organization plugin). */\n organizationId: {\n type: \"string\",\n required: false,\n },\n ipAddress: {\n type: \"string\",\n required: false,\n },\n userAgent: {\n type: \"string\",\n required: false,\n },\n /** The error code of a failed request, e.g. `INVALID_EMAIL_OR_PASSWORD`. */\n errorCode: {\n type: \"string\",\n required: false,\n },\n /** JSON-encoded. */\n metadata: {\n type: \"string\",\n required: false,\n },\n createdAt: {\n type: \"date\",\n required: true,\n index: true,\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\n/** Added with the `hashChain` option: one link per event, in order. */\nexport const auditChainSchema = {\n auditChain: {\n fields: {\n /** 1, 2, 3, ... Unique, so concurrent writers can't fork the chain. */\n sequence: {\n type: \"number\",\n required: true,\n },\n /** The event's id. Not a foreign key: a deleted event must show as missing. */\n eventId: {\n type: \"string\",\n required: true,\n },\n previousHash: {\n type: \"string\",\n required: true,\n },\n /** HMAC-SHA256 of the event, the sequence number, and `previousHash`. */\n hash: {\n type: \"string\",\n required: true,\n },\n /** The event's `createdAt`, for retention. */\n createdAt: {\n type: \"date\",\n required: true,\n index: true,\n },\n },\n indexes: [{ fields: [\"sequence\"], unique: true }],\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport type AuditLogSchema = typeof auditLogSchema & Partial<typeof auditChainSchema>;\n","import type { AuditEvent, AuditEventQuery, AuditLogStorage } from \"./types\";\n\n/**\n * Keeps events in memory, newest last. For tests and demos: events are\n * lost on restart and not shared between server instances.\n *\n * @example\n * ```ts\n * const storage = memoryStorage();\n * betterAuth({ plugins: [auditLog({ storage })] });\n * storage.events; // every stored event\n * ```\n */\nexport function memoryStorage(): AuditLogStorage & { readonly events: AuditEvent[] } {\n const events: AuditEvent[] = [];\n const matches = (event: AuditEvent, query: AuditEventQuery) =>\n (!query.userId || event.userId === query.userId) &&\n (!query.actorUserId || event.actorUserId === query.actorUserId) &&\n (!query.type || event.type === query.type) &&\n (!query.status || event.status === query.status) &&\n (!query.from || event.createdAt >= query.from) &&\n (!query.to || event.createdAt < query.to);\n\n return {\n events,\n async write(input) {\n const event = { ...input, id: crypto.randomUUID() };\n events.push(event);\n return event;\n },\n async read(query) {\n const found = events.filter((event) => matches(event, query)).reverse();\n return { events: found.slice(query.offset, query.offset + query.limit), total: found.length };\n },\n async deleteOlderThan(date) {\n const kept = events.filter((event) => event.createdAt >= date);\n const deleted = events.length - kept.length;\n events.splice(0, events.length, ...kept);\n return deleted;\n },\n };\n}\n","import type { AuthContext, BetterAuthPlugin } from \"@better-auth/core\";\nimport { mergeSchema } from \"better-auth/db\";\nimport { version } from \"../package.json\";\nimport { AUDIT_LOG_ERROR_CODES } from \"./error-codes\";\nimport { createDatabaseHooks } from \"./hooks/database\";\nimport { createPathHooks } from \"./hooks/paths\";\nimport { resolveOptions } from \"./options\";\nimport { createRecordEvent } from \"./record-event\";\nimport { createRecorder } from \"./recorder\";\nimport { createEventEndpoints } from \"./routes/events\";\nimport { auditChainSchema, auditLogSchema } from \"./schema\";\nimport type { AuditLogOptions } from \"./types\";\n\nexport type { AuditChainLink, ChainProblem, ChainVerification } from \"./chain\";\nexport { AUDIT_LOG_ERROR_CODES, type AuditLogErrorCode } from \"./error-codes\";\nexport { memoryStorage } from \"./memory\";\nexport type { PluginEventDetails } from \"./record-event\";\nexport type {\n AdminAuditAction,\n AuditEvent,\n AuditEventInput,\n AuditEventQuery,\n AuditEventSeverity,\n AuditEventStatus,\n AuditEventType,\n AuditLogOptions,\n AuditLogRetention,\n AuditLogStorage,\n BuiltInAuditEventType,\n} from \"./types\";\n\n/** Plugins whose after-hooks clear `ctx.context.newSession` (a sign-in that isn't done yet). */\nconst CLEARS_NEW_SESSION = new Set([\"two-factor\"]);\n\n/**\n * Sign-ins are read from `ctx.context.newSession` after the endpoint ran,\n * which plugins such as `twoFactor` clear in their own after-hooks (the\n * sign-in isn't done until the second factor). Hooks run in plugin order,\n * so the audit log must come after them.\n */\nfunction warnAboutOrder(ctx: Pick<AuthContext, \"options\" | \"logger\">) {\n const plugins = ctx.options.plugins ?? [];\n const index = plugins.findIndex((plugin) => plugin.id === \"audit-log\");\n const later = plugins\n .slice(index + 1)\n .filter((plugin) => CLEARS_NEW_SESSION.has(plugin.id))\n .map((plugin) => plugin.id);\n if (later.length > 0) {\n ctx.logger.warn(\n `[audit-log] Put auditLog() after ${later.join(\", \")} in \"plugins\", so sign-ins are recorded once they are complete.`,\n );\n }\n}\n\n/**\n * Records security-relevant events: sign-ins (any method) and failed\n * sign-ins, sign-ups, sign-outs, revoked sessions, password and email\n * changes, linked accounts, deleted users, and the actions of better-auth's\n * `admin` plugin. Users can list their own events, admins everyone's.\n *\n * @example\n * ```ts\n * import { betterAuth } from \"better-auth\";\n * import { auditLog } from \"@checodo/better-auth-audit-log\";\n *\n * export const auth = betterAuth({\n * plugins: [\n * // ...other plugins, then:\n * auditLog({ retention: { days: 90 } }),\n * ],\n * });\n * ```\n */\nexport const auditLog = (options: AuditLogOptions = {}) => {\n const resolved = resolveOptions(options);\n const recorder = createRecorder(resolved);\n\n const plugin = {\n id: \"audit-log\",\n version,\n init(ctx) {\n warnAboutOrder(ctx);\n return { options: { databaseHooks: createDatabaseHooks(ctx, recorder) } };\n },\n // Custom storage keeps events elsewhere, so no table is needed.\n // mergeSchema changes the object it gets, so each instance gets a copy.\n ...(resolved.storage\n ? {}\n : {\n schema: mergeSchema(\n structuredClone({ ...auditLogSchema, ...(resolved.hashChain ? auditChainSchema : {}) }),\n options.schema,\n ),\n }),\n endpoints: createEventEndpoints(resolved, recorder),\n hooks: createPathHooks(resolved, recorder),\n $ERROR_CODES: AUDIT_LOG_ERROR_CODES,\n options,\n } satisfies BetterAuthPlugin;\n\n return Object.assign(plugin, {\n /**\n * Records an event from another plugin or your own server code, with\n * the current request's user, session, IP address, and user agent:\n * `ctx.context.getPlugin(\"audit-log\")?.recordEvent({ type: \"x.y\", userId })`.\n * Never throws.\n */\n recordEvent: createRecordEvent(recorder),\n });\n};\n\nexport type AuditLogPlugin = ReturnType<typeof auditLog>;\n\n/** Lets `auth.$context.getPlugin(\"audit-log\")` infer this plugin's exact type. */\ndeclare module \"@better-auth/core\" {\n interface BetterAuthPluginRegistry<AuthOptions, Options> {\n \"audit-log\": {\n creator: typeof auditLog;\n };\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,78 @@
1
1
  {
2
2
  "name": "@checodo/better-auth-audit-log",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Audit log of sign-ins, account changes, and admin actions for better-auth.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "Clas Heinemeyer",
9
+ "email": "the.checodo@gmail.com",
10
+ "url": "https://checodo.dev"
11
+ },
12
+ "homepage": "https://github.com/checodo/better-auth-plugins/tree/main/packages/audit-log",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/checodo/better-auth-plugins.git",
16
+ "directory": "packages/audit-log"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/checodo/better-auth-plugins/issues"
20
+ },
21
+ "keywords": [
22
+ "better-auth",
23
+ "better-auth-plugin",
24
+ "audit-log",
25
+ "audit",
26
+ "auth"
27
+ ],
28
+ "sideEffects": false,
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./client": {
35
+ "types": "./dist/client.d.ts",
36
+ "default": "./dist/client.js"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "main": "./dist/index.js",
41
+ "types": "./dist/index.d.ts",
42
+ "files": [
43
+ "dist",
44
+ "CHANGELOG.md"
45
+ ],
46
+ "peerDependencies": {
47
+ "@better-auth/core": "^1.7.20",
48
+ "better-auth": "^1.7.20"
49
+ },
50
+ "dependencies": {
51
+ "zod": "^4.5.4"
52
+ },
53
+ "devDependencies": {
54
+ "@better-auth/core": "^1.7.20",
55
+ "@checodo/better-auth-blocklist": "0.1.0",
56
+ "@checodo/better-auth-referral": "0.1.0",
57
+ "@checodo/better-auth-waitlist": "0.1.0",
58
+ "@repo/plugin-utils": "0.0.0",
59
+ "@repo/tooling": "0.0.0",
60
+ "@types/node": "^24.19.2",
61
+ "better-auth": "^1.7.20",
62
+ "tsdown": "^0.23.7",
63
+ "typescript": "^5.9.3",
64
+ "vitest": "^5.1.0"
65
+ },
66
+ "publishConfig": {
67
+ "access": "public"
68
+ },
69
+ "scripts": {
70
+ "build": "tsdown",
71
+ "dev": "tsdown --watch --no-clean",
72
+ "test": "vitest run",
73
+ "test:watch": "vitest",
74
+ "typecheck": "tsc --noEmit",
75
+ "clean": "rm -rf dist .turbo",
76
+ "lint:package": "publint && attw --pack . --profile esm-only"
77
+ }
6
78
  }