diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 357f14b05..3474a1c4d 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -14,7 +14,7 @@
"web": "expo start --web"
},
"dependencies": {
- "@better-auth/expo": "1.0.23-beta.2",
+ "@better-auth/expo": "1.1.8",
"@expo/vector-icons": "^14.0.2",
"@follow/components": "workspace:*",
"@follow/constants": "workspace:*",
@@ -30,7 +30,7 @@
"@react-navigation/native": "^7.0.0",
"@shopify/flash-list": "1.7.1",
"@tanstack/react-query": "5.62.3",
- "better-auth": "1.1.3",
+ "better-auth": "1.1.8",
"cookie-es": "^1.2.2",
"dayjs": "1.11.13",
"es-toolkit": "1.29.0",
diff --git a/apps/renderer/src/modules/profile/account-management.tsx b/apps/renderer/src/modules/profile/account-management.tsx
index e1933e11e..438e14642 100644
--- a/apps/renderer/src/modules/profile/account-management.tsx
+++ b/apps/renderer/src/modules/profile/account-management.tsx
@@ -1,47 +1,73 @@
import { Button } from "@follow/components/ui/button/index.js"
import { authProvidersConfig } from "@follow/constants"
-import { linkSocial } from "@follow/shared/auth"
+import { linkSocial, unlinkAccount } from "@follow/shared/auth"
import { IN_ELECTRON } from "@follow/shared/constants"
import { cn } from "@follow/utils/utils"
+import { useMutation } from "@tanstack/react-query"
import { useTranslation } from "react-i18next"
+import { toast } from "sonner"
-import { useSocialAccounts } from "~/queries/auth"
+import { auth, useSocialAccounts } from "~/queries/auth"
import { useAuthProviders } from "~/queries/users"
+function AuthProviderButton({ provider }: { provider: string }) {
+ const { t } = useTranslation("settings")
+ const { data: accounts } = useSocialAccounts()
+ const unlinkAccountMutation = useMutation({
+ mutationFn: async () => {
+ const res = await unlinkAccount({ providerId: provider })
+ if (res.error) throw new Error(res.error.message)
+ },
+ onSuccess: () => {
+ toast.success(t("profile.link_social.unlink.success"))
+ auth.getAccounts().invalidate()
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ })
+ const account = accounts?.find((account) => account.provider === provider)
+ if (!account && IN_ELECTRON) return null
+
+ return (
+
+ )
+}
+
export function AccountManagement() {
const { t } = useTranslation("settings")
- const { data: accounts } = useSocialAccounts()
+
const { data: providers } = useAuthProviders()
return (
-
Auth Providers
+
{t("profile.link_social.auth_provider")}
{Object.keys(providers || {})
.filter((provider) => provider !== "credential")
- .map((provider) => {
- const account = accounts?.find((account) => account.provider === provider)
- if (!account && IN_ELECTRON) return null
- return (
-
- )
- })}
+ .map((provider) => (
+
+ ))}
{t("profile.link_social.description")}
diff --git a/locales/settings/en.json b/locales/settings/en.json
index 3bbbc4603..00d371b0d 100644
--- a/locales/settings/en.json
+++ b/locales/settings/en.json
@@ -278,7 +278,10 @@
"profile.email.verified": "Verified",
"profile.handle.description": "Your unique identifier.",
"profile.handle.label": "Handle",
+ "profile.link_social.auth_provider": "Auth Provider",
"profile.link_social.description": "You can currently only connect social accounts with the same email.",
+ "profile.link_social.link": "Link",
+ "profile.link_social.unlink.success": "Social account unlinked.",
"profile.name.description": "Your public display name.",
"profile.name.label": "Display Name",
"profile.new_password.label": "New Password",
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 500269ae1..227641303 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -34,7 +34,7 @@
"@electron-toolkit/tsconfig": "^1.0.1",
"@hono/node-server": "1.13.7",
"@t3-oss/env-core": "^0.11.1",
- "better-auth": "1.1.3",
+ "better-auth": "1.1.8",
"drizzle-orm": "0.37.0",
"hono": "4.6.13",
"react": "^18.3.1",
diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts
index 5c34753a9..bb8d23564 100644
--- a/packages/shared/src/auth.ts
+++ b/packages/shared/src/auth.ts
@@ -51,6 +51,7 @@ export const {
signIn,
signOut,
signUp,
+ unlinkAccount,
updateUser,
} = authClient
diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts
index 047b9a387..c7ece5908 100644
--- a/packages/shared/src/hono.ts
+++ b/packages/shared/src/hono.ts
@@ -6843,9 +6843,18 @@ declare const auth: {
}>]>(...ctx: C): Promise;
path: "/sign-up/email";
options: {
@@ -7476,6 +7503,9 @@ declare const auth: {
}>]>(...ctx: C): Promise;
path: "/sign-in/email";
options: {
@@ -7732,6 +7759,7 @@ declare const auth: {
token: string;
callbackURL?: string | undefined;
}>;
+ use: better_call.Endpoint, better_call.EndpointOptions>[];
metadata: {
openapi: {
description: string;
@@ -7762,6 +7790,18 @@ declare const auth: {
asResponse: true;
}] ? Response : void | {
status: boolean;
+ user: {
+ id: any;
+ email: any;
+ name: any;
+ image: any;
+ emailVerified: any;
+ createdAt: any;
+ updatedAt: any;
+ };
+ } | {
+ status: boolean;
+ user: null;
}>;
path: "/verify-email";
options: {
@@ -7776,6 +7816,7 @@ declare const auth: {
token: string;
callbackURL?: string | undefined;
}>;
+ use: better_call.Endpoint, better_call.EndpointOptions>[];
metadata: {
openapi: {
description: string;
@@ -8148,6 +8189,15 @@ declare const auth: {
asResponse: true;
}] ? Response : {
token: string | null;
+ user: {
+ id: string;
+ email: string;
+ name: string;
+ image: string | null | undefined;
+ emailVerified: boolean;
+ createdAt: Date;
+ updatedAt: Date;
+ };
}>;
path: "/change-password";
options: {
@@ -8603,6 +8653,7 @@ declare const auth: {
}, {
callbackURL: string;
}>;
+ use: better_call.Endpoint, better_call.EndpointOptions>[];
metadata: {
openapi: {
description: string;
@@ -8638,6 +8689,7 @@ declare const auth: {
}, {
callbackURL: string;
}>;
+ use: better_call.Endpoint, better_call.EndpointOptions>[];
metadata: {
openapi: {
description: string;
@@ -9338,6 +9390,10 @@ declare const auth: {
}] ? Response : {
id: string;
provider: string;
+ createdAt: Date;
+ updatedAt: Date;
+ accountId: string;
+ scopes: string[];
}[]>;
path: "/list-accounts";
options: {
@@ -9410,6 +9466,7 @@ declare const auth: {
token: string;
callbackURL?: string | undefined;
}>;
+ use: better_call.Endpoint, better_call.EndpointOptions>[];
}>]>(...ctx: C): Promise;
+ use: better_call.Endpoint, better_call.EndpointOptions>[];
+ };
+ method: better_call.Method | better_call.Method[];
+ headers: Headers;
+ };
+ unlinkAccount: {
+ ;
+ use: better_call.Endpoint & {
+ id: string;
+ createdAt: Date;
+ updatedAt: Date;
+ userId: string;
+ expiresAt: Date;
+ token: string;
+ ipAddress?: string | null | undefined;
+ userAgent?: string | null | undefined;
+ };
+ user: Record & {
+ id: string;
+ email: string;
+ emailVerified: boolean;
+ name: string;
+ createdAt: Date;
+ updatedAt: Date;
+ image?: string | null | undefined;
+ };
+ };
+ }>, better_call.EndpointOptions>[];
+ }>]>(...ctx: C): Promise;
+ path: "/unlink-account";
+ options: {
+ method: "POST";
+ body: zod.ZodObject<{
+ providerId: zod.ZodString;
+ }, "strip", zod.ZodTypeAny, {
+ providerId: string;
+ }, {
+ providerId: string;
+ }>;
+ use: better_call.Endpoint & {
+ id: string;
+ createdAt: Date;
+ updatedAt: Date;
+ userId: string;
+ expiresAt: Date;
+ token: string;
+ ipAddress?: string | null | undefined;
+ userAgent?: string | null | undefined;
+ };
+ user: Record & {
+ id: string;
+ email: string;
+ emailVerified: boolean;
+ name: string;
+ createdAt: Date;
+ updatedAt: Date;
+ image?: string | null | undefined;
+ };
+ };
+ }>, better_call.EndpointOptions>[];
};
method: better_call.Method | better_call.Method[];
headers: Headers;
@@ -9878,6 +10011,8 @@ declare const auth: {
EMAIL_CAN_NOT_BE_UPDATED: string;
CREDENTIAL_ACCOUNT_NOT_FOUND: string;
SESSION_EXPIRED: string;
+ FAILED_TO_UNLINK_LAST_ACCOUNT: string;
+ ACCOUNT_NOT_FOUND: string;
};
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f2722f17c..9544ad507 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -395,8 +395,8 @@ importers:
apps/mobile:
dependencies:
'@better-auth/expo':
- specifier: 1.0.23-beta.2
- version: 1.0.23-beta.2(better-auth@1.1.3(encoding@0.1.13))
+ specifier: 1.1.8
+ version: 1.1.8(better-auth@1.1.8(encoding@0.1.13))
'@expo/vector-icons':
specifier: ^14.0.2
version: 14.0.4
@@ -443,8 +443,8 @@ importers:
specifier: 5.62.3
version: 5.62.3(react@18.3.1)
better-auth:
- specifier: 1.1.3
- version: 1.1.3(encoding@0.1.13)
+ specifier: 1.1.8
+ version: 1.1.8(encoding@0.1.13)
cookie-es:
specifier: ^1.2.2
version: 1.2.2
@@ -1361,8 +1361,8 @@ importers:
specifier: ^0.11.1
version: 0.11.1(typescript@5.7.2)(zod@3.23.8)
better-auth:
- specifier: 1.1.3
- version: 1.1.3(encoding@0.1.13)
+ specifier: 1.1.8
+ version: 1.1.8(encoding@0.1.13)
drizzle-orm:
specifier: 0.37.0
version: 0.37.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.14)(expo-sqlite@15.0.3(qc23ies2iywwyrf5w5kjvn5iki))(kysely@0.27.5)(react@18.3.1)
@@ -2191,13 +2191,13 @@ packages:
resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==}
engines: {node: '>=6.9.0'}
- '@better-auth/expo@1.0.23-beta.2':
- resolution: {integrity: sha512-K0gxAAwwB8aubVhuKYBk60xG6qmzg6ZCHirdCrzG30fGIrBsuTSg4f/BAWb6OaSqmM+ujWe74ZpUi/XXDgRKXw==}
+ '@better-auth/expo@1.1.8':
+ resolution: {integrity: sha512-7sHPYrliz/l4fAPveOdlI8UEEE2To3U0qc6mzMQCovMiIdy6Nh/SEktPULmRk3gbFytAceEce8BI3ZR773a0AA==}
peerDependencies:
- better-auth: 1.0.23-beta.2
+ better-auth: 1.1.8
- '@better-auth/utils@0.2.2':
- resolution: {integrity: sha512-YlOin3pX3sc8VPRdzCOWqz6uHKitwxm/k8p6/2I1JYI0hgMWmEqqismyvEjtOKoiSTnu1huwSq+yzcN7ehu59w==}
+ '@better-auth/utils@0.2.3':
+ resolution: {integrity: sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==}
'@better-fetch/fetch@1.1.12':
resolution: {integrity: sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA==}
@@ -6500,14 +6500,14 @@ packages:
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
- better-auth@1.1.3:
- resolution: {integrity: sha512-8zUOd5zGAQLBJXupDiwAUzreLf2Msu1HVtkRWQk09hvHURZZIiuH8g48WIRXraNNoeqIA+bW70uap5Fxnj8d1w==}
+ better-auth@1.1.8:
+ resolution: {integrity: sha512-8E9SH8PglrbMu2b5ue9c9/fQvvycIJJ+Z6+STtGJAAMljXtDWb4KUVbb/VNIAiGa0khMExgC3d3VSMreTsDB8Q==}
better-call@0.2.15:
resolution: {integrity: sha512-IVT8Mraf+eu6hyCJNMyFVMWhvsmNyHjM4K2h7pZg1LEwZUkTpsfjIghjTq6YiGaooMUmTLK1C9eCEYMM2obW9w==}
- better-call@0.3.3-beta.4:
- resolution: {integrity: sha512-txigUTFsjOlE4dfkfhDhu6Gk0I1Xo3S4Nye+nfMKJXknb+WsymcWtqoRUSUGdhOMumM3LT9R3QttTUxwny9lWQ==}
+ better-call@0.3.3:
+ resolution: {integrity: sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA==}
better-opn@3.0.2:
resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==}
@@ -13876,7 +13876,7 @@ packages:
'@microsoft/api-extractor': ^7.36.0
'@swc/core': ^1
postcss: ^8.4.12
- typescript: 5.6.3
+ typescript: '>=4.5.0'
peerDependenciesMeta:
'@microsoft/api-extractor':
optional: true
@@ -14822,6 +14822,9 @@ packages:
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
+ zod@3.24.1:
+ resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
+
zustand@5.0.2:
resolution: {integrity: sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==}
engines: {node: '>=12.20.0'}
@@ -15801,13 +15804,13 @@ snapshots:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
- '@better-auth/expo@1.0.23-beta.2(better-auth@1.1.3(encoding@0.1.13))':
+ '@better-auth/expo@1.1.8(better-auth@1.1.8(encoding@0.1.13))':
dependencies:
- better-auth: 1.1.3(encoding@0.1.13)
+ better-auth: 1.1.8(encoding@0.1.13)
better-call: 0.2.15
zod: 3.23.8
- '@better-auth/utils@0.2.2':
+ '@better-auth/utils@0.2.3':
dependencies:
uncrypto: 0.1.3
@@ -21375,20 +21378,20 @@ snapshots:
before-after-hook@2.2.3: {}
- better-auth@1.1.3(encoding@0.1.13):
+ better-auth@1.1.8(encoding@0.1.13):
dependencies:
- '@better-auth/utils': 0.2.2
+ '@better-auth/utils': 0.2.3
'@better-fetch/fetch': 1.1.12
'@noble/ciphers': 0.6.0
'@noble/hashes': 1.6.1
'@simplewebauthn/browser': 13.0.0
'@simplewebauthn/server': 13.0.0(encoding@0.1.13)
- better-call: 0.3.3-beta.4
+ better-call: 0.3.3
defu: 6.1.4
jose: 5.9.6
kysely: 0.27.5
uncrypto: 0.1.3
- zod: 3.23.8
+ zod: 3.24.1
transitivePeerDependencies:
- encoding
@@ -21400,13 +21403,12 @@ snapshots:
uncrypto: 0.1.3
zod: 3.23.8
- better-call@0.3.3-beta.4:
+ better-call@0.3.3:
dependencies:
'@better-fetch/fetch': 1.1.12
rou3: 0.5.1
- set-cookie-parser: 2.7.1
uncrypto: 0.1.3
- zod: 3.23.8
+ zod: 3.24.1
better-opn@3.0.2:
dependencies:
@@ -31279,6 +31281,8 @@ snapshots:
zod@3.23.8: {}
+ zod@3.24.1: {}
+
zustand@5.0.2(@types/react@18.3.14)(immer@10.1.1(patch_hash=og7mbnoo5vh43tjlw5rbmrdbvu))(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)):
optionalDependencies:
'@types/react': 18.3.14