[PR #4196] Generic OAuth: make mapProfileToUser synchronous-only to prevent silent drops #22146

Open
opened 2026-04-15 20:51:04 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/4196
Author: @kyrregjerstad
Created: 8/24/2025
Status: 🔄 Open

Base: mainHead: fix/type-mapProfileToUser-sync


📝 Commits (2)

  • e64ad6e fix(generic-oauth): remove Promise from mapProfileToUser return type
  • cdcb611 Merge branch 'canary' into fix/type-mapProfileToUser-sync

📊 Changes

1 file changed (+1 additions, -1 deletions)

View changed files

📝 packages/better-auth/src/plugins/generic-oauth/index.ts (+1 -1)

📄 Description

Summary

  • Enforces that mapProfileToUser must return synchronously.
  • Prevents accidental async functions from being passed, which previously led to fields being silently dropped when merged in the core callback path.

Motivation

In the core OAuth callback, the result of mapProfileToUser is merged without await. When an async function was supplied, the returned Promise was spread (a no-op), causing mapped fields to be lost before user.create.before.

What changed

  • Type signature narrowed to disallow Promise return.
  • No runtime logic changes. Strictly a DX/type-safety improvement.

Impact

  • Compile-time error if an async mapProfileToUser is provided.
  • No behavioral changes for existing synchronous mappings.

Migration guide

  • If your mapProfileToUser is async, make it synchronous.
  • Move any async work into getUserInfo and return a fully shaped user there.

Example (before, problematic):

mapProfileToUser: async (profile) => {
	const data = await something()

	return {
		...profile,
		...data
	}
}

Example (after, recommended):

getUserInfo: async (tokens) => {
  const profile = await fetchProviderProfile(tokens);
  return {
    id: profile.sub,
    email: profile.email,
    emailVerified: profile.email_verified,
    name: profile.name,
    firstName: profile.given_name,
  };
},
mapProfileToUser: (profile) => ({
  // Optional: do a final sync mapping if needed
  lastName: profile.family_name,
})

Summary by cubic

Enforces mapProfileToUser to be synchronous-only in Generic OAuth to prevent silent field drops caused by async functions. Type-only change; existing synchronous mappings are unaffected.

  • Migration
    • If you used an async mapProfileToUser, make it synchronous.
    • Move any async work to getUserInfo and return the final user shape there.

🔄 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/4196 **Author:** [@kyrregjerstad](https://github.com/kyrregjerstad) **Created:** 8/24/2025 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/type-mapProfileToUser-sync` --- ### 📝 Commits (2) - [`e64ad6e`](https://github.com/better-auth/better-auth/commit/e64ad6e97ed142b227f13d97f51e09d09c4283cf) fix(generic-oauth): remove Promise from mapProfileToUser return type - [`cdcb611`](https://github.com/better-auth/better-auth/commit/cdcb611d90285d8c9ffe2ff4e700ff69ac92b1d3) Merge branch 'canary' into fix/type-mapProfileToUser-sync ### 📊 Changes **1 file changed** (+1 additions, -1 deletions) <details> <summary>View changed files</summary> 📝 `packages/better-auth/src/plugins/generic-oauth/index.ts` (+1 -1) </details> ### 📄 Description ### Summary - Enforces that `mapProfileToUser` must return synchronously. - Prevents accidental `async` functions from being passed, which previously led to fields being silently dropped when merged in the core callback path. ### Motivation In the core OAuth callback, the result of `mapProfileToUser` is merged without `await`. When an async function was supplied, the returned Promise was spread (a no-op), causing mapped fields to be lost before `user.create.before`. ### What changed - Type signature narrowed to disallow Promise return. - No runtime logic changes. Strictly a DX/type-safety improvement. ### Impact - Compile-time error if an async `mapProfileToUser` is provided. - No behavioral changes for existing synchronous mappings. ### Migration guide - If your `mapProfileToUser` is `async`, make it synchronous. - Move any async work into `getUserInfo` and return a fully shaped user there. Example (before, problematic): ```ts mapProfileToUser: async (profile) => { const data = await something() return { ...profile, ...data } } ``` Example (after, recommended): ```ts getUserInfo: async (tokens) => { const profile = await fetchProviderProfile(tokens); return { id: profile.sub, email: profile.email, emailVerified: profile.email_verified, name: profile.name, firstName: profile.given_name, }; }, mapProfileToUser: (profile) => ({ // Optional: do a final sync mapping if needed lastName: profile.family_name, }) ``` <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Enforces mapProfileToUser to be synchronous-only in Generic OAuth to prevent silent field drops caused by async functions. Type-only change; existing synchronous mappings are unaffected. - **Migration** - If you used an async mapProfileToUser, make it synchronous. - Move any async work to getUserInfo and return the final user shape there. <!-- 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 20:51:04 -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#22146