revert: remove mobile auth cookie fallback (#4902)
This commit is contained in:
parent
93e4a3b48f
commit
93c1fc3de6
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue