mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-19 21:00:40 +08:00
Fix Apple OAuth behavior
This commit is contained in:
parent
eabbc05a49
commit
ef27c98492
@ -1,4 +1,5 @@
|
||||
import { createOAuthUserAndAccount, findExistingOAuthAccount, getProjectUserIdFromOAuthAccount, handleOAuthEmailMergeStrategy, linkOAuthAccountToUser } from "@/lib/oauth";
|
||||
import { isAppleEmailVerified } from "@/oauth/utils";
|
||||
import { getBestEffortEndUserRequestContext } from "@/lib/end-users";
|
||||
import { buildSignUpRuleOptions } from "@/lib/sign-up-context";
|
||||
import { getDisabledBotChallengeAssessment, isBotChallengeDisabled } from "@/lib/turnstile";
|
||||
@ -34,7 +35,7 @@ async function verifyAppleIdToken(idToken: string, allowedBundleIds: string[]):
|
||||
return {
|
||||
sub: payload.sub ?? throwErr("No sub claim in Apple ID token"),
|
||||
email: typeof payload.email === "string" ? payload.email : null,
|
||||
emailVerified: payload.email_verified === true || payload.email_verified === "true",
|
||||
emailVerified: isAppleEmailVerified(payload.email_verified),
|
||||
};
|
||||
} catch (error) {
|
||||
captureError("apple-native-sign-in-token-verification-failed", error);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { HexclaveAssertionError, throwErr } from "@hexclave/shared/dist/utils/errors";
|
||||
import { decodeJwt } from 'jose';
|
||||
import { OAuthUserInfo, validateUserInfo } from "../utils";
|
||||
import { OAuthUserInfo, isAppleEmailVerified, validateUserInfo } from "../utils";
|
||||
import { OAuthBaseProvider, TokenSet } from "./base";
|
||||
|
||||
export class AppleProvider extends OAuthBaseProvider {
|
||||
@ -41,7 +41,7 @@ export class AppleProvider extends OAuthBaseProvider {
|
||||
return validateUserInfo({
|
||||
accountId: payload.sub,
|
||||
email: payload.email,
|
||||
emailVerified: !!payload.email_verified,
|
||||
emailVerified: isAppleEmailVerified(payload.email_verified),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
31
apps/backend/src/oauth/utils.test.ts
Normal file
31
apps/backend/src/oauth/utils.test.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isAppleEmailVerified } from "./utils";
|
||||
|
||||
describe("isAppleEmailVerified", () => {
|
||||
it("treats the boolean true as verified", () => {
|
||||
expect(isAppleEmailVerified(true)).toBe(true);
|
||||
});
|
||||
|
||||
it("treats the string \"true\" as verified", () => {
|
||||
expect(isAppleEmailVerified("true")).toBe(true);
|
||||
});
|
||||
|
||||
it("treats the boolean false as unverified", () => {
|
||||
expect(isAppleEmailVerified(false)).toBe(false);
|
||||
});
|
||||
|
||||
// Regression: a naive `!!value` coerces the string "false" to `true`, which
|
||||
// would let an unverified Apple email pass the account-merge verification gate.
|
||||
it("treats the string \"false\" as unverified", () => {
|
||||
expect(isAppleEmailVerified("false")).toBe(false);
|
||||
});
|
||||
|
||||
it("treats missing/empty/other values as unverified", () => {
|
||||
expect(isAppleEmailVerified(undefined)).toBe(false);
|
||||
expect(isAppleEmailVerified(null)).toBe(false);
|
||||
expect(isAppleEmailVerified("")).toBe(false);
|
||||
expect(isAppleEmailVerified("True")).toBe(false);
|
||||
expect(isAppleEmailVerified("1")).toBe(false);
|
||||
expect(isAppleEmailVerified(1)).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -16,3 +16,18 @@ export function validateUserInfo(
|
||||
): OAuthUserInfo {
|
||||
return OAuthUserInfoSchema.validateSync(userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apple emits the `email_verified` claim as either a boolean or its string
|
||||
* representation ("true"/"false"). A naive `!!value` coerces the string "false"
|
||||
* into `true`, which would let an UNVERIFIED Apple email satisfy the account-merge
|
||||
* verification gate in `handleOAuthEmailMergeStrategy` and silently link into an
|
||||
* existing account (account takeover). Treat only a real `true` or the exact
|
||||
* string "true" as verified; anything else (including "false") is unverified.
|
||||
*
|
||||
* Shared between the web provider (`providers/apple.tsx`) and the native sign-in
|
||||
* route so the two can never drift apart again.
|
||||
*/
|
||||
export function isAppleEmailVerified(value: unknown): boolean {
|
||||
return value === true || value === "true";
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user