revert: remove mobile auth cookie fallback (#4902)

This commit is contained in:
DIYgod 2026-03-10 11:53:31 +08:00 committed by GitHub
parent 93e4a3b48f
commit 93c1fc3de6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 38 additions and 214 deletions

View File

@ -1,11 +1,10 @@
import { expoClient, getSetCookie, hasBetterAuthCookies } from "@better-auth/expo/client"
import { expoClient } from "@better-auth/expo/client"
import { baseAuthPlugins } from "@follow/shared/auth"
import { isNewUserQueryKey } from "@follow/store/user/constants"
import { whoamiQueryKey } from "@follow/store/user/hooks"
import { createMobileAPIHeaders } from "@follow/utils/headers"
import { useQuery } from "@tanstack/react-query"
import { createAuthClient } from "better-auth/react"
import { fetch as expoFetch } from "expo/fetch"
import { nativeApplicationVersion } from "expo-application"
import * as FileSystem from "expo-file-system/legacy"
import Storage from "expo-sqlite/kv-store"
@ -73,48 +72,10 @@ const plugins = [
}),
]
const updateCookieStorage = (serializedCookie: string) => {
try {
safeSecureStore.setItem(cookieKey, serializedCookie)
} catch (error) {
console.warn("SecureStore.setItem failed during auth cookie persistence:", error)
return false
}
const env = getEnvProfile()
try {
safeSecureStore.setItem(`${cookieKey}_${env}`, serializedCookie)
} catch {
// Keychain may be unavailable in background
}
bumpAuthStateRevision()
queryClient.invalidateQueries({ queryKey: whoamiQueryKey })
queryClient.invalidateQueries({ queryKey: isNewUserQueryKey })
return true
}
export const persistAuthCookieHeader = (setCookie: string | null | undefined) => {
if (!setCookie || !hasBetterAuthCookies(setCookie, "better-auth")) {
return false
}
let previousCookie: string | undefined
try {
previousCookie = safeSecureStore.getItem(cookieKey) ?? undefined
} catch (error) {
console.warn("SecureStore.getItem failed during auth cookie persistence:", error)
}
const serializedCookie = getSetCookie(setCookie, previousCookie)
return updateCookieStorage(serializedCookie)
}
export const authClient = createAuthClient({
baseURL: `${proxyEnv.API_URL}/better-auth`,
fetchOptions: {
cache: "no-store",
customFetchImpl: async (input, init) => expoFetch(input.toString(), init as any) as any,
// Learn more: https://better-fetch.vercel.app/docs/hooks
onRequest: async (ctx) => {
const headers = createMobileAPIHeaders({

View File

@ -1,4 +1,3 @@
import { userSyncService } from "@follow/store/user/store"
import { tracker } from "@follow/tracker"
import { zodResolver } from "@hookform/resolvers/zod"
import { useMutation } from "@tanstack/react-query"
@ -15,7 +14,7 @@ import { z } from "zod"
import { SubmitButton } from "@/src/components/common/SubmitButton"
import { PlainTextField } from "@/src/components/ui/form/TextField"
import { Text } from "@/src/components/ui/typography/Text"
import { authClient, persistAuthCookieHeader } from "@/src/lib/auth"
import { signIn, signUp } from "@/src/lib/auth"
import { useNavigation } from "@/src/lib/navigation/hooks"
import { Navigation } from "@/src/lib/navigation/Navigation"
import { toast } from "@/src/lib/toast"
@ -30,134 +29,6 @@ const formSchema = z.object({
})
type FormValue = z.infer<typeof formSchema>
const getAuthErrorMessage = (value: unknown) => {
if (!value || typeof value !== "object" || !("error" in value)) {
return
}
const { error } = value
if (!error || typeof error !== "object" || !("message" in error)) {
return
}
return typeof error.message === "string" ? error.message : undefined
}
const getAuthData = (value: unknown) => {
if (!value || typeof value !== "object" || !("data" in value)) {
return
}
return value.data && typeof value.data === "object" ? value.data : undefined
}
const getResponseSetCookie = (response: Response) => {
const directValue =
response.headers.get("x-better-auth-set-cookie") ??
response.headers.get("set-cookie") ??
response.headers.get("Set-Cookie")
if (directValue) {
return directValue
}
const rawHeaders = (response as Response & { _rawHeaders?: unknown })._rawHeaders
if (!Array.isArray(rawHeaders)) {
return null
}
const values = rawHeaders
.filter(
(header): header is [string, string] =>
Array.isArray(header) &&
header.length >= 2 &&
typeof header[0] === "string" &&
typeof header[1] === "string",
)
.filter(([key]) => key.toLowerCase() === "set-cookie")
.map(([, value]) => value)
return values.length > 0 ? values.join(", ") : null
}
const hasTwoFactorRedirect = (value: unknown) => {
const data = getAuthData(value)
if (data && "twoFactorRedirect" in data) {
return Boolean(data.twoFactorRedirect)
}
if (!value || typeof value !== "object" || !("response" in value)) {
return false
}
const { response } = value
if (!response || typeof response !== "object" || !("twoFactorRedirect" in response)) {
return false
}
return Boolean(response.twoFactorRedirect)
}
const requestCredentialAuth = async ({
path,
body,
}: {
path: "/sign-in/email" | "/sign-up/email"
body: Record<string, string>
}) => {
let setCookie: string | null = null
const result = await authClient.$fetch(path, {
method: "POST",
body,
headers: await getTokenHeaders(),
throw: false,
onResponse(context) {
setCookie = getResponseSetCookie(context.response)
},
})
const persistedCookie = setCookie ? persistAuthCookieHeader(setCookie) : false
return {
result,
persistedCookie,
}
}
const establishCredentialSession = async ({
email,
password,
onTwoFactorRedirect,
}: {
email: string
password: string
onTwoFactorRedirect?: () => void
}) => {
const { result, persistedCookie } = await requestCredentialAuth({
path: "/sign-in/email",
body: {
email,
password,
},
})
const errorMessage = getAuthErrorMessage(result)
if (errorMessage) {
throw new Error(errorMessage)
}
if (hasTwoFactorRedirect(result)) {
onTwoFactorRedirect?.()
return null
}
const session = persistedCookie ? await userSyncService.whoami().catch(() => null) : null
if (!session?.user?.id) {
return null
}
return session
}
async function onSubmit(values: FormValue) {
const result = formSchema.safeParse(values)
if (!result.success) {
@ -166,29 +37,37 @@ async function onSubmit(values: FormValue) {
return false
}
let session = null
try {
session = await establishCredentialSession({
email: result.data.email,
password: result.data.password,
onTwoFactorRedirect: () => {
Navigation.rootNavigation.presentControllerView(TwoFactorAuthScreen)
const res = await signIn.email(
{
email: result.data.email,
password: result.data.password,
},
})
{
headers: await getTokenHeaders(),
},
)
if (res.error) {
throw new Error(res.error.message)
}
// @ts-expect-error better-auth response type omits twoFactorRedirect
if (res.data?.twoFactorRedirect) {
Navigation.rootNavigation.presentControllerView(TwoFactorAuthScreen)
return false
}
} catch (error) {
Alert.alert(error instanceof Error ? error.message : "Unable to sign in")
return false
}
if (!session?.user?.id) {
return false
}
tracker.userLogin({
type: "email",
})
return true
}
export function EmailLogin() {
const { t } = useTranslation()
const [emailValue, setEmailValue] = useState("")
@ -314,44 +193,28 @@ export function EmailSignUp() {
})
const submitMutation = useMutation({
mutationFn: async (values: SignupFormValue) => {
try {
const { result, persistedCookie } = await requestCredentialAuth({
path: "/sign-up/email",
body: {
await signUp
.email(
{
email: values.email,
password: values.password,
name: values.email.split("@")[0] ?? "",
},
{
headers: await getTokenHeaders(),
},
)
.then((res) => {
if (res.error?.message) {
toast.error(res.error.message)
} else {
toast.success(i18next.t("login.sign_up_successful"))
tracker.register({
type: "email",
})
Navigation.rootNavigation.back()
}
})
const errorMessage = getAuthErrorMessage(result)
if (errorMessage) {
toast.error(errorMessage)
return false
}
let session = persistedCookie ? await userSyncService.whoami().catch(() => null) : null
if (!session?.user?.id) {
session = await establishCredentialSession({
email: values.email,
password: values.password,
})
}
if (!session?.user?.id) {
toast.error("Unable to establish session after sign up")
return false
}
toast.success(i18next.t("login.sign_up_successful"))
tracker.register({
type: "email",
})
return true
} catch (error) {
toast.error(error instanceof Error ? error.message : "Unable to sign up")
return false
}
},
})
const signup = handleSubmit((values) => {