@guilhermejansen/better-auth-waitlist 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.
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/dist/client.d.mts +89 -0
- package/dist/client.mjs +66 -0
- package/dist/client.mjs.map +1 -0
- package/dist/error-codes-DCX2o7NB.d.mts +84 -0
- package/dist/index.d.mts +543 -0
- package/dist/index.mjs +641 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/error-codes.ts","../src/routes/admin.ts","../src/routes/public.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["// NOTE: Error code const must be all capital of string (ref https://github.com/better-auth/better-auth/issues/4386)\nimport { defineErrorCodes } from \"@better-auth/core/utils\";\n\nexport const WAITLIST_ERROR_CODES = defineErrorCodes({\n\tEMAIL_ALREADY_IN_WAITLIST: \"This email is already on the waitlist\",\n\tWAITLIST_ENTRY_NOT_FOUND: \"Waitlist entry not found\",\n\tNOT_APPROVED: \"You must be approved from the waitlist to register\",\n\tINVALID_INVITE_CODE: \"Invalid or expired invite code\",\n\tINVITE_CODE_REQUIRED: \"An invite code is required to register\",\n\tALREADY_REGISTERED:\n\t\t\"This waitlist entry has already been used for registration\",\n\tWAITLIST_FULL: \"The waitlist is currently full\",\n\tUNAUTHORIZED_ADMIN_ACTION: \"You are not authorized to perform this action\",\n});\n","import {\n\tcreateAuthEndpoint,\n\tcreateAuthMiddleware,\n} from \"@better-auth/core/api\";\nimport { APIError, sessionMiddleware } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport { WAITLIST_ERROR_CODES } from \"../error-codes\";\nimport type { WaitlistEntry, WaitlistOptions } from \"../types\";\n\nconst adminMiddleware = (options: WaitlistOptions) =>\n\tcreateAuthMiddleware(\n\t\t{\n\t\t\tuse: [sessionMiddleware],\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst adminRoles = options.adminRoles ?? [\"admin\"];\n\t\t\tconst userRole = (ctx.context.session.user as Record<string, unknown>)\n\t\t\t\t.role as string | undefined;\n\t\t\tif (!userRole || !adminRoles.includes(userRole)) {\n\t\t\t\tthrow new APIError(\"FORBIDDEN\", {\n\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.UNAUTHORIZED_ADMIN_ACTION,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsession: ctx.context.session,\n\t\t\t};\n\t\t},\n\t);\n\nexport const approveEntry = (options: WaitlistOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/waitlist/approve\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: z.object({\n\t\t\t\temail: z.email(),\n\t\t\t}),\n\t\t\tuse: [adminMiddleware(options)],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Approve a waitlist entry\",\n\t\t\t\t\tresponses: { 200: { description: \"Entry approved\" } },\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst normalizedEmail = ctx.body.email.toLowerCase();\n\t\t\tconst entry = (await ctx.context.adapter.findOne({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t})) as Record<string, unknown> | null;\n\t\t\tif (!entry) {\n\t\t\t\tthrow new APIError(\"NOT_FOUND\", {\n\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.WAITLIST_ENTRY_NOT_FOUND,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (entry.status === \"registered\") {\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.ALREADY_REGISTERED,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst inviteCode = crypto.randomUUID();\n\t\t\tconst expSeconds = options.inviteCodeExpiration ?? 172800;\n\t\t\tconst inviteExpiresAt = new Date(Date.now() + expSeconds * 1000);\n\t\t\tconst now = new Date();\n\n\t\t\tconst updated = (await ctx.context.adapter.update({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t\tupdate: {\n\t\t\t\t\tstatus: \"approved\",\n\t\t\t\t\tinviteCode,\n\t\t\t\t\tinviteExpiresAt,\n\t\t\t\t\tapprovedAt: now,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t},\n\t\t\t})) as Record<string, unknown>;\n\n\t\t\tconst updatedEntry = { ...entry, ...updated } as unknown as WaitlistEntry;\n\n\t\t\tif (options.sendInviteEmail) {\n\t\t\t\tawait options.sendInviteEmail({\n\t\t\t\t\temail: normalizedEmail,\n\t\t\t\t\tinviteCode,\n\t\t\t\t\texpiresAt: inviteExpiresAt,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (options.onApproved) {\n\t\t\t\tawait options.onApproved(updatedEntry);\n\t\t\t}\n\n\t\t\treturn ctx.json(updatedEntry);\n\t\t},\n\t);\n\nexport const rejectEntry = (options: WaitlistOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/waitlist/reject\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: z.object({\n\t\t\t\temail: z.email(),\n\t\t\t\treason: z.string().optional(),\n\t\t\t}),\n\t\t\tuse: [adminMiddleware(options)],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Reject a waitlist entry\",\n\t\t\t\t\tresponses: { 200: { description: \"Entry rejected\" } },\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst normalizedEmail = ctx.body.email.toLowerCase();\n\t\t\tconst entry = (await ctx.context.adapter.findOne({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t})) as Record<string, unknown> | null;\n\t\t\tif (!entry) {\n\t\t\t\tthrow new APIError(\"NOT_FOUND\", {\n\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.WAITLIST_ENTRY_NOT_FOUND,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst now = new Date();\n\t\t\tconst updated = (await ctx.context.adapter.update({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t\tupdate: {\n\t\t\t\t\tstatus: \"rejected\",\n\t\t\t\t\trejectedAt: now,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t},\n\t\t\t})) as Record<string, unknown>;\n\n\t\t\tconst updatedEntry = { ...entry, ...updated } as unknown as WaitlistEntry;\n\t\t\tif (options.onRejected) {\n\t\t\t\tawait options.onRejected(updatedEntry);\n\t\t\t}\n\n\t\t\treturn ctx.json(updatedEntry);\n\t\t},\n\t);\n\nexport const bulkApprove = (options: WaitlistOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/waitlist/bulk-approve\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: z.object({\n\t\t\t\temails: z.array(z.email()).optional(),\n\t\t\t\tcount: z.number().int().positive().optional(),\n\t\t\t}),\n\t\t\tuse: [adminMiddleware(options)],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Bulk approve waitlist entries\",\n\t\t\t\t\tresponses: { 200: { description: \"Entries approved\" } },\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { emails, count } = ctx.body;\n\t\t\tconst approved: WaitlistEntry[] = [];\n\t\t\tconst inviteExpSeconds = options.inviteCodeExpiration ?? 172800;\n\n\t\t\tif (emails && emails.length > 0) {\n\t\t\t\tfor (const email of emails) {\n\t\t\t\t\tconst normalizedEmail = email.toLowerCase();\n\t\t\t\t\tconst entry = (await ctx.context.adapter.findOne({\n\t\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t\t\t})) as Record<string, unknown> | null;\n\t\t\t\t\tif (!entry || entry.status !== \"pending\") continue;\n\n\t\t\t\t\tconst inviteCode = crypto.randomUUID();\n\t\t\t\t\tconst inviteExpiresAt = new Date(\n\t\t\t\t\t\tDate.now() + inviteExpSeconds * 1000,\n\t\t\t\t\t);\n\t\t\t\t\tconst now = new Date();\n\n\t\t\t\t\tawait ctx.context.adapter.update({\n\t\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t\t\t\tupdate: {\n\t\t\t\t\t\t\tstatus: \"approved\",\n\t\t\t\t\t\t\tinviteCode,\n\t\t\t\t\t\t\tinviteExpiresAt,\n\t\t\t\t\t\t\tapprovedAt: now,\n\t\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tconst updatedEntry = {\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tstatus: \"approved\",\n\t\t\t\t\t\tinviteCode,\n\t\t\t\t\t\tinviteExpiresAt,\n\t\t\t\t\t\tapprovedAt: now,\n\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t} as unknown as WaitlistEntry;\n\t\t\t\t\tapproved.push(updatedEntry);\n\n\t\t\t\t\tif (options.sendInviteEmail) {\n\t\t\t\t\t\tawait options.sendInviteEmail({\n\t\t\t\t\t\t\temail: normalizedEmail,\n\t\t\t\t\t\t\tinviteCode,\n\t\t\t\t\t\t\texpiresAt: inviteExpiresAt,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tif (options.onApproved) {\n\t\t\t\t\t\tawait options.onApproved(updatedEntry);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (count) {\n\t\t\t\tconst pending = (await ctx.context.adapter.findMany({\n\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\twhere: [{ field: \"status\", value: \"pending\" }],\n\t\t\t\t\tsortBy: { field: \"position\", direction: \"asc\" },\n\t\t\t\t\tlimit: count,\n\t\t\t\t})) as Record<string, unknown>[];\n\n\t\t\t\tfor (const entry of pending) {\n\t\t\t\t\tconst inviteCode = crypto.randomUUID();\n\t\t\t\t\tconst inviteExpiresAt = new Date(\n\t\t\t\t\t\tDate.now() + inviteExpSeconds * 1000,\n\t\t\t\t\t);\n\t\t\t\t\tconst now = new Date();\n\n\t\t\t\t\tawait ctx.context.adapter.update({\n\t\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\t\twhere: [{ field: \"id\", value: entry.id as string }],\n\t\t\t\t\t\tupdate: {\n\t\t\t\t\t\t\tstatus: \"approved\",\n\t\t\t\t\t\t\tinviteCode,\n\t\t\t\t\t\t\tinviteExpiresAt,\n\t\t\t\t\t\t\tapprovedAt: now,\n\t\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tconst updatedEntry = {\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tstatus: \"approved\",\n\t\t\t\t\t\tinviteCode,\n\t\t\t\t\t\tinviteExpiresAt,\n\t\t\t\t\t\tapprovedAt: now,\n\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t} as unknown as WaitlistEntry;\n\t\t\t\t\tapproved.push(updatedEntry);\n\n\t\t\t\t\tif (options.sendInviteEmail) {\n\t\t\t\t\t\tawait options.sendInviteEmail({\n\t\t\t\t\t\t\temail: entry.email as string,\n\t\t\t\t\t\t\tinviteCode,\n\t\t\t\t\t\t\texpiresAt: inviteExpiresAt,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tif (options.onApproved) {\n\t\t\t\t\t\tawait options.onApproved(updatedEntry);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn ctx.json({ approved: approved.length, entries: approved });\n\t\t},\n\t);\n\nexport const listWaitlist = (options: WaitlistOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/waitlist/list\",\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tquery: z.object({\n\t\t\t\tstatus: z\n\t\t\t\t\t.enum([\"pending\", \"approved\", \"rejected\", \"registered\"])\n\t\t\t\t\t.optional(),\n\t\t\t\tpage: z.coerce.number().int().positive().optional(),\n\t\t\t\tlimit: z.coerce.number().int().positive().max(100).optional(),\n\t\t\t\tsortBy: z.enum([\"createdAt\", \"position\", \"email\", \"status\"]).optional(),\n\t\t\t\tsortDirection: z.enum([\"asc\", \"desc\"]).optional(),\n\t\t\t}),\n\t\t\tuse: [adminMiddleware(options)],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"List waitlist entries with pagination\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: { description: \"Paginated waitlist entries\" },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst page = ctx.query.page ?? 1;\n\t\t\tconst limit = ctx.query.limit ?? 20;\n\t\t\tconst offset = (page - 1) * limit;\n\n\t\t\tconst where = ctx.query.status\n\t\t\t\t? [{ field: \"status\" as const, value: ctx.query.status }]\n\t\t\t\t: undefined;\n\n\t\t\tconst entries = await ctx.context.adapter.findMany({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere,\n\t\t\t\tsortBy: {\n\t\t\t\t\tfield: ctx.query.sortBy ?? \"createdAt\",\n\t\t\t\t\tdirection: ctx.query.sortDirection ?? \"desc\",\n\t\t\t\t},\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t});\n\n\t\t\tconst total = await ctx.context.adapter.count({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere,\n\t\t\t});\n\n\t\t\treturn ctx.json({\n\t\t\t\tentries,\n\t\t\t\ttotal,\n\t\t\t\tpage,\n\t\t\t\ttotalPages: Math.ceil(total / limit),\n\t\t\t});\n\t\t},\n\t);\n\nexport const getWaitlistStats = (options: WaitlistOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/waitlist/stats\",\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tuse: [adminMiddleware(options)],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Get waitlist statistics\",\n\t\t\t\t\tresponses: { 200: { description: \"Waitlist statistics\" } },\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst total = await ctx.context.adapter.count({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t});\n\t\t\tconst pending = await ctx.context.adapter.count({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"status\", value: \"pending\" }],\n\t\t\t});\n\t\t\tconst approved = await ctx.context.adapter.count({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"status\", value: \"approved\" }],\n\t\t\t});\n\t\t\tconst rejected = await ctx.context.adapter.count({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"status\", value: \"rejected\" }],\n\t\t\t});\n\t\t\tconst registered = await ctx.context.adapter.count({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"status\", value: \"registered\" }],\n\t\t\t});\n\n\t\t\treturn ctx.json({ total, pending, approved, rejected, registered });\n\t\t},\n\t);\n","import { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { APIError } from \"better-auth/api\";\nimport * as z from \"zod\";\nimport { WAITLIST_ERROR_CODES } from \"../error-codes\";\nimport type { WaitlistEntry, WaitlistOptions } from \"../types\";\n\nexport const joinWaitlist = (options: WaitlistOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/waitlist/join\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: z.object({\n\t\t\t\temail: z.email(),\n\t\t\t\treferredBy: z.string().optional(),\n\t\t\t\tmetadata: z.record(z.string(), z.unknown()).optional(),\n\t\t\t}),\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Join the waitlist\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: { description: \"Successfully joined the waitlist\" },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { email, referredBy, metadata } = ctx.body;\n\t\t\tconst normalizedEmail = email.toLowerCase();\n\n\t\t\t// Check for duplicate\n\t\t\tconst existing = await ctx.context.adapter.findOne({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t});\n\t\t\tif (existing) {\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.EMAIL_ALREADY_IN_WAITLIST,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Check max size\n\t\t\tif (options.maxWaitlistSize) {\n\t\t\t\tconst count = await ctx.context.adapter.count({\n\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t});\n\t\t\t\tif (count >= options.maxWaitlistSize) {\n\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.WAITLIST_FULL,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Calculate position\n\t\t\tconst totalCount = await ctx.context.adapter.count({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t});\n\n\t\t\t// Check auto-approve\n\t\t\tlet status = \"pending\";\n\t\t\tlet inviteCode: string | null = null;\n\t\t\tlet inviteExpiresAt: Date | null = null;\n\t\t\tlet approvedAt: Date | null = null;\n\n\t\t\tif (options.autoApprove) {\n\t\t\t\tconst shouldApprove =\n\t\t\t\t\ttypeof options.autoApprove === \"function\"\n\t\t\t\t\t\t? await options.autoApprove(normalizedEmail)\n\t\t\t\t\t\t: true;\n\t\t\t\tif (shouldApprove) {\n\t\t\t\t\tstatus = \"approved\";\n\t\t\t\t\tinviteCode = crypto.randomUUID();\n\t\t\t\t\tconst expSeconds = options.inviteCodeExpiration ?? 172800;\n\t\t\t\t\tinviteExpiresAt = new Date(Date.now() + expSeconds * 1000);\n\t\t\t\t\tapprovedAt = new Date();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst now = new Date();\n\t\t\tconst entry = (await ctx.context.adapter.create({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\tdata: {\n\t\t\t\t\temail: normalizedEmail,\n\t\t\t\t\tstatus,\n\t\t\t\t\tinviteCode,\n\t\t\t\t\tinviteExpiresAt,\n\t\t\t\t\tposition: totalCount + 1,\n\t\t\t\t\treferredBy: referredBy ?? null,\n\t\t\t\t\tmetadata: metadata ? JSON.stringify(metadata) : null,\n\t\t\t\t\tapprovedAt,\n\t\t\t\t\trejectedAt: null,\n\t\t\t\t\tregisteredAt: null,\n\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t},\n\t\t\t})) as Record<string, unknown>;\n\n\t\t\t// Callbacks\n\t\t\tif (options.onJoinWaitlist) {\n\t\t\t\tawait options.onJoinWaitlist(entry as unknown as WaitlistEntry);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tstatus === \"approved\" &&\n\t\t\t\toptions.sendInviteEmail &&\n\t\t\t\tinviteCode &&\n\t\t\t\tinviteExpiresAt\n\t\t\t) {\n\t\t\t\tawait options.sendInviteEmail({\n\t\t\t\t\temail: normalizedEmail,\n\t\t\t\t\tinviteCode,\n\t\t\t\t\texpiresAt: inviteExpiresAt,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (status === \"approved\" && options.onApproved) {\n\t\t\t\tawait options.onApproved(entry as unknown as WaitlistEntry);\n\t\t\t}\n\n\t\t\treturn ctx.json({\n\t\t\t\tid: entry.id,\n\t\t\t\temail: entry.email,\n\t\t\t\tstatus: entry.status,\n\t\t\t\tposition: entry.position,\n\t\t\t\tcreatedAt: entry.createdAt,\n\t\t\t});\n\t\t},\n\t);\n\nexport const getWaitlistStatus = (_options: WaitlistOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/waitlist/status\",\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tquery: z.object({\n\t\t\t\temail: z.email(),\n\t\t\t}),\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Check waitlist status for an email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: { description: \"Waitlist status\" },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst normalizedEmail = ctx.query.email.toLowerCase();\n\t\t\tconst entry = (await ctx.context.adapter.findOne({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t})) as Record<string, unknown> | null;\n\t\t\tif (!entry) {\n\t\t\t\tthrow new APIError(\"NOT_FOUND\", {\n\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.WAITLIST_ENTRY_NOT_FOUND,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn ctx.json({\n\t\t\t\tstatus: entry.status as string,\n\t\t\t\tposition: entry.position as number,\n\t\t\t});\n\t\t},\n\t);\n\nexport const verifyInviteCode = (_options: WaitlistOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/waitlist/verify-invite\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: z.object({\n\t\t\t\tinviteCode: z.string(),\n\t\t\t}),\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Verify a waitlist invite code\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: { description: \"Invite code verification result\" },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { inviteCode } = ctx.body;\n\t\t\tconst entry = (await ctx.context.adapter.findOne({\n\t\t\t\tmodel: \"waitlist\",\n\t\t\t\twhere: [\n\t\t\t\t\t{ field: \"inviteCode\", value: inviteCode },\n\t\t\t\t\t{ field: \"status\", value: \"approved\" },\n\t\t\t\t],\n\t\t\t})) as Record<string, unknown> | null;\n\t\t\tif (!entry) {\n\t\t\t\treturn ctx.json({ valid: false, email: null });\n\t\t\t}\n\t\t\tif (\n\t\t\t\tentry.inviteExpiresAt &&\n\t\t\t\tnew Date(entry.inviteExpiresAt as string) < new Date()\n\t\t\t) {\n\t\t\t\treturn ctx.json({ valid: false, email: null });\n\t\t\t}\n\t\t\treturn ctx.json({ valid: true, email: entry.email as string });\n\t\t},\n\t);\n","import type { BetterAuthPluginDBSchema } from \"@better-auth/core/db\";\n\nexport const schema = {\n\twaitlist: {\n\t\tfields: {\n\t\t\temail: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: true,\n\t\t\t\tunique: true,\n\t\t\t},\n\t\t\tstatus: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: true,\n\t\t\t\tdefaultValue: \"pending\",\n\t\t\t},\n\t\t\tinviteCode: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: false,\n\t\t\t\tunique: true,\n\t\t\t},\n\t\t\tinviteExpiresAt: {\n\t\t\t\ttype: \"date\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\ttype: \"number\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\treferredBy: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\tmetadata: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\tapprovedAt: {\n\t\t\t\ttype: \"date\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\trejectedAt: {\n\t\t\t\ttype: \"date\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\tregisteredAt: {\n\t\t\t\ttype: \"date\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\tcreatedAt: {\n\t\t\t\ttype: \"date\",\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\tupdatedAt: {\n\t\t\t\ttype: \"date\",\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t},\n\t},\n} satisfies BetterAuthPluginDBSchema;\n","import { createAuthMiddleware } from \"@better-auth/core/api\";\nimport type { BetterAuthPlugin } from \"better-auth\";\nimport { APIError } from \"better-auth/api\";\nimport { mergeSchema } from \"better-auth/db\";\nimport { WAITLIST_ERROR_CODES } from \"./error-codes\";\nimport {\n\tapproveEntry,\n\tbulkApprove,\n\tgetWaitlistStats,\n\tlistWaitlist,\n\trejectEntry,\n} from \"./routes/admin\";\nimport {\n\tgetWaitlistStatus,\n\tjoinWaitlist,\n\tverifyInviteCode,\n} from \"./routes/public\";\nimport { schema } from \"./schema\";\nimport type { WaitlistOptions } from \"./types\";\n\nexport { WAITLIST_ERROR_CODES } from \"./error-codes\";\nexport type { WaitlistOptions, WaitlistEntry, WaitlistStatus } from \"./types\";\n\nconst DEFAULT_INTERCEPT_PATHS = [\n\t\"/sign-up/email\",\n\t\"/callback/\",\n\t\"/oauth2/callback/\",\n\t\"/magic-link/verify\",\n\t\"/sign-in/email-otp\",\n\t\"/email-otp/verify-email\",\n\t\"/phone-number/verify\",\n\t\"/sign-in/anonymous\",\n\t\"/one-tap/callback\",\n\t\"/siwe/verify\",\n];\n\nexport const waitlist = (options?: WaitlistOptions) => {\n\tconst opts: WaitlistOptions = {\n\t\tenabled: true,\n\t\trequireInviteCode: false,\n\t\tinviteCodeExpiration: 172800,\n\t\tskipAnonymous: false,\n\t\tadminRoles: [\"admin\"],\n\t\t...options,\n\t};\n\n\treturn {\n\t\tid: \"waitlist\",\n\n\t\tinit() {\n\t\t\treturn {\n\t\t\t\toptions: {\n\t\t\t\t\tdatabaseHooks: {\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\tcreate: {\n\t\t\t\t\t\t\t\tasync before(user, ctx) {\n\t\t\t\t\t\t\t\t\tif (opts.enabled === false) return;\n\n\t\t\t\t\t\t\t\t\t// Skip anonymous users if configured\n\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\topts.skipAnonymous &&\n\t\t\t\t\t\t\t\t\t\t(user as Record<string, unknown>).isAnonymous\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\treturn;\n\n\t\t\t\t\t\t\t\t\t// No email means we can't check waitlist\n\t\t\t\t\t\t\t\t\tif (!user.email) return;\n\n\t\t\t\t\t\t\t\t\tconst email = user.email.toLowerCase();\n\n\t\t\t\t\t\t\t\t\tif (!ctx) return;\n\t\t\t\t\t\t\t\t\tconst adapter = ctx.context?.adapter;\n\t\t\t\t\t\t\t\t\tif (!adapter) return;\n\n\t\t\t\t\t\t\t\t\tconst entry = await adapter.findOne({\n\t\t\t\t\t\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t\t\t\t\t{ field: \"email\", value: email },\n\t\t\t\t\t\t\t\t\t\t\t{ field: \"status\", value: \"approved\" },\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\t// If no approved entry, block user creation\n\t\t\t\t\t\t\t\t\tif (!entry) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tasync after(user, ctx) {\n\t\t\t\t\t\t\t\t\tif (opts.enabled === false) return;\n\t\t\t\t\t\t\t\t\tif (!user.email) return;\n\t\t\t\t\t\t\t\t\tif (!ctx) return;\n\n\t\t\t\t\t\t\t\t\tconst email = user.email.toLowerCase();\n\t\t\t\t\t\t\t\t\tconst adapter = ctx.context?.adapter;\n\t\t\t\t\t\t\t\t\tif (!adapter) return;\n\n\t\t\t\t\t\t\t\t\t// Mark waitlist entry as registered\n\t\t\t\t\t\t\t\t\tconst entry = await adapter.findOne({\n\t\t\t\t\t\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\t\t\t\t\t\twhere: [{ field: \"email\", value: email }],\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tif (entry) {\n\t\t\t\t\t\t\t\t\t\tawait adapter.update({\n\t\t\t\t\t\t\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\t\t\t\t\t\t\twhere: [{ field: \"email\", value: email }],\n\t\t\t\t\t\t\t\t\t\t\tupdate: {\n\t\t\t\t\t\t\t\t\t\t\t\tstatus: \"registered\",\n\t\t\t\t\t\t\t\t\t\t\t\tregisteredAt: new Date(),\n\t\t\t\t\t\t\t\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tendpoints: {\n\t\t\tjoinWaitlist: joinWaitlist(opts),\n\t\t\tgetWaitlistStatus: getWaitlistStatus(opts),\n\t\t\tverifyInviteCode: verifyInviteCode(opts),\n\t\t\tapproveEntry: approveEntry(opts),\n\t\t\trejectEntry: rejectEntry(opts),\n\t\t\tbulkApprove: bulkApprove(opts),\n\t\t\tlistWaitlist: listWaitlist(opts),\n\t\t\tgetWaitlistStats: getWaitlistStats(opts),\n\t\t},\n\n\t\thooks: {\n\t\t\tbefore: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context: { path?: string }) {\n\t\t\t\t\t\tif (opts.enabled === false) return false;\n\t\t\t\t\t\tconst paths = opts.interceptPaths ?? DEFAULT_INTERCEPT_PATHS;\n\t\t\t\t\t\treturn paths.some(\n\t\t\t\t\t\t\t(p) => context.path === p || context.path?.startsWith(p),\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\t// Extract email from request body\n\t\t\t\t\t\tconst email = ctx.body?.email as string | undefined;\n\n\t\t\t\t\t\tif (email) {\n\t\t\t\t\t\t\tconst normalizedEmail = email.toLowerCase();\n\n\t\t\t\t\t\t\t// Check if this is a login (user already exists) vs signup\n\t\t\t\t\t\t\tconst existingUser =\n\t\t\t\t\t\t\t\tawait ctx.context.internalAdapter.findUserByEmail(\n\t\t\t\t\t\t\t\t\tnormalizedEmail,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (existingUser) {\n\t\t\t\t\t\t\t\t// Existing user -- this is a login, let it through\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (opts.requireInviteCode) {\n\t\t\t\t\t\t\t// Require invite code in body or header\n\t\t\t\t\t\t\tconst code =\n\t\t\t\t\t\t\t\t(ctx.body?.inviteCode as string | undefined) ||\n\t\t\t\t\t\t\t\tctx.headers?.get(\"x-invite-code\");\n\t\t\t\t\t\t\tif (!code) {\n\t\t\t\t\t\t\t\tthrow new APIError(\"FORBIDDEN\", {\n\t\t\t\t\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.INVITE_CODE_REQUIRED,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst entry = (await ctx.context.adapter.findOne({\n\t\t\t\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\t\t\t\twhere: [\n\t\t\t\t\t\t\t\t\t{ field: \"inviteCode\", value: code },\n\t\t\t\t\t\t\t\t\t{ field: \"status\", value: \"approved\" },\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t})) as Record<string, unknown> | null;\n\n\t\t\t\t\t\t\tif (!entry) {\n\t\t\t\t\t\t\t\tthrow new APIError(\"FORBIDDEN\", {\n\t\t\t\t\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.INVALID_INVITE_CODE,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check expiration\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tentry.inviteExpiresAt &&\n\t\t\t\t\t\t\t\tnew Date(entry.inviteExpiresAt as string) < new Date()\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tthrow new APIError(\"FORBIDDEN\", {\n\t\t\t\t\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.INVALID_INVITE_CODE,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (email) {\n\t\t\t\t\t\t\t// No invite code required -- just check approval status\n\t\t\t\t\t\t\tconst normalizedEmail = email.toLowerCase();\n\t\t\t\t\t\t\tconst entry = (await ctx.context.adapter.findOne({\n\t\t\t\t\t\t\t\tmodel: \"waitlist\",\n\t\t\t\t\t\t\t\twhere: [{ field: \"email\", value: normalizedEmail }],\n\t\t\t\t\t\t\t})) as Record<string, unknown> | null;\n\n\t\t\t\t\t\t\tif (!entry || entry.status !== \"approved\") {\n\t\t\t\t\t\t\t\tthrow new APIError(\"FORBIDDEN\", {\n\t\t\t\t\t\t\t\t\tmessage: WAITLIST_ERROR_CODES.NOT_APPROVED,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// If no email in body (e.g., OAuth callbacks), the databaseHooks\n\t\t\t\t\t\t// will catch it\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\n\t\tschema: mergeSchema(schema, opts.schema),\n\t\t$ERROR_CODES: WAITLIST_ERROR_CODES,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@guilhermejansen/better-auth-waitlist",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Waitlist / early-access plugin for Better Auth. Intercepts all registration paths and gates sign-ups behind an invite-based waitlist.",
|
|
6
|
+
"author": "Guilherme Jansen",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "dist/index.mjs",
|
|
9
|
+
"module": "dist/index.mjs",
|
|
10
|
+
"types": "dist/index.d.mts",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"dev-source": "./src/index.ts",
|
|
17
|
+
"types": "./dist/index.d.mts",
|
|
18
|
+
"default": "./dist/index.mjs"
|
|
19
|
+
},
|
|
20
|
+
"./client": {
|
|
21
|
+
"dev-source": "./src/client.ts",
|
|
22
|
+
"types": "./dist/client.d.mts",
|
|
23
|
+
"default": "./dist/client.mjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"typesVersions": {
|
|
27
|
+
"*": {
|
|
28
|
+
"*": [
|
|
29
|
+
"./dist/index.d.mts"
|
|
30
|
+
],
|
|
31
|
+
"client": [
|
|
32
|
+
"./dist/client.d.mts"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"keywords": [
|
|
40
|
+
"better-auth",
|
|
41
|
+
"auth",
|
|
42
|
+
"waitlist",
|
|
43
|
+
"early-access",
|
|
44
|
+
"invite",
|
|
45
|
+
"plugin",
|
|
46
|
+
"typescript"
|
|
47
|
+
],
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/guilhermejansen/better-auth-waitlist.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/guilhermejansen/better-auth-waitlist#readme",
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"zod": "^4.3.5"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@better-auth/core": "^1.0.0",
|
|
58
|
+
"better-auth": "^1.0.0",
|
|
59
|
+
"better-call": "^1.0.0",
|
|
60
|
+
"nanostores": "^1.0.1"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"nanostores": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@better-auth/core": "1.4.18",
|
|
69
|
+
"@better-fetch/fetch": "1.1.21",
|
|
70
|
+
"@biomejs/biome": "^1.9.4",
|
|
71
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
72
|
+
"@types/bun": "^1.3.8",
|
|
73
|
+
"@types/node": "^25.2.2",
|
|
74
|
+
"better-auth": "1.4.18",
|
|
75
|
+
"better-call": "1.1.8",
|
|
76
|
+
"better-sqlite3": "^12.6.2",
|
|
77
|
+
"nanostores": "^1.1.0",
|
|
78
|
+
"tsdown": "^0.20.3",
|
|
79
|
+
"type-fest": "^5.4.4",
|
|
80
|
+
"typescript": "^5.9.3",
|
|
81
|
+
"vitest": "^4.0.18"
|
|
82
|
+
},
|
|
83
|
+
"scripts": {
|
|
84
|
+
"test": "vitest",
|
|
85
|
+
"coverage": "vitest run --coverage --coverage.provider=istanbul",
|
|
86
|
+
"lint:package": "publint run --strict",
|
|
87
|
+
"lint:types": "attw --profile esm-only --pack .",
|
|
88
|
+
"lint": "biome check .",
|
|
89
|
+
"lint:fix": "biome check --write .",
|
|
90
|
+
"format": "biome format --write .",
|
|
91
|
+
"format:check": "biome format .",
|
|
92
|
+
"build": "tsdown",
|
|
93
|
+
"dev": "tsdown --watch",
|
|
94
|
+
"typecheck": "tsc --noEmit"
|
|
95
|
+
}
|
|
96
|
+
}
|