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

Open
opened 2026-04-13 10:29:52 -05:00 by GiteaMirror · 0 comments
Owner

Original Pull Request: https://github.com/better-auth/better-auth/pull/8606

State: open
Merged: No


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.

**Original Pull Request:** https://github.com/better-auth/better-auth/pull/8606 **State:** open **Merged:** No --- ## 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. -->
GiteaMirror added the pull-request label 2026-04-13 10:29:52 -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#16340