@checodo/better-auth-referral 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","../../plugin-utils/src/admin.ts","../../plugin-utils/src/options.ts","../src/options.ts","../../plugin-utils/src/audit.ts","../../plugin-utils/src/callbacks.ts","../src/store.ts","../src/utils/code.ts","../src/referrals.ts","../src/hooks/database.ts","../src/routes/codes.ts","../../plugin-utils/src/pagination.ts","../src/utils/mask.ts","../src/routes/referrals.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["","/** 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 type { AuthContext } from \"@better-auth/core\";\nimport { resolveAdminOptions } from \"@repo/plugin-utils/admin\";\nimport { createFail } from \"@repo/plugin-utils/options\";\nimport type { ReferralOptions, ResolvedReferralOptions } from \"./types\";\n\n/** The step every referral starts with. Not configurable, and not stored. */\nexport const SIGN_UP_STEP = \"sign-up\";\n/** A step the plugin records itself, when the referred user's email is verified. */\nexport const EMAIL_VERIFIED_STEP = \"email-verified\";\n/** Longest step name. Part of a unique index, so bounded. */\nexport const MAX_STEP_LENGTH = 64;\n\nconst fail = createFail(\"referral\");\n\n/** Resolves defaults and validates the options once, at plugin creation. */\nexport function resolveOptions(options: ReferralOptions): ResolvedReferralOptions {\n const codeLength = options.codeLength ?? 8;\n if (!Number.isInteger(codeLength) || codeLength < 6 || codeLength > 32) {\n fail(`\"codeLength\" must be an integer between 6 and 32, got ${codeLength}.`);\n }\n\n const max = options.maxReferralsPerUser;\n if (max !== undefined && (!Number.isInteger(max) || max < 1)) {\n fail(`\"maxReferralsPerUser\" must be a positive integer, got ${max}.`);\n }\n\n const steps = [...(options.steps ?? [])];\n for (const step of steps) {\n if (!/^[a-z0-9][a-z0-9_-]*$/i.test(step) || step.length > MAX_STEP_LENGTH) {\n fail(\n `Invalid step ${JSON.stringify(step)}: use letters, digits, \"-\", and \"_\", up to ${MAX_STEP_LENGTH} characters.`,\n );\n }\n if (step === SIGN_UP_STEP) {\n fail(`\"${SIGN_UP_STEP}\" is always the first step; don't list it in \"steps\".`);\n }\n }\n if (new Set(steps).size !== steps.length) fail(`\"steps\" lists a step twice.`);\n\n return {\n codeLength,\n generateCode: options.generateCode,\n createCodeOnSignUp: options.createCodeOnSignUp ?? false,\n steps,\n stepsConfigured: options.steps !== undefined,\n maxReferralsPerUser: max,\n invalidCode: options.invalidCode ?? \"ignore\",\n allowReferral: options.allowReferral,\n onReferral: options.onReferral,\n onStepCompleted: options.onStepCompleted,\n onReferralCompleted: options.onReferralCompleted,\n maskEmails: options.maskEmails ?? true,\n hideNames: options.hideNames ?? false,\n ...resolveAdminOptions(options),\n };\n}\n\n/**\n * Defaults that depend on the app's auth config, applied in the plugin's\n * `init`: without explicit `steps`, apps that send verification emails\n * require `email-verified`, so fake sign-ups don't complete referrals.\n * Warns when referrals earn rewards but complete at sign-up.\n */\nexport function applyAuthDefaults(\n options: ResolvedReferralOptions,\n ctx: Pick<AuthContext, \"options\" | \"logger\">,\n): void {\n // Settled once per auth instance, before any request reads `steps`.\n if (!options.stepsConfigured && ctx.options.emailVerification?.sendVerificationEmail) {\n options.steps = [EMAIL_VERIFIED_STEP];\n }\n // An explicit `steps: []` is a choice; only warn about the default.\n if (!options.stepsConfigured && options.steps.length === 0 && options.onReferralCompleted) {\n ctx.logger.warn(\n '[referral] Referrals complete at sign-up (no \"steps\"), so fake sign-ups earn rewards. ' +\n 'Require at least \"email-verified\" (see \"Steps and rewards\" in the referral plugin docs).',\n );\n }\n}\n","import { tryGetCurrentAuthEndpointContext } from \"@better-auth/core/context\";\n\n/** An event for `@checodo/better-auth-audit-log`. */\nexport interface AuditEvent {\n type: string;\n status?: \"success\" | \"failure\" | undefined;\n severity?: \"low\" | \"medium\" | \"high\" | \"critical\" | undefined;\n /** The user the event is about. */\n userId?: string | null | undefined;\n /** Who did it; defaults to the signed-in user. */\n actorUserId?: string | null | undefined;\n errorCode?: string | null | undefined;\n metadata?: Record<string, unknown> | undefined;\n}\n\n/** The part of the audit log plugin used here. */\ninterface AuditLog {\n recordEvent?: (event: AuditEvent) => Promise<void>;\n}\n\n/**\n * Records an event in the audit log plugin, if the app uses it. The audit\n * log is found through `getPlugin`, like official plugins find each other,\n * so it isn't a dependency. Does nothing outside a request. Never throws:\n * the audit log's `recordEvent` doesn't.\n */\nexport async function recordAuditEvent(event: AuditEvent): Promise<void> {\n const endpoint = tryGetCurrentAuthEndpointContext();\n const auditLog = endpoint?.context.getPlugin(\"audit-log\") as AuditLog | null | undefined;\n await auditLog?.recordEvent?.(event);\n}\n","import type { AuthContext } from \"@better-auth/core\";\n\n/**\n * Runs a user callback (`onJoin`, `onReferralCompleted`, ...) in the\n * background when better-auth's `advanced.backgroundTasks` is configured,\n * so response times don't depend on it, and awaits it otherwise. Errors\n * are logged as `[<pluginId>] <name> threw an error` and never reach the\n * client.\n */\nexport async function runCallback(\n context: Pick<AuthContext, \"logger\" | \"runInBackgroundOrAwait\">,\n pluginId: string,\n name: string,\n callback: () => void | Promise<void>,\n): Promise<void> {\n const task = (async () => {\n try {\n await callback();\n } catch (error) {\n context.logger.error(`[${pluginId}] ${name} threw an error`, error);\n }\n })();\n await context.runInBackgroundOrAwait(task);\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport type { Where } from \"@better-auth/core/db/adapter\";\nimport type { Referral, ReferralCode, ReferralStatus, ReferralStep } from \"./types\";\n\n/** The adapter methods used here; satisfied by the base adapter and a transaction adapter. */\nexport type Adapter = Pick<\n AuthContext[\"adapter\"],\n \"findMany\" | \"findOne\" | \"create\" | \"update\" | \"count\" | \"delete\"\n>;\n\n// ----- codes -----\n\nexport async function findCodeByUserId(\n adapter: Adapter,\n userId: string,\n): Promise<ReferralCode | null> {\n return adapter.findOne<ReferralCode>({\n model: \"referralCode\",\n where: [{ field: \"userId\", value: userId }],\n });\n}\n\nexport async function findCodeByCode(adapter: Adapter, code: string): Promise<ReferralCode | null> {\n return adapter.findOne<ReferralCode>({\n model: \"referralCode\",\n where: [{ field: \"code\", value: code }],\n });\n}\n\nexport async function createCode(\n adapter: Adapter,\n input: { userId: string; code: string },\n): Promise<ReferralCode> {\n return adapter.create<Omit<ReferralCode, \"id\">, ReferralCode>({\n model: \"referralCode\",\n data: { userId: input.userId, code: input.code, createdAt: new Date() },\n });\n}\n\nexport async function replaceCode(\n adapter: Adapter,\n id: string,\n code: string,\n): Promise<ReferralCode | null> {\n return adapter.update<ReferralCode>({\n model: \"referralCode\",\n where: [{ field: \"id\", value: id }],\n update: { code, createdAt: new Date() },\n });\n}\n\n// ----- referrals -----\n\nexport async function findReferralByReferredUserId(\n adapter: Adapter,\n referredUserId: string,\n): Promise<Referral | null> {\n return adapter.findOne<Referral>({\n model: \"referral\",\n where: [{ field: \"referredUserId\", value: referredUserId }],\n });\n}\n\nexport async function createReferral(\n adapter: Adapter,\n input: Pick<Referral, \"referrerUserId\" | \"referredUserId\" | \"code\" | \"status\">,\n): Promise<Referral> {\n const now = new Date();\n return adapter.create<Omit<Referral, \"id\">, Referral>({\n model: \"referral\",\n data: {\n ...input,\n completedAt: input.status === \"completed\" ? now : null,\n createdAt: now,\n },\n });\n}\n\nexport async function deleteReferral(adapter: Adapter, id: string): Promise<void> {\n await adapter.delete({ model: \"referral\", where: [{ field: \"id\", value: id }] });\n}\n\n/**\n * Number of the referrer's referrals made before `referral`: earlier ones,\n * and those made in the same millisecond with a smaller id. Gives every\n * referral of a referrer a fixed place, so concurrent sign-ups agree on\n * which of them are over `maxReferralsPerUser`.\n */\nexport async function countReferralsBefore(\n adapter: Adapter,\n referral: Pick<Referral, \"id\" | \"referrerUserId\" | \"createdAt\">,\n): Promise<number> {\n const createdAt = new Date(referral.createdAt);\n const referrer = { field: \"referrerUserId\", value: referral.referrerUserId } satisfies Where;\n const [earlier, sameTime] = await Promise.all([\n adapter.count({\n model: \"referral\",\n where: [referrer, { field: \"createdAt\", value: createdAt, operator: \"lt\" }],\n }),\n adapter.count({\n model: \"referral\",\n where: [\n referrer,\n { field: \"createdAt\", value: createdAt },\n { field: \"id\", value: referral.id, operator: \"lt\" },\n ],\n }),\n ]);\n return earlier + sameTime;\n}\n\n/**\n * Marks a pending referral completed. Returns `null` if it wasn't pending\n * (anymore), so only one caller gets to run `onReferralCompleted`.\n */\nexport async function completeReferral(adapter: Adapter, id: string): Promise<Referral | null> {\n return adapter.update<Referral>({\n model: \"referral\",\n where: [\n { field: \"id\", value: id },\n { field: \"status\", value: \"pending\" },\n ],\n update: { status: \"completed\", completedAt: new Date() },\n });\n}\n\nfunction referralFilters(input: {\n referrerUserId?: string | undefined;\n status?: ReferralStatus | undefined;\n}): Where[] {\n const where: Where[] = [];\n if (input.referrerUserId) where.push({ field: \"referrerUserId\", value: input.referrerUserId });\n if (input.status) where.push({ field: \"status\", value: input.status });\n return where;\n}\n\n/** Newest first. */\nexport async function listReferrals(\n adapter: Adapter,\n input: {\n referrerUserId?: string | undefined;\n status?: ReferralStatus | undefined;\n limit: number;\n offset: number;\n },\n): Promise<Referral[]> {\n return adapter.findMany<Referral>({\n model: \"referral\",\n where: referralFilters(input),\n limit: input.limit,\n offset: input.offset,\n sortBy: { field: \"createdAt\", direction: \"desc\" },\n });\n}\n\nexport async function countReferrals(\n adapter: Adapter,\n input: { referrerUserId?: string | undefined; status?: ReferralStatus | undefined },\n): Promise<number> {\n return adapter.count({ model: \"referral\", where: referralFilters(input) });\n}\n\n// ----- steps -----\n\nexport async function createStep(\n adapter: Adapter,\n input: { referralId: string; step: string; metadata?: string | undefined },\n): Promise<ReferralStep> {\n return adapter.create<Omit<ReferralStep, \"id\">, ReferralStep>({\n model: \"referralStep\",\n data: {\n referralId: input.referralId,\n step: input.step,\n metadata: input.metadata ?? null,\n completedAt: new Date(),\n },\n });\n}\n\nexport async function findStep(\n adapter: Adapter,\n referralId: string,\n step: string,\n): Promise<ReferralStep | null> {\n return adapter.findOne<ReferralStep>({\n model: \"referralStep\",\n where: [\n { field: \"referralId\", value: referralId },\n { field: \"step\", value: step },\n ],\n });\n}\n\n/** The completed steps of these referrals, by referral id. */\nexport async function stepsByReferral(\n adapter: Adapter,\n referralIds: string[],\n): Promise<Map<string, string[]>> {\n const result = new Map<string, string[]>(referralIds.map((id) => [id, []]));\n if (referralIds.length === 0) return result;\n const steps = await adapter.findMany<ReferralStep>({\n model: \"referralStep\",\n where: [{ field: \"referralId\", value: referralIds, operator: \"in\" }],\n sortBy: { field: \"completedAt\", direction: \"asc\" },\n });\n for (const { referralId, step } of steps) result.get(referralId)?.push(step);\n return result;\n}\n\n// ----- users -----\n\n/**\n * Whether a user can't refer anyone: deleted, or banned with better-auth's\n * `admin` plugin (until `banExpires`, if set). Without the admin plugin,\n * nobody is banned.\n */\nexport async function isReferrerBlocked(adapter: Adapter, userId: string): Promise<boolean> {\n const user = await adapter.findOne<{ banned?: boolean | null; banExpires?: Date | null }>({\n model: \"user\",\n where: [{ field: \"id\", value: userId }],\n });\n if (!user) return true;\n if (user.banned !== true) return false;\n return !user.banExpires || new Date(user.banExpires).getTime() > Date.now();\n}\n\nexport async function findUsers(\n adapter: Adapter,\n ids: string[],\n): Promise<Map<string, { id: string; name: string; email: string }>> {\n if (ids.length === 0) return new Map();\n const users = await adapter.findMany<{ id: string; name: string; email: string }>({\n model: \"user\",\n where: [{ field: \"id\", value: ids, operator: \"in\" }],\n });\n return new Map(users.map((user) => [user.id, user]));\n}\n","/**\n * Consonants and digits. No look-alikes (0/O, 1/I/L), and no vowels\n * (A, E, I, O, U, Y), so random codes can't spell words, offensive ones\n * included.\n */\nexport const CODE_ALPHABET = \"BCDFGHJKMNPQRSTVWXZ23456789\";\n\n/** What a code may look like once normalized: letters, digits, `-`, `_`. */\nexport const CODE_PATTERN = /^[A-Z0-9_-]{4,64}$/;\n\n/** A random code of `length` characters from `CODE_ALPHABET`, without modulo bias. */\nexport function generateCode(length: number): string {\n // 27 symbols: reject bytes >= 243 (9 * 27) so every symbol is equally likely.\n const limit = 256 - (256 % CODE_ALPHABET.length);\n let code = \"\";\n while (code.length < length) {\n // Web Crypto: available in Node, browsers, and edge runtimes alike.\n for (const byte of crypto.getRandomValues(new Uint8Array(length * 2))) {\n if (byte >= limit) continue;\n code += CODE_ALPHABET[byte % CODE_ALPHABET.length];\n if (code.length === length) break;\n }\n }\n return code;\n}\n\n/** Trims and uppercases a code, the way codes are stored and compared. */\nexport function normalizeCode(code: string): string {\n return code.trim().toUpperCase();\n}\n\n/** Whether `code` (normalized) is a well-formed code. */\nexport function isWellFormedCode(code: string): boolean {\n return CODE_PATTERN.test(code);\n}\n","import type { AuthContext } from \"@better-auth/core\";\nimport { recordAuditEvent } from \"@repo/plugin-utils/audit\";\nimport { runCallback } from \"@repo/plugin-utils/callbacks\";\nimport { APIError } from \"better-auth/api\";\nimport { REFERRAL_ERROR_CODES } from \"./error-codes\";\nimport type { Adapter } from \"./store\";\nimport {\n completeReferral,\n countReferrals,\n createCode,\n createStep,\n findCodeByCode,\n findCodeByUserId,\n findStep,\n isReferrerBlocked,\n replaceCode,\n stepsByReferral,\n} from \"./store\";\nimport type { Referral, ReferralCode, ResolvedReferralOptions } from \"./types\";\nimport { generateCode, isWellFormedCode, normalizeCode } from \"./utils/code\";\n\ntype Callbacks = Pick<AuthContext, \"logger\" | \"runInBackgroundOrAwait\">;\n\n/** How often a colliding generated code is replaced before giving up. */\nconst MAX_CODE_ATTEMPTS = 5;\n\n/** A new, unused, well-formed code from `generateCode` or the built-in generator. */\nasync function newCode(adapter: Adapter, options: ResolvedReferralOptions): Promise<string | null> {\n const code = normalizeCode(\n options.generateCode ? await options.generateCode() : generateCode(options.codeLength),\n );\n if (!isWellFormedCode(code)) {\n throw new Error(\n `[referral] generateCode returned ${JSON.stringify(code)}: use 4 to 64 letters, digits, \"-\", or \"_\".`,\n );\n }\n return (await findCodeByCode(adapter, code)) ? null : code;\n}\n\n/**\n * The code `userId` refers with, created on first use. Safe to call\n * concurrently: a second creation hits the unique index and returns the\n * code the first one stored.\n */\nexport async function getOrCreateCode(\n adapter: Adapter,\n options: ResolvedReferralOptions,\n userId: string,\n): Promise<ReferralCode> {\n const existing = await findCodeByUserId(adapter, userId);\n if (existing) return existing;\n\n for (let attempt = 0; attempt < MAX_CODE_ATTEMPTS; attempt++) {\n const code = await newCode(adapter, options);\n if (!code) continue;\n try {\n return await createCode(adapter, { userId, code });\n } catch (error) {\n // Either this user got a code concurrently, or the code was taken.\n const raced = await findCodeByUserId(adapter, userId);\n if (raced) return raced;\n if (!(await findCodeByCode(adapter, code))) throw error;\n }\n }\n throw APIError.from(\"INTERNAL_SERVER_ERROR\", REFERRAL_ERROR_CODES.CODE_GENERATION_FAILED);\n}\n\n/**\n * Gives `userId` a new code; the old one stops working. Referrals made\n * with the old code keep counting (they store the code they used).\n */\nexport async function regenerateCode(\n adapter: Adapter,\n options: ResolvedReferralOptions,\n userId: string,\n): Promise<ReferralCode> {\n const existing = await findCodeByUserId(adapter, userId);\n if (!existing) return getOrCreateCode(adapter, options, userId);\n\n for (let attempt = 0; attempt < MAX_CODE_ATTEMPTS; attempt++) {\n const code = await newCode(adapter, options);\n if (!code) continue;\n try {\n return (await replaceCode(adapter, existing.id, code)) ?? { ...existing, code };\n } catch (error) {\n // The code was taken concurrently (unique index): try another.\n if (!(await findCodeByCode(adapter, code))) throw error;\n }\n }\n throw APIError.from(\"INTERNAL_SERVER_ERROR\", REFERRAL_ERROR_CODES.CODE_GENERATION_FAILED);\n}\n\n/**\n * The stored code for `rawCode` if it can still refer someone: it exists,\n * its owner isn't banned, and hasn't reached `maxReferralsPerUser`. `null`\n * otherwise.\n */\nexport async function findUsableCode(\n adapter: Adapter,\n options: Pick<ResolvedReferralOptions, \"maxReferralsPerUser\">,\n rawCode: string,\n): Promise<ReferralCode | null> {\n const code = normalizeCode(rawCode);\n if (!isWellFormedCode(code)) return null;\n const stored = await findCodeByCode(adapter, code);\n if (!stored) return null;\n if (await isReferrerBlocked(adapter, stored.userId)) return null;\n if (\n options.maxReferralsPerUser !== undefined &&\n (await countReferrals(adapter, { referrerUserId: stored.userId })) >=\n options.maxReferralsPerUser\n ) {\n return null;\n }\n return stored;\n}\n\n/** Records a completed referral in the audit log, for the referrer it rewards. */\nfunction recordCompleted(referral: Referral): Promise<void> {\n return recordAuditEvent({\n type: \"referral.completed\",\n severity: \"medium\",\n userId: referral.referrerUserId,\n metadata: { referralId: referral.id, referredUserId: referral.referredUserId },\n });\n}\n\n/**\n * Marks `referral` completed once all required steps are done, and runs\n * `onReferralCompleted` exactly once.\n */\nasync function completeIfDone(\n adapter: Adapter,\n context: Callbacks,\n options: ResolvedReferralOptions,\n referral: Referral,\n): Promise<Referral> {\n if (referral.status === \"completed\") return referral;\n const done = new Set((await stepsByReferral(adapter, [referral.id])).get(referral.id));\n if (!options.steps.every((step) => done.has(step))) return referral;\n\n const completed = await completeReferral(adapter, referral.id);\n // null: another request completed it first and runs the callback.\n if (!completed) return { ...referral, status: \"completed\" };\n await recordCompleted(completed);\n const { onReferralCompleted } = options;\n if (onReferralCompleted) {\n await runCallback(context, \"referral\", \"onReferralCompleted\", () =>\n onReferralCompleted(completed),\n );\n }\n return completed;\n}\n\n/**\n * Records `step` for `referral` (once; repeating it changes nothing) and\n * completes the referral when it was the last required step.\n */\nexport async function recordStep(\n adapter: Adapter,\n context: Callbacks,\n options: ResolvedReferralOptions,\n referral: Referral,\n step: string,\n metadata?: string | undefined,\n): Promise<Referral> {\n if (!options.steps.includes(step)) {\n throw APIError.from(\"BAD_REQUEST\", REFERRAL_ERROR_CODES.UNKNOWN_REFERRAL_STEP);\n }\n\n let isNew = false;\n if (!(await findStep(adapter, referral.id, step))) {\n try {\n await createStep(adapter, { referralId: referral.id, step, metadata });\n isNew = true;\n } catch (error) {\n // Recorded concurrently (unique index on referral and step).\n if (!(await findStep(adapter, referral.id, step))) throw error;\n }\n }\n\n const { onStepCompleted } = options;\n if (isNew && onStepCompleted) {\n await runCallback(context, \"referral\", \"onStepCompleted\", () =>\n onStepCompleted({ referral, step }),\n );\n }\n return completeIfDone(adapter, context, options, referral);\n}\n\n/**\n * Runs once a referral is created: `onReferral`, then `onReferralCompleted`\n * if it was created completed (no steps required).\n */\nexport async function referralCreated(\n context: Callbacks,\n options: ResolvedReferralOptions,\n referral: Referral,\n): Promise<void> {\n await recordAuditEvent({\n type: \"referral.created\",\n userId: referral.referredUserId,\n actorUserId: referral.referredUserId,\n metadata: {\n referralId: referral.id,\n referrerUserId: referral.referrerUserId,\n code: referral.code,\n },\n });\n if (referral.status === \"completed\") await recordCompleted(referral);\n const { onReferral, onReferralCompleted } = options;\n if (onReferral) await runCallback(context, \"referral\", \"onReferral\", () => onReferral(referral));\n if (referral.status === \"completed\" && onReferralCompleted) {\n await runCallback(context, \"referral\", \"onReferralCompleted\", () =>\n onReferralCompleted(referral),\n );\n }\n}\n","import type { AuthContext, BetterAuthOptions, GenericEndpointContext } from \"@better-auth/core\";\nimport { getCurrentAdapter, queueAfterTransactionHook } from \"@better-auth/core/context\";\nimport { APIError, getOAuthState } from \"better-auth/api\";\nimport { REFERRAL_ERROR_CODES } from \"../error-codes\";\nimport { EMAIL_VERIFIED_STEP } from \"../options\";\nimport { findUsableCode, getOrCreateCode, recordStep, referralCreated } from \"../referrals\";\nimport {\n countReferralsBefore,\n createReferral,\n deleteReferral,\n findReferralByReferredUserId,\n} from \"../store\";\nimport type { Referral, ResolvedReferralOptions } from \"../types\";\n\nexport const REFERRAL_CODE_HEADER = \"x-referral-code\";\n/** Longest code read from a request; longer values are ignored. */\nconst MAX_CODE_INPUT_LENGTH = 128;\n\n/**\n * The referral code sent with the request that creates the user: the\n * `referralCode` field of the sign-up body, the `x-referral-code` header,\n * or `additionalData.referralCode` of a social sign-in (read on the\n * callback).\n */\nasync function readReferralCode(\n context: GenericEndpointContext | null | undefined,\n): Promise<string | undefined> {\n const valid = (value: unknown): value is string =>\n typeof value === \"string\" && value.trim() !== \"\" && value.length <= MAX_CODE_INPUT_LENGTH;\n\n const body = context?.body as Record<string, unknown> | undefined;\n if (valid(body?.referralCode)) return body.referralCode;\n\n const header = context?.headers?.get(REFERRAL_CODE_HEADER);\n if (valid(header)) return header;\n\n try {\n const state = await getOAuthState<{ referralCode?: unknown }>();\n if (valid(state?.referralCode)) return state.referralCode;\n } catch {\n // Not inside an OAuth flow.\n }\n return undefined;\n}\n\n/**\n * Attributes new users to the code they signed up with, whichever way\n * they sign up, and records the `email-verified` step. Failures after the\n * user exists are logged, never returned: a referral problem must not\n * fail a sign-up that already happened.\n */\nexport function createDatabaseHooks(\n authContext: AuthContext,\n options: ResolvedReferralOptions,\n): BetterAuthOptions[\"databaseHooks\"] {\n const contextFor = (context: GenericEndpointContext | null | undefined) =>\n context?.context ?? authContext;\n const tracksEmailVerified = options.steps.includes(EMAIL_VERIFIED_STEP);\n /** Requests whose user update sets `emailVerified: true` (see `update.before`). */\n const verifying = new WeakSet<object>();\n\n const attribute = async (\n user: { id: string; email: string; emailVerified?: boolean },\n context: GenericEndpointContext | null | undefined,\n ) => {\n const ctx = contextFor(context);\n const adapter = await getCurrentAdapter(ctx.adapter);\n\n if (options.createCodeOnSignUp) await getOrCreateCode(adapter, options, user.id);\n\n const rawCode = await readReferralCode(context);\n if (!rawCode) return;\n const code = await findUsableCode(adapter, options, rawCode);\n if (!code || code.userId === user.id) return;\n\n if (options.allowReferral) {\n let allowed = false;\n try {\n allowed = await options.allowReferral({\n referrerUserId: code.userId,\n referredUser: { id: user.id, email: user.email },\n code: code.code,\n context: context ?? null,\n });\n } catch (error) {\n ctx.logger.error(\"[referral] allowReferral threw an error\", error);\n }\n if (!allowed) return;\n }\n\n let referral: Referral;\n try {\n referral = await createReferral(adapter, {\n referrerUserId: code.userId,\n referredUserId: user.id,\n code: code.code,\n status: options.steps.length === 0 ? \"completed\" : \"pending\",\n });\n } catch (error) {\n // Already referred (unique index): keep the first referral.\n if (await findReferralByReferredUserId(adapter, user.id)) return;\n throw error;\n }\n\n // Settled once the user's transaction (if any) is committed:\n // concurrent sign-ups in other transactions are visible by then, so\n // `maxReferralsPerUser` holds, and nothing is announced for a sign-up\n // that was rolled back.\n const settle = async () => {\n try {\n const max = options.maxReferralsPerUser;\n if (max !== undefined && (await countReferralsBefore(ctx.adapter, referral)) >= max) {\n // Signed up while the last free places were taken: no referral.\n await deleteReferral(ctx.adapter, referral.id);\n return;\n }\n await referralCreated(ctx, options, referral);\n if (tracksEmailVerified && user.emailVerified) {\n await recordStep(ctx.adapter, ctx, options, referral, EMAIL_VERIFIED_STEP);\n }\n } catch (error) {\n ctx.logger.error(\"[referral] failed to record the referral\", error);\n }\n };\n if (adapter === ctx.adapter) await settle();\n else await queueAfterTransactionHook(settle);\n };\n\n return {\n user: {\n create: {\n before: async (_user, context) => {\n if (options.invalidCode !== \"reject\") return;\n const rawCode = await readReferralCode(context);\n if (rawCode === undefined) return;\n const adapter = await getCurrentAdapter(contextFor(context).adapter);\n if (!(await findUsableCode(adapter, options, rawCode))) {\n throw APIError.from(\"BAD_REQUEST\", REFERRAL_ERROR_CODES.INVALID_REFERRAL_CODE);\n }\n },\n after: async (user, context) => {\n try {\n await attribute(user, context);\n } catch (error) {\n contextFor(context).logger.error(\"[referral] failed to record the referral\", error);\n }\n },\n },\n update: {\n before: async (data, context) => {\n // Note the updates that verify an email, so `after` looks for a\n // referral only then, not on every update of a verified user.\n if (tracksEmailVerified && context && data.emailVerified === true) {\n verifying.add(context);\n }\n },\n after: async (user, context) => {\n if (!tracksEmailVerified || user.emailVerified !== true) return;\n // Requests: only updates that set emailVerified. Server code\n // outside a request: always (rare, and its data isn't tracked).\n if (context && !verifying.delete(context)) return;\n const ctx = contextFor(context);\n try {\n const adapter = await getCurrentAdapter(ctx.adapter);\n const referral = await findReferralByReferredUserId(adapter, user.id);\n if (referral) await recordStep(adapter, ctx, options, referral, EMAIL_VERIFIED_STEP);\n } catch (error) {\n ctx.logger.error(\"[referral] failed to record the email-verified step\", error);\n }\n },\n },\n },\n };\n}\n","import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { createAuthEndpoint, sessionMiddleware } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport { findUsableCode, getOrCreateCode, regenerateCode } from \"../referrals\";\nimport { findCodeByUserId } from \"../store\";\nimport type { ResolvedReferralOptions } from \"../types\";\n\nexport function createCodeEndpoints(options: ResolvedReferralOptions) {\n return {\n getReferralCode: createAuthEndpoint(\n \"/referral/get-code\",\n {\n method: \"GET\",\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Get the signed-in user's referral code, or null if they have none\",\n },\n },\n },\n async (ctx) => {\n const code = await findCodeByUserId(ctx.context.adapter, ctx.context.session.user.id);\n return ctx.json({ code: code?.code ?? null });\n },\n ),\n createReferralCode: createAuthEndpoint(\n \"/referral/create-code\",\n {\n method: \"POST\",\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description:\n \"Create the signed-in user's referral code. Returns the existing code if they already have one\",\n },\n },\n },\n async (ctx) => {\n const code = await getOrCreateCode(\n ctx.context.adapter,\n options,\n ctx.context.session.user.id,\n );\n return ctx.json({ code: code.code });\n },\n ),\n regenerateReferralCode: createAuthEndpoint(\n \"/referral/regenerate-code\",\n {\n method: \"POST\",\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description:\n \"Replace the signed-in user's referral code with a new one. The old code and its share links stop working; referrals made with it keep counting\",\n },\n },\n },\n async (ctx) => {\n const code = await regenerateCode(\n ctx.context.adapter,\n options,\n ctx.context.session.user.id,\n );\n return ctx.json({ code: code.code });\n },\n ),\n checkReferralCode: createAuthEndpoint(\n \"/referral/check-code\",\n {\n method: \"GET\",\n query: z.object({\n code: z.string().min(1).max(128).meta({ description: \"The referral code\" }),\n }),\n metadata: {\n openapi: {\n description:\n \"Check whether a referral code can be used at sign-up. Doesn't reveal whose code it is\",\n },\n },\n },\n async (ctx) => {\n const code = await findUsableCode(ctx.context.adapter, options, ctx.query.code);\n return ctx.json({ valid: code !== null });\n },\n ),\n } satisfies BetterAuthPlugin[\"endpoints\"];\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","/** `ada@example.com` → `a***@example.com`. Emails without `@` become `***`. */\nexport function maskEmail(email: string): string {\n const at = email.lastIndexOf(\"@\");\n if (at < 1) return \"***\";\n return `${email[0]}***${email.slice(at)}`;\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 { REFERRAL_ERROR_CODES } from \"../error-codes\";\nimport { MAX_STEP_LENGTH } from \"../options\";\nimport { recordStep } from \"../referrals\";\nimport {\n countReferrals,\n findReferralByReferredUserId,\n findUsers,\n listReferrals,\n stepsByReferral,\n} from \"../store\";\nimport type { Referral, ReferralWithSteps, ResolvedReferralOptions } from \"../types\";\nimport { maskEmail } from \"../utils/mask\";\n\n/** Most referrals one list request returns. */\nexport const MAX_PAGE_SIZE = 100;\n/** Longest JSON-encoded step `metadata`. */\nexport const MAX_METADATA_LENGTH = 2000;\n\nconst pageSchema = paginationQuery({ defaultLimit: 20, maxLimit: MAX_PAGE_SIZE });\nconst statusSchema = z\n .enum([\"pending\", \"completed\"])\n .meta({ description: \"Only referrals with this status\" });\n\nasync function withSteps(\n adapter: Parameters<typeof stepsByReferral>[0],\n referrals: Referral[],\n): Promise<ReferralWithSteps[]> {\n const steps = await stepsByReferral(\n adapter,\n referrals.map((referral) => referral.id),\n );\n return referrals.map((referral) => ({ ...referral, steps: steps.get(referral.id) ?? [] }));\n}\n\nexport function createReferralEndpoints(options: ResolvedReferralOptions) {\n return {\n listReferrals: createAuthEndpoint(\n \"/referral/list-referrals\",\n {\n method: \"GET\",\n use: [sessionMiddleware],\n query: z.optional(z.object({ status: statusSchema.optional(), ...pageSchema })),\n metadata: {\n openapi: {\n description:\n \"List the users the signed-in user referred, newest first, with their steps\",\n },\n },\n },\n async (ctx) => {\n const { adapter } = ctx.context;\n const referrerUserId = ctx.context.session.user.id;\n const status = ctx.query?.status;\n const [referrals, total, completed] = await Promise.all([\n listReferrals(adapter, {\n referrerUserId,\n status,\n limit: ctx.query?.limit ?? 20,\n offset: ctx.query?.offset ?? 0,\n }),\n countReferrals(adapter, { referrerUserId, status }),\n countReferrals(adapter, { referrerUserId, status: \"completed\" }),\n ]);\n const [detailed, users] = await Promise.all([\n withSteps(adapter, referrals),\n findUsers(\n adapter,\n referrals.map((referral) => referral.referredUserId),\n ),\n ]);\n\n return ctx.json({\n // Only what a referrer needs: who (name, masked email) and how far.\n referrals: detailed.map((referral) => {\n const user = users.get(referral.referredUserId);\n return {\n id: referral.id,\n status: referral.status,\n steps: referral.steps,\n createdAt: referral.createdAt,\n completedAt: referral.completedAt ?? null,\n referredUser: {\n name: options.hideNames ? null : (user?.name ?? null),\n email: user ? (options.maskEmails ? maskEmail(user.email) : user.email) : null,\n },\n };\n }),\n total,\n completed,\n });\n },\n ),\n listAllReferrals: createAuthEndpoint(\n \"/referral/list-all-referrals\",\n {\n method: \"GET\",\n use: [sessionMiddleware],\n query: z.optional(\n z.object({\n referrerUserId: z\n .string()\n .min(1)\n .optional()\n .meta({ description: \"Only referrals by this user\" }),\n status: statusSchema.optional(),\n ...pageSchema,\n }),\n ),\n metadata: {\n openapi: { description: \"List all referrals, newest first. Admins only\" },\n },\n },\n async (ctx) => {\n if (!isAdmin(ctx.context.session.user, options)) {\n throw APIError.from(\n \"FORBIDDEN\",\n REFERRAL_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_REFERRALS,\n );\n }\n const { adapter } = ctx.context;\n const filters = { referrerUserId: ctx.query?.referrerUserId, status: ctx.query?.status };\n const [referrals, total] = await Promise.all([\n listReferrals(adapter, {\n ...filters,\n limit: ctx.query?.limit ?? 20,\n offset: ctx.query?.offset ?? 0,\n }),\n countReferrals(adapter, filters),\n ]);\n return ctx.json({ referrals: await withSteps(adapter, referrals), total });\n },\n ),\n /**\n * Server only (no path, so not reachable over HTTP): your backend calls\n * `auth.api.completeReferralStep` when a referred user does something\n * that counts, e.g. their first purchase.\n */\n completeReferralStep: createAuthEndpoint(\n {\n method: \"POST\",\n body: z.object({\n userId: z.string().min(1).meta({ description: \"The referred user\" }),\n step: z\n .string()\n .min(1)\n .max(MAX_STEP_LENGTH)\n .meta({ description: \"A step from the plugin's `steps` option\" }),\n metadata: z\n .record(z.string(), z.unknown())\n .optional()\n .meta({ description: \"Extra data to keep with the step, e.g. an order id\" }),\n }),\n metadata: {\n openapi: {\n description:\n \"Record a step of the user's referral. Does nothing if the user wasn't referred or the step is already done\",\n },\n },\n },\n async (ctx) => {\n const { userId, step } = ctx.body;\n const metadata = ctx.body.metadata ? JSON.stringify(ctx.body.metadata) : undefined;\n if (metadata && metadata.length > MAX_METADATA_LENGTH) {\n throw APIError.from(\"BAD_REQUEST\", REFERRAL_ERROR_CODES.METADATA_TOO_LARGE);\n }\n if (!options.steps.includes(step)) {\n throw APIError.from(\"BAD_REQUEST\", REFERRAL_ERROR_CODES.UNKNOWN_REFERRAL_STEP);\n }\n const { adapter } = ctx.context;\n const referral = await findReferralByReferredUserId(adapter, userId);\n // Most users weren't referred: that's not an error.\n if (!referral) return ctx.json({ referral: null });\n\n const updated = await recordStep(adapter, ctx.context, options, referral, step, metadata);\n const [detailed] = await withSteps(adapter, [updated]);\n return ctx.json({ referral: detailed ?? null });\n },\n ),\n } satisfies BetterAuthPlugin[\"endpoints\"];\n}\n","import type { BetterAuthPluginDBSchema } from \"@better-auth/core/db\";\n\nexport const referralSchema = {\n /** One code per user. */\n referralCode: {\n fields: {\n userId: {\n type: \"string\",\n required: true,\n references: { model: \"user\", field: \"id\", onDelete: \"cascade\" },\n },\n /** Uppercase. */\n code: {\n type: \"string\",\n required: true,\n },\n createdAt: {\n type: \"date\",\n required: true,\n },\n },\n indexes: [\n { fields: [\"userId\"], unique: true },\n { fields: [\"code\"], unique: true },\n ],\n },\n /** Who referred whom. A user is referred at most once. */\n referral: {\n fields: {\n referrerUserId: {\n type: \"string\",\n required: true,\n index: true,\n references: { model: \"user\", field: \"id\", onDelete: \"cascade\" },\n },\n referredUserId: {\n type: \"string\",\n required: true,\n references: { model: \"user\", field: \"id\", onDelete: \"cascade\" },\n },\n /** The code used at sign-up, kept even if the code changes. */\n code: {\n type: \"string\",\n required: true,\n },\n /** \"pending\" | \"completed\" */\n status: {\n type: \"string\",\n required: true,\n index: true,\n },\n completedAt: {\n type: \"date\",\n required: false,\n },\n createdAt: {\n type: \"date\",\n required: true,\n },\n },\n indexes: [{ fields: [\"referredUserId\"], unique: true }],\n },\n /** Steps a referred user has completed. Each step counts once. */\n referralStep: {\n fields: {\n referralId: {\n type: \"string\",\n required: true,\n references: { model: \"referral\", field: \"id\", onDelete: \"cascade\" },\n },\n step: {\n type: \"string\",\n required: true,\n },\n /** JSON-encoded. */\n metadata: {\n type: \"string\",\n required: false,\n },\n completedAt: {\n type: \"date\",\n required: true,\n },\n },\n indexes: [{ fields: [\"referralId\", \"step\"], unique: true }],\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport type ReferralSchema = typeof referralSchema;\n","import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { mergeSchema } from \"better-auth/db\";\nimport { version } from \"../package.json\";\nimport { REFERRAL_ERROR_CODES } from \"./error-codes\";\nimport { createDatabaseHooks } from \"./hooks/database\";\nimport { applyAuthDefaults, resolveOptions } from \"./options\";\nimport { createCodeEndpoints } from \"./routes/codes\";\nimport { createReferralEndpoints } from \"./routes/referrals\";\nimport { referralSchema } from \"./schema\";\nimport type { ReferralOptions } from \"./types\";\n\nexport { REFERRAL_ERROR_CODES, type ReferralErrorCode } from \"./error-codes\";\nexport { EMAIL_VERIFIED_STEP } from \"./options\";\nexport type {\n Referral,\n ReferralCandidate,\n ReferralCode,\n ReferralOptions,\n ReferralStatus,\n ReferralStep,\n ReferralWithSteps,\n} from \"./types\";\n\n/**\n * Gives users referral codes, attributes new users to the code they signed\n * up with (any sign-up method), and tracks steps a referral must complete\n * before it counts, e.g. a verified email or a first purchase.\n *\n * @example\n * ```ts\n * import { betterAuth } from \"better-auth\";\n * import { referral } from \"@checodo/better-auth-referral\";\n *\n * export const auth = betterAuth({\n * plugins: [\n * referral({\n * steps: [\"email-verified\", \"first-purchase\"],\n * onReferralCompleted: async ({ referrerUserId }) => {\n * await grantCredit(referrerUserId);\n * },\n * }),\n * ],\n * });\n * ```\n */\nexport const referral = (options: ReferralOptions = {}) => {\n const resolved = resolveOptions(options);\n\n return {\n id: \"referral\",\n version,\n init(ctx) {\n applyAuthDefaults(resolved, ctx);\n return { options: { databaseHooks: createDatabaseHooks(ctx, resolved) } };\n },\n // mergeSchema changes the object it gets, so each instance gets a copy.\n schema: mergeSchema(structuredClone(referralSchema), options.schema),\n endpoints: {\n ...createCodeEndpoints(resolved),\n ...createReferralEndpoints(resolved),\n },\n // Anyone can call check-code, so codes can't be guessed quickly.\n // Apps can change this with better-auth's `rateLimit.customRules`.\n rateLimit: [{ pathMatcher: (path) => path === \"/referral/check-code\", window: 60, max: 5 }],\n $ERROR_CODES: REFERRAL_ERROR_CODES,\n options,\n } satisfies BetterAuthPlugin;\n};\n\nexport type ReferralPlugin = ReturnType<typeof referral>;\n\n/** Lets `auth.$context.getPlugin(\"referral\")` infer this plugin's exact type. */\ndeclare module \"@better-auth/core\" {\n interface BetterAuthPluginRegistry<AuthOptions, Options> {\n referral: {\n creator: typeof referral;\n };\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "@checodo/better-auth-referral",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Referral codes, sign-up attribution, and reward steps 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/referral",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/checodo/better-auth-plugins.git",
16
+ "directory": "packages/referral"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/checodo/better-auth-plugins/issues"
20
+ },
21
+ "keywords": [
22
+ "better-auth",
23
+ "better-auth-plugin",
24
+ "referral",
25
+ "referrals",
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
+ "@repo/plugin-utils": "0.0.0",
57
+ "@repo/tooling": "0.0.0",
58
+ "@types/node": "^24.19.2",
59
+ "better-auth": "^1.7.20",
60
+ "tsdown": "^0.23.7",
61
+ "typescript": "^5.9.3",
62
+ "vitest": "^5.1.0"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "scripts": {
68
+ "build": "tsdown",
69
+ "dev": "tsdown --watch --no-clean",
70
+ "test": "vitest run",
71
+ "test:watch": "vitest",
72
+ "typecheck": "tsc --noEmit",
73
+ "clean": "rm -rf dist .turbo",
74
+ "lint:package": "publint && attw --pack . --profile esm-only"
75
+ }
6
76
  }