{"id":30344467,"url":"https://github.com/bard/better-auth-invite","last_synced_at":"2025-08-18T12:42:20.315Z","repository":{"id":295379024,"uuid":"989956359","full_name":"bard/better-auth-invite","owner":"bard","description":null,"archived":false,"fork":false,"pushed_at":"2025-07-01T08:37:03.000Z","size":421,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-09T02:28:07.353Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bard.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-25T07:36:57.000Z","updated_at":"2025-07-18T17:06:32.000Z","dependencies_parsed_at":"2025-05-25T08:32:04.196Z","dependency_job_id":null,"html_url":"https://github.com/bard/better-auth-invite","commit_stats":null,"previous_names":["bard/better-auth-invite"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bard/better-auth-invite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bard%2Fbetter-auth-invite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bard%2Fbetter-auth-invite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bard%2Fbetter-auth-invite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bard%2Fbetter-auth-invite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bard","download_url":"https://codeload.github.com/bard/better-auth-invite/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bard%2Fbetter-auth-invite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270996198,"owners_count":24681933,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-18T02:00:08.743Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-08-18T12:42:19.539Z","updated_at":"2025-08-18T12:42:20.287Z","avatar_url":"https://github.com/bard.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"**UNDER DEVELOPMENT — ALPHA**\n\n# Invite System for better-auth\n\n## Features\n\n- Invites don't depend on the inviter knowing the invitee's email — invite links can be shared over 3rd-party platforms (instant messaging, SMS) or live (with a QR code).\n  - Choice of email to sign up with is up to the invitee.\n- Users without invite can sign up with reduced access (useful for waitlist functionality).\n- Meant to work with all authentication methods. Currently tested: email and password; email OTP; social.\n- Customizable code duration, code generation, and invite creation and acceptance criteria (e.g. to limit upgrade to users from a given domain).\n- Keeps track of who created and who accepted the invite.\n\n## Mode of Operation\n\n`better-auth-invite` works in concert with the `admin` plugin and its access control capabilities.\n\nWhen a user activates a valid invite, the invite code gets stored into a signed, http-only cookie in the user's browser.\n\nIf a user without an active invite signs up, he or she receives the default role, as defined by the `admin` plugin. That is `\"user\"` by default, but you might want to reserve that for invited users, and set the default role to `\"guest\"` for clarity.\n\nIf a user with default role and an active invite signs up or signs in, his or her role gets upgraded.\n\n## Alternatives\n\n- [@better-auth-kit/app-invite](https://www.better-auth-kit.com/docs/plugins/app-invite)\n\n## Setup\n\n### Server-Side Setup\n\nImport the `invite` plugin and add it to your `betterAuth` configuration.\n\n**Important**: make sure `defaultRole` in the `admin` plugin options and `roleForSignupWithoutInvite` in the `invite` plugin options match.\n\n```typescript\nimport { betterAuth } from \"better-auth\";\nimport { admin as adminPlugin } from \"better-auth/plugins\";\nimport { createAccessControl } from \"better-auth/plugins/access\";\nimport { invite } from \"better-auth-invite\";\n\n// Define your access control statements and roles\nconst statement = { ...defaultStatements } as const;\nconst ac = createAccessControl(statement);\nconst guest = ac.newRole({ ...userAc.statements });\nconst user = ac.newRole({ ...userAc.statements });\nconst admin = ac.newRole({ ...adminAc.statements });\n\nconst auth = betterAuth({\n  database, // Your database adapter\n  plugins: [\n    adminPlugin({\n      ac,\n      roles: { guest, user, admin },\n      defaultRole: \"guest\",\n    }),\n    invite({\n      inviteDurationSeconds: 3600, // Invites valid for 1 hour\n      roleForSignupWithoutInvite: \"guest\", // Role for users signing up without an invite\n      roleForSignupWithInvite: \"user\", // Role for users signing up with a valid invite\n      // Optional:\n      // generateCode: () =\u003e generateRandomString(8),\n      // canCreateInvite: (user) =\u003e user.role === 'manager',\n      // canAcceptInvite: (user) =\u003e user.email.endsWith('@acme.com'),\n    }),\n  ],\n  emailAndPassword: { enabled: true }, // Or other auth strategies\n  // ... other betterAuth options\n});\n```\n\n**`InviteOptions`:**\n\n- `inviteDurationSeconds` (number, required): The duration in seconds for which an invite code is valid after its creation.\n- `roleForSignupWithoutInvite` (string, required): The role assigned by the `admin` plugin to users who sign up without an active invite.\n- `roleForSignupWithInvite` (string, required): The role assigned to users who sign up with a valid, active invite.\n- `canCreateInvite` (function, optional): A function `(user: UserWithRole) =\u003e boolean` that determines if a given user can create invites. If not provided, any authenticated user who is not in the `roleForSignupWithoutInvite` can create invites.\n- `[TODO]` `canAcceptInvite` (function, optional): A function `(user: UserWithRole) =\u003e boolean` that determines if a given user can activate an invite. If not provided, any user can accept an invite.\n- `generateCode` (function, optional): A function `() =\u003e string` that returns a string to be used as the invite code. Defaults to a cryptographically strong random string generator (6 characters, 0-9, A-Z).\n- `getDate` (function, optional): A function `() =\u003e Date` that returns the current `Date`. Defaults to `() =\u003e new Date()`. Useful for testing time-sensitive features.\n\n### Client-Side Setup\n\nImport the `inviteClient` plugin and add it to your `betterAuth` client configuration.\n\n```typescript\nimport { createClient } from \"better-auth/client\"; // Or your client creation utility\n// Adjust the import path based on your project setup\nimport { inviteClient } from \"./client.js\";\n\nconst client = createClient({\n  // ... other client options\n  plugins: [inviteClient()],\n});\n```\n\n## Usage\n\n### 1. Creating Invites\n\nAuthenticated users can create invite codes. The client plugin (`client.invite.create`) provides a method for this:\n\n```typescript\nimport { client } from \"@/lib/auth-client\";\n\nconst { data, error } = await client.invite.create({\n  _: true, // better-call seems to require a body to be always defined for POST\n});\n\nif (error) {\n  console.error(\"Failed to create invite:\", error);\n  return;\n}\n\nif (data) {\n  console.log(\"Invite code created:\", data.code);\n  // Example response: { data: { code: \"invite-123\" }, error: null }\n  return data.code;\n}\n```\n\nThe server will handle storing this invite code, associating it with the creating user, and setting its expiry based on `inviteDurationSeconds`. By default, an invite can be used once (`maxUses: 1`).\n\n### 2. Activating Invites\n\nWhen a user receives an invite code, he or she needs to activate it. This is typically done by visiting a specific link or entering the code on a page. The client plugin (`client.invite.activate`) provides a method to handle this.\n\n```typescript\n// Assuming 'client' is your configured better-auth client instance\n\nasync function activateInvite(code: string) {\n  const { data, error } = await client.invite.activate({ code });\n\n  if (error) {\n    console.error(\"Failed to activate invite:\", error);\n    // Handle error (e.g., code invalid, expired, already used)\n    return false;\n  }\n\n  // On successful activation, a cookie named 'better-auth.invite-code'\n  // is set in the user's browser. This cookie will be used during sign-up.\n  console.log(\"Invite activated successfully. User can now sign up.\");\n  return true;\n}\n```\n\n### 3. Signing Up\n\nThe invite system integrates with the standard sign-up and sign-in process. The behavior depends on whether a user has an active, activated invite.\n\n**Scenario 1: Signing Up or Signing In with an Active Invite**\n\n1.  **Activate Invite**: The user first activates an invite code (see \"Activating Invites\"). This sets a `better-auth.invite-code` cookie.\n2.  **Sign Up / Sign In**: The user proceeds to sign up or sign in (e.g., using email and password).\n3.  **Role Upgrade**: If a valid `better-auth.invite-code` cookie is present and the user's current role is `roleForSignupWithoutInvite`, the invite is validated. If the invite is valid:\n    - The user's role is upgraded to `roleForSignupWithInvite`.\n    - The invite code is marked as used in the database.\n    - The `better-auth.invite-code` cookie is cleared.\n\n```typescript\n// Assuming 'client' is your configured better-auth client instance\n// and the user has already activated an invite code (the 'better-auth.invite-code' cookie is set).\n\nasync function signUpNewUserWithInvite(email, password, name) {\n  const { data, error } = await client.signUp.email({\n    email,\n    password,\n    name,\n  });\n\n  if (error) {\n    console.error(\"Sign-up failed:\", error);\n    return;\n  }\n\n  if (data) {\n    console.log(\n      \"Sign-up successful, user should have roleForSignupWithInvite:\",\n      data.user,\n    );\n    // data.user contains the new user object, whose role should now be roleForSignupWithInvite.\n    // data.token contains the session token.\n  }\n}\n```\n\n**Scenario 2: Signing Up Or Signing In Without an Invite (or with an Invalid/Expired Invite)**\n\nUsers can also sign up without an invite code. This is useful to implement waiting lists.\n\n1.  **Sign Up**: The user signs up directly without activating an invite, or if their activated invite is invalid, expired, or already used.\n2.  **Default Role**:\n    - The user is created with the default role as defined in the `admin` plugin.\n    - The `invite` plugin does not run.\n\nThis allows the system to capture user interest even if invites are limited. Their role can be upgraded later, potentially by issuing them an invite or through other administrative actions.\n\n```typescript\n// Assuming 'client' is your configured better-auth client instance\n\nasync function signUpNewUserWithoutInvite(email, password, name) {\n  const { data, error } = await client.signUp.email({\n    email,\n    password,\n    name,\n  });\n\n  if (error) {\n    console.error(\"Sign-up failed:\", error);\n    // Standard sign-up errors.\n    return;\n  }\n\n  if (data) {\n    console.log(\n      \"Sign-up successful, user should have roleForSignupWithoutInvite:\",\n      data.user,\n    );\n    // data.user contains the new user object with the roleForSignupWithoutInvite.\n    // data.token contains the session token.\n  }\n}\n```\n\n## Database Schema\n\nThe invite plugin adds two tables to your database: `invite` and `invite_use`.\n\n#### `invite` table\n\nStores the invite codes.\n\n- `code` (string, unique): The invite code.\n- `createdByUserId` (string, references `user.id`): The ID of the user who created the invite.\n- `maxUses` (number): The maximum number of times the invite can be used.\n- `createdAt` (date): Timestamp of creation.\n- `expiresAt` (date): Timestamp when the invite expires.\n\n#### `invite_use` table\n\nTracks each use of an invite.\n\n- `inviteId` (string, references `invite.id`): The ID of the invite being used.\n- `usedByUserId` (string, references `user.id`): The ID of the user who used the invite.\n- `usedAt` (date): Timestamp when the invite was used.\n\nThis schema is managed by `better-auth` migrations when the plugin is active and `shouldRunMigrations: true` is set during initialization.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbard%2Fbetter-auth-invite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbard%2Fbetter-auth-invite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbard%2Fbetter-auth-invite/lists"}