mirror of
https://github.com/better-auth/better-auth.git
synced 2026-07-27 16:53:02 -05:00
fix(oauth): forward hook errors and sync linked profiles on rewritten callback (#9702, #9788, #8758)
Re-applies three main fixes onto next's rewritten OAuth callback, which now routes generic-oauth through the shared callback route. - #9702/#9788: a before-callback hook that rejects sign-in (the banned-user guard is the flagship case) now redirects to the per-flow errorURL carrying the hook's machine-readable code and message, instead of surfacing a raw error response. handleOAuthUserInfo is wrapped in try/catch; the hook's APIError code is forwarded verbatim (it is app-defined, not a member of OAUTH_CALLBACK_ERROR_CODES) via next's local redirectOnError closure. The admin plugin's stale inline /callback redirect is removed so the banned-user path flows through that single forwarder and emits BANNED_USER with a URL-encoded description. - #8758: accountLinking.updateUserInfoOnLink now applies on the explicit OAuth link path, not only id-token linking. The regression tests (banned social sign-in emits error=BANNED_USER, the cross-origin errorCallbackURL case, and the 403-JSON id-token case) arrived via the merge and were red on the baseline; these source changes make them pass. (cherry picked from commit 1d893ae572277447025e29462946a7aeb11443ac)
This commit is contained in:
@@ -6,10 +6,14 @@ import { getAwaitableValue } from "../../context/helpers";
|
||||
import { setSessionCookie } from "../../cookies";
|
||||
import { OAUTH_CALLBACK_ERROR_CODES } from "../../oauth2/error-codes";
|
||||
import { missingEmailLogMessage } from "../../oauth2/errors";
|
||||
import { handleOAuthUserInfo } from "../../oauth2/link-account";
|
||||
import {
|
||||
applyUpdateUserInfoOnLink,
|
||||
handleOAuthUserInfo,
|
||||
} from "../../oauth2/link-account";
|
||||
import { generateState, parseState } from "../../oauth2/state";
|
||||
import { setTokenUtil } from "../../oauth2/utils";
|
||||
import { HIDE_METADATA } from "../../utils/hide-metadata";
|
||||
import { isAPIError } from "../../utils/is-api-error";
|
||||
|
||||
const schema = z.object({
|
||||
code: z.string().optional(),
|
||||
@@ -274,6 +278,7 @@ export const callbackOAuth = createAuthEndpoint(
|
||||
);
|
||||
}
|
||||
}
|
||||
await applyUpdateUserInfoOnLink(c, link.userId, userInfo);
|
||||
let toRedirectTo: string;
|
||||
try {
|
||||
const url = callbackURL;
|
||||
@@ -294,20 +299,33 @@ export const callbackOAuth = createAuthEndpoint(
|
||||
...tokens,
|
||||
scope: tokens.scopes?.join(","),
|
||||
};
|
||||
const result = await handleOAuthUserInfo(c, {
|
||||
userInfo: {
|
||||
...userInfo,
|
||||
id: providerAccountId,
|
||||
email: userInfo.email,
|
||||
name: userInfo.name || "",
|
||||
},
|
||||
account: accountData,
|
||||
callbackURL,
|
||||
disableSignUp:
|
||||
(provider.disableImplicitSignUp && !requestSignUp) ||
|
||||
provider.options?.disableSignUp,
|
||||
overrideUserInfo: provider.options?.overrideUserInfoOnSignIn,
|
||||
});
|
||||
let result: Awaited<ReturnType<typeof handleOAuthUserInfo>>;
|
||||
try {
|
||||
result = await handleOAuthUserInfo(c, {
|
||||
userInfo: {
|
||||
...userInfo,
|
||||
id: providerAccountId,
|
||||
email: userInfo.email,
|
||||
name: userInfo.name || "",
|
||||
},
|
||||
account: accountData,
|
||||
callbackURL,
|
||||
disableSignUp:
|
||||
(provider.disableImplicitSignUp && !requestSignUp) ||
|
||||
provider.options?.disableSignUp,
|
||||
overrideUserInfo: provider.options?.overrideUserInfoOnSignIn,
|
||||
});
|
||||
} catch (e) {
|
||||
// A before-callback hook (for example the admin plugin's banned-user
|
||||
// guard) can reject sign-in by throwing an APIError. Forward its
|
||||
// machine-readable code and message to the per-flow errorURL instead
|
||||
// of surfacing a raw error response. The code is app-defined, so it is
|
||||
// forwarded verbatim rather than mapped onto OAUTH_CALLBACK_ERROR_CODES.
|
||||
if (isAPIError(e) && e.body?.code) {
|
||||
redirectOnError(e.body.code, e.body.message);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
if (result.error) {
|
||||
c.context.logger.error(result.error.split(" ").join("_"));
|
||||
return redirectOnError(result.error.split(" ").join("_"));
|
||||
|
||||
@@ -113,15 +113,6 @@ export const admin = <O extends AdminOptions>(options?: O | undefined) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.path.startsWith("/callback")) {
|
||||
const redirectURI =
|
||||
ctx.context.options.onAPIError?.errorURL ||
|
||||
`${ctx.context.baseURL}/error`;
|
||||
throw ctx.redirect(
|
||||
`${redirectURI}?error=banned&error_description=${opts.bannedUserMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
throw APIError.from("FORBIDDEN", {
|
||||
message: opts.bannedUserMessage,
|
||||
code: "BANNED_USER",
|
||||
|
||||
Reference in New Issue
Block a user