[PR #8606] feat(organization): add email confirmation flow for ownership transfer #24994

Open
opened 2026-04-15 22:40:50 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/8606
Author: @mcorbelli
Created: 3/14/2026
Status: 🔄 Open

Base: nextHead: feat/confirm-ownership-transfer


📝 Commits (10+)

  • 07e656e feat(organization): add email confirmation flow for ownership transfer
  • 02e6022 test(organization): add tests and docs for org ownership transfer
  • fe77b35 fix(organization): fix transfer ownership token validation and open redirect
  • 8e32828 fix(organization): revalidate sender's owner role on transfer token redemption
  • 0273634 feat(organization): formatted code
  • 2e407c3 Merge branch 'canary' into feat/confirm-ownership-transfer
  • 16613f0 fix(organization): cspell lint
  • 674a93b Merge branch 'canary' into feat/confirm-ownership-transfer
  • e44ec87 feat(organization): add email confirmation flow for ownership transfer
  • 8f91ef6 test(organization): add tests and docs for org ownership transfer

📊 Changes

7 files changed (+1511 additions, -2 deletions)

View changed files

📝 docs/content/docs/plugins/organization.mdx (+136 -0)
📝 packages/better-auth/src/plugins/organization/client.ts (+2 -0)
📝 packages/better-auth/src/plugins/organization/error-codes.ts (+5 -0)
📝 packages/better-auth/src/plugins/organization/organization.ts (+32 -0)
📝 packages/better-auth/src/plugins/organization/routes/crud-members.test.ts (+740 -1)
📝 packages/better-auth/src/plugins/organization/routes/crud-members.ts (+514 -1)
📝 packages/better-auth/src/plugins/organization/types.ts (+82 -0)

📄 Description

Summary

Adds an optional email confirmation flow to the organization ownership transfer. By default, organization.transferOwnership remains immediate (no breaking change). When sendTransferOwnershipEmail is configured, the transfer is deferred until the prospective new owner confirms via email.

Changes

New options (OrganizationOptions)

  • sendTransferOwnershipEmail — async callback invoked with { organization, currentOwner, newOwner, url, token } to send the confirmation email
  • transferOwnershipTokenExpiresIn — expiry in seconds for the verification token (default: 86400)

New endpoints

  • POST /organization/transfer-ownership — initiates the transfer (immediate or deferred)
  • GET /organization/transfer-ownership/callback — accepts the transfer when the new owner clicks the email link

New hooks (organizationHooks)

  • beforeTransferOwnership — called before ownership is transferred, receives { organization, currentOwner, newOwnerMember }
  • afterTransferOwnership — called after ownership is transferred, receives { organization, previousOwner, newOwnerMember }

New error codes

  • CANNOT_TRANSFER_OWNERSHIP_TO_NON_MEMBER
  • YOU_ARE_NOT_ALLOWED_TO_TRANSFER_OWNERSHIP
  • INVALID_TRANSFER_TOKEN

Client

  • authClient.organization.transferOwnership({ organizationId, memberId, callbackURL? })
  • authClient.organization.acceptOwnershipTransfer({ token })

Flow (when sendTransferOwnershipEmail is configured)

  1. Current owner calls organization.transferOwnership → token generated, email sent
  2. New owner receives email with link to GET /organization/transfer-ownership/callback?token=<token>
  3. New owner visits link while authenticated → transfer completed, optional redirect to callbackURL

Tests

8 new tests in crud-members.test.ts covering:

  • immediate transfer
  • deferred email flow (token generation)
  • callback acceptance by correct user
  • rejection when wrong user tries to accept
  • invalid token handling
  • non-owner initiating transfer (403)
  • non-member target (400)
  • beforeTransferOwnership / afterTransferOwnership hooks

Documentation

New "Transfer Ownership" section added to docs/content/docs/plugins/organization.mdx with API reference, immediate transfer example, and full email confirmation flow.

Breaking changes

None. The existing transferOwnership behavior is unchanged when sendTransferOwnershipEmail is not configured.


Summary by cubic

Adds an optional email confirmation flow for organization ownership transfer with strict token checks and safe redirects. Immediate transfers remain default; when enabled, the new owner must confirm via email link or by sending the token.

  • New Features

    • Endpoints and APIs: POST /organization/transfer-ownership (also accepts a token in the body for programmatic acceptance) and GET /organization/transfer-ownership/callback; client: authClient.organization.transferOwnership, authClient.organization.transferOwnershipCallback; server: auth.api.transferOwnership, auth.api.transferOwnershipCallback.
    • Options and hooks: sendTransferOwnershipEmail, transferOwnershipTokenExpiresIn (24h default), and beforeTransferOwnership/afterTransferOwnership.
    • Flow: When email is enabled, the current owner can optionally pass password for verification; otherwise unchanged immediate transfer.
  • Bug Fixes

    • Strong token validation (org match, intended recipient, expiry) and revalidation of the sender’s owner role on redemption; promotes new owner before demoting old to avoid orphan states.
    • Prevents open redirects by validating the callbackURL origin in the callback endpoint.
    • Docs updated with a new “Transfer Ownership” section; tests cover immediate and email flows, GET/POST acceptance, wrong user attempts, expiry, and hooks.

Written for commit 0ec5c29880. Summary will update on new commits.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/8606 **Author:** [@mcorbelli](https://github.com/mcorbelli) **Created:** 3/14/2026 **Status:** 🔄 Open **Base:** `next` ← **Head:** `feat/confirm-ownership-transfer` --- ### 📝 Commits (10+) - [`07e656e`](https://github.com/better-auth/better-auth/commit/07e656ee8680aba29e9f84666e4c1425d6f19a14) feat(organization): add email confirmation flow for ownership transfer - [`02e6022`](https://github.com/better-auth/better-auth/commit/02e602223402edc1bc17a9fbe7ab61519f5a9b1a) test(organization): add tests and docs for org ownership transfer - [`fe77b35`](https://github.com/better-auth/better-auth/commit/fe77b354f77dc4edf2e6fa035880a538c0801ac2) fix(organization): fix transfer ownership token validation and open redirect - [`8e32828`](https://github.com/better-auth/better-auth/commit/8e32828e4d4fa3f37000f0fd5b1ef7a474940114) fix(organization): revalidate sender's owner role on transfer token redemption - [`0273634`](https://github.com/better-auth/better-auth/commit/027363452bb8d10d746f2e0cf5af559de496c126) feat(organization): formatted code - [`2e407c3`](https://github.com/better-auth/better-auth/commit/2e407c3fa9e04a5132360c68115ed7da99244962) Merge branch 'canary' into feat/confirm-ownership-transfer - [`16613f0`](https://github.com/better-auth/better-auth/commit/16613f01a2b26ed7146a96af8a821600e5a4ec0e) fix(organization): cspell lint - [`674a93b`](https://github.com/better-auth/better-auth/commit/674a93b68f0f3a84a7a0b798f0242ed3fd5164a3) Merge branch 'canary' into feat/confirm-ownership-transfer - [`e44ec87`](https://github.com/better-auth/better-auth/commit/e44ec87a33823369ed2b09557346623cd1c09b86) feat(organization): add email confirmation flow for ownership transfer - [`8f91ef6`](https://github.com/better-auth/better-auth/commit/8f91ef6d98d9bbf3a8f5564e8a6cb0bfbc79081f) test(organization): add tests and docs for org ownership transfer ### 📊 Changes **7 files changed** (+1511 additions, -2 deletions) <details> <summary>View changed files</summary> 📝 `docs/content/docs/plugins/organization.mdx` (+136 -0) 📝 `packages/better-auth/src/plugins/organization/client.ts` (+2 -0) 📝 `packages/better-auth/src/plugins/organization/error-codes.ts` (+5 -0) 📝 `packages/better-auth/src/plugins/organization/organization.ts` (+32 -0) 📝 `packages/better-auth/src/plugins/organization/routes/crud-members.test.ts` (+740 -1) 📝 `packages/better-auth/src/plugins/organization/routes/crud-members.ts` (+514 -1) 📝 `packages/better-auth/src/plugins/organization/types.ts` (+82 -0) </details> ### 📄 Description ## Summary Adds an optional email confirmation flow to the organization ownership transfer. By default, `organization.transferOwnership` remains immediate (no breaking change). When `sendTransferOwnershipEmail` is configured, the transfer is deferred until the prospective new owner confirms via email. ## Changes ### New options (`OrganizationOptions`) - `sendTransferOwnershipEmail` — async callback invoked with `{ organization, currentOwner, newOwner, url, token }` to send the confirmation email - `transferOwnershipTokenExpiresIn` — expiry in seconds for the verification token (default: 86400) ### New endpoints - `POST /organization/transfer-ownership` — initiates the transfer (immediate or deferred) - `GET /organization/transfer-ownership/callback` — accepts the transfer when the new owner clicks the email link ### New hooks (`organizationHooks`) - `beforeTransferOwnership` — called before ownership is transferred, receives `{ organization, currentOwner, newOwnerMember }` - `afterTransferOwnership` — called after ownership is transferred, receives `{ organization, previousOwner, newOwnerMember }` ### New error codes - `CANNOT_TRANSFER_OWNERSHIP_TO_NON_MEMBER` - `YOU_ARE_NOT_ALLOWED_TO_TRANSFER_OWNERSHIP` - `INVALID_TRANSFER_TOKEN` ### Client - `authClient.organization.transferOwnership({ organizationId, memberId, callbackURL? })` - `authClient.organization.acceptOwnershipTransfer({ token })` ## Flow (when `sendTransferOwnershipEmail` is configured) 1. Current owner calls `organization.transferOwnership` → token generated, email sent 2. New owner receives email with link to `GET /organization/transfer-ownership/callback?token=<token>` 3. New owner visits link while authenticated → transfer completed, optional redirect to `callbackURL` ## Tests 8 new tests in `crud-members.test.ts` covering: - immediate transfer - deferred email flow (token generation) - callback acceptance by correct user - rejection when wrong user tries to accept - invalid token handling - non-owner initiating transfer (403) - non-member target (400) - `beforeTransferOwnership` / `afterTransferOwnership` hooks ## Documentation New "Transfer Ownership" section added to `docs/content/docs/plugins/organization.mdx` with API reference, immediate transfer example, and full email confirmation flow. ## Breaking changes None. The existing `transferOwnership` behavior is unchanged when `sendTransferOwnershipEmail` is not configured. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds an optional email confirmation flow for organization ownership transfer with strict token checks and safe redirects. Immediate transfers remain default; when enabled, the new owner must confirm via email link or by sending the token. - **New Features** - Endpoints and APIs: POST `/organization/transfer-ownership` (also accepts a `token` in the body for programmatic acceptance) and GET `/organization/transfer-ownership/callback`; client: `authClient.organization.transferOwnership`, `authClient.organization.transferOwnershipCallback`; server: `auth.api.transferOwnership`, `auth.api.transferOwnershipCallback`. - Options and hooks: `sendTransferOwnershipEmail`, `transferOwnershipTokenExpiresIn` (24h default), and `beforeTransferOwnership`/`afterTransferOwnership`. - Flow: When email is enabled, the current owner can optionally pass `password` for verification; otherwise unchanged immediate transfer. - **Bug Fixes** - Strong token validation (org match, intended recipient, expiry) and revalidation of the sender’s owner role on redemption; promotes new owner before demoting old to avoid orphan states. - Prevents open redirects by validating the `callbackURL` origin in the callback endpoint. - Docs updated with a new “Transfer Ownership” section; tests cover immediate and email flows, GET/POST acceptance, wrong user attempts, expiry, and hooks. <sup>Written for commit 0ec5c2988025f08ca40f4b0a5ef26ae7c7b16989. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-15 22:40:50 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#24994