Skip to content

Commit 319216e

Browse files
authored
feat: one time token (#3482)
* feat: unified auth for desktop and ssr * feat: use one time token * feat: remove better auth patch * feat(desktop): handle session changes * feat(desktop): recover ck auth for compatibility
1 parent 05a22d3 commit 319216e

File tree

17 files changed

+725
-614
lines changed

17 files changed

+725
-614
lines changed

apps/desktop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"update:main-hash": "tsx plugins/vite/generate-main-hash.ts"
3434
},
3535
"dependencies": {
36+
"cookie-es": "2.0.0",
3637
"react-google-recaptcha": "3.1.0",
3738
"react-google-recaptcha-v3": "1.10.1"
3839
},

apps/desktop/src/main/src/index.ts

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -140,41 +140,6 @@ function bootstrap() {
140140
registerUpdater()
141141
registerAppTray()
142142

143-
// handle session cookie when sign in with email in electron
144-
session.defaultSession.webRequest.onHeadersReceived(
145-
{
146-
urls: [
147-
`${apiURL}/better-auth/sign-in/email`,
148-
`${apiURL}/better-auth/sign-in/email?*`,
149-
`${apiURL}/better-auth/two-factor/verify-totp`,
150-
`${apiURL}/better-auth/two-factor/verify-totp?*`,
151-
],
152-
},
153-
(detail, callback) => {
154-
const { responseHeaders } = detail
155-
if (responseHeaders?.["set-cookie"]) {
156-
const cookies = responseHeaders["set-cookie"] as string[]
157-
cookies.forEach((cookie) => {
158-
const cookieObj = parse(cookie, { decode: (value) => value })
159-
Object.keys(cookieObj).forEach((name) => {
160-
const value = cookieObj[name]
161-
mainWindow.webContents.session.cookies.set({
162-
url: apiURL,
163-
name,
164-
value,
165-
secure: true,
166-
httpOnly: true,
167-
domain: new URL(apiURL).hostname,
168-
sameSite: "no_restriction",
169-
})
170-
})
171-
})
172-
}
173-
174-
callback({ cancel: false, responseHeaders })
175-
},
176-
)
177-
178143
app.on("open-url", (_, url) => {
179144
if (mainWindow && !mainWindow.isDestroyed()) {
180145
if (mainWindow.isMinimized()) mainWindow.restore()
@@ -232,29 +197,36 @@ function bootstrap() {
232197
const urlObj = new URL(url)
233198

234199
if (urlObj.hostname === "auth" || urlObj.pathname === "//auth") {
235-
const ck = urlObj.searchParams.get("ck")
236-
const userId = urlObj.searchParams.get("userId")
237-
238-
if (ck && apiURL) {
239-
setBetterAuthSessionCookie(ck)
240-
const cookie = parse(atob(ck), { decode: (value) => value })
241-
Object.keys(cookie).forEach((name) => {
242-
const value = cookie[name]
243-
mainWindow.webContents.session.cookies.set({
244-
url: apiURL,
245-
name,
246-
value,
247-
secure: true,
248-
httpOnly: true,
249-
domain: new URL(apiURL).hostname,
250-
sameSite: "no_restriction",
200+
const token = urlObj.searchParams.get("token")
201+
202+
if (token) {
203+
await callWindowExpose(mainWindow).applyOneTimeToken(token)
204+
} else {
205+
// compatible with old version of ssr, should be removed in 0.4.4
206+
const ck = urlObj.searchParams.get("ck")
207+
const userId = urlObj.searchParams.get("userId")
208+
209+
if (ck && apiURL) {
210+
setBetterAuthSessionCookie(ck)
211+
const cookie = parse(atob(ck), { decode: (value) => value })
212+
Object.keys(cookie).forEach((name) => {
213+
const value = cookie[name]
214+
mainWindow.webContents.session.cookies.set({
215+
url: apiURL,
216+
name,
217+
value,
218+
secure: true,
219+
httpOnly: true,
220+
domain: new URL(apiURL).hostname,
221+
sameSite: "no_restriction",
222+
})
251223
})
252-
})
253224

254-
userId && (await callWindowExpose(mainWindow).clearIfLoginOtherAccount(userId))
255-
mainWindow.reload()
225+
userId && (await callWindowExpose(mainWindow).clearIfLoginOtherAccount(userId))
226+
mainWindow.reload()
256227

257-
updateNotificationsToken()
228+
updateNotificationsToken()
229+
}
258230
}
259231
} else {
260232
handleUrlRouting(url)

apps/desktop/src/renderer/src/hooks/biz/useSignOut.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { setWhoami } from "~/atoms/user"
55
import { QUERY_PERSIST_KEY } from "~/constants"
66
import { signOut } from "~/lib/auth"
77
import { tipcClient } from "~/lib/client"
8+
import { handleSessionChanges } from "~/queries/auth"
89
import { clearLocalPersistStoreData } from "~/store/utils/clear"
910

1011
export const useSignOut = () =>
@@ -30,6 +31,6 @@ export const useSignOut = () =>
3031
])
3132
// Sign out
3233
await signOut().then(() => {
33-
window.location.reload()
34+
handleSessionChanges()
3435
})
3536
}, [])
Lines changed: 7 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,22 @@
1-
import { IN_ELECTRON } from "@follow/shared"
1+
import { Auth } from "@follow/shared/auth"
22
import { env } from "@follow/shared/env.desktop"
3-
import type { authPlugins } from "@follow/shared/hono"
4-
import type { BetterAuthClientPlugin } from "better-auth/client"
5-
import { inferAdditionalFields, twoFactorClient } from "better-auth/client/plugins"
6-
import { createAuthClient } from "better-auth/react"
73

8-
import { WEB_URL } from "~/constants/env"
9-
10-
type AuthPlugin = (typeof authPlugins)[number]
11-
const serverPlugins = [
12-
{
13-
id: "customGetProviders",
14-
$InferServerPlugin: {} as Extract<AuthPlugin, { id: "customGetProviders" }>,
15-
},
16-
{
17-
id: "customCreateSession",
18-
$InferServerPlugin: {} as Extract<AuthPlugin, { id: "customCreateSession" }>,
19-
},
20-
{
21-
id: "getAccountInfo",
22-
$InferServerPlugin: {} as Extract<AuthPlugin, { id: "getAccountInfo" }>,
23-
},
24-
inferAdditionalFields({
25-
user: {
26-
handle: {
27-
type: "string",
28-
required: false,
29-
},
30-
},
31-
}),
32-
] satisfies BetterAuthClientPlugin[]
33-
34-
const authClient = createAuthClient({
35-
baseURL: `${env.VITE_API_URL}/better-auth`,
36-
plugins: [...serverPlugins, twoFactorClient()],
4+
const auth = new Auth({
5+
apiURL: env.VITE_API_URL,
6+
webURL: env.VITE_WEB_URL,
377
})
388

399
// @keep-sorted
4010
export const {
4111
changeEmail,
4212
changePassword,
43-
createSession,
4413
forgetPassword,
4514
getAccountInfo,
4615
getProviders,
4716
getSession,
4817
linkSocial,
4918
listAccounts,
19+
oneTimeToken,
5020
resetPassword,
5121
sendVerificationEmail,
5222
signIn,
@@ -55,33 +25,6 @@ export const {
5525
twoFactor,
5626
unlinkAccount,
5727
updateUser,
58-
} = authClient
59-
60-
export type LoginRuntime = "browser" | "app"
61-
export const loginHandler = async (
62-
provider: string,
63-
runtime?: LoginRuntime,
64-
args?: {
65-
email?: string
66-
password?: string
67-
headers?: Record<string, string>
68-
},
69-
) => {
70-
const { email, password, headers } = args ?? {}
71-
if (IN_ELECTRON && provider !== "credential") {
72-
window.open(`${WEB_URL}/login?provider=${provider}`)
73-
} else {
74-
if (provider === "credential") {
75-
if (!email || !password) {
76-
window.location.href = "/login"
77-
return
78-
}
79-
return signIn.email({ email, password }, { headers })
80-
}
28+
} = auth.authClient
8129

82-
signIn.social({
83-
provider: provider as "google" | "github" | "apple",
84-
callbackURL: runtime === "app" ? `${WEB_URL}/login` : WEB_URL,
85-
})
86-
}
87-
}
30+
export const { loginHandler } = auth

apps/desktop/src/renderer/src/modules/auth/Form.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
FormMessage,
99
} from "@follow/components/ui/form/index.js"
1010
import { Input } from "@follow/components/ui/input/Input.js"
11+
import type { LoginRuntime } from "@follow/shared/auth"
1112
import { env } from "@follow/shared/env.desktop"
1213
import { zodResolver } from "@hookform/resolvers/zod"
1314
import { useRef } from "react"
@@ -18,8 +19,8 @@ import { toast } from "sonner"
1819
import { z } from "zod"
1920

2021
import { useCurrentModal, useModalStack } from "~/components/ui/modal/stacked/hooks"
21-
import type { LoginRuntime } from "~/lib/auth"
2222
import { loginHandler, signUp, twoFactor } from "~/lib/auth"
23+
import { handleSessionChanges } from "~/queries/auth"
2324

2425
import { TOTPForm } from "../profile/two-factor"
2526

@@ -71,14 +72,14 @@ export function LoginWithPassword({ runtime }: { runtime: LoginRuntime }) {
7172
}
7273
}}
7374
onSuccess={() => {
74-
window.location.reload()
75+
handleSessionChanges()
7576
}}
7677
/>
7778
)
7879
},
7980
})
8081
} else {
81-
window.location.reload()
82+
handleSessionChanges()
8283
}
8384
}
8485

@@ -188,7 +189,7 @@ function RegisterForm() {
188189
callbackURL: "/",
189190
fetchOptions: {
190191
onSuccess() {
191-
window.location.reload()
192+
handleSessionChanges()
192193
},
193194
onError(context) {
194195
toast.error(context.error.message)

apps/desktop/src/renderer/src/modules/auth/LoginModalContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import {
88
TooltipPortal,
99
TooltipTrigger,
1010
} from "@follow/components/ui/tooltip/index.js"
11+
import type { LoginRuntime } from "@follow/shared/auth"
1112
import { clsx } from "@follow/utils/utils"
1213
import { m } from "framer-motion"
1314
import { useTranslation } from "react-i18next"
1415

1516
import { useCurrentModal } from "~/components/ui/modal/stacked/hooks"
16-
import type { LoginRuntime } from "~/lib/auth"
1717
import { loginHandler } from "~/lib/auth"
1818
import { useAuthProviders } from "~/queries/users"
1919

apps/desktop/src/renderer/src/pages/settings/(settings)/profile.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { TwoFactor } from "~/modules/profile/two-factor"
1212
import { UpdatePasswordForm } from "~/modules/profile/update-password-form"
1313
import { SettingsTitle } from "~/modules/settings/title"
1414
import { defineSettingPageData } from "~/modules/settings/utils"
15+
import { handleSessionChanges } from "~/queries/auth"
1516

1617
const iconName = "i-mgc-user-setting-cute-re"
1718
const priority = 1090
@@ -55,7 +56,7 @@ export function Component() {
5556
variant="outline"
5657
onClick={async () => {
5758
await signOut()
58-
window.location.reload()
59+
handleSessionChanges()
5960
}}
6061
>
6162
Delete

apps/desktop/src/renderer/src/providers/extension-expose-provider.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import { setUpdaterStatus } from "~/atoms/updater"
1111
import { useDialog, useModalStack } from "~/components/ui/modal/stacked/hooks"
1212
import { useDiscoverRSSHubRouteModal } from "~/hooks/biz/useDiscoverRSSHubRoute"
1313
import { useFollow } from "~/hooks/biz/useFollow"
14+
import { oneTimeToken } from "~/lib/auth"
1415
import { usePresentUserProfileModal } from "~/modules/profile/hooks"
1516
import { useSettingModal } from "~/modules/settings/modal/use-setting-modal"
17+
import { handleSessionChanges } from "~/queries/auth"
1618
import { clearDataIfLoginOtherAccount } from "~/store/utils/clear"
1719

1820
declare module "@follow/components/providers/stable-router-provider.js" {
@@ -42,6 +44,11 @@ export const ExtensionExposeProvider = () => {
4244
clearIfLoginOtherAccount(newUserId: string) {
4345
clearDataIfLoginOtherAccount(newUserId)
4446
},
47+
async applyOneTimeToken(token: string) {
48+
await oneTimeToken.apply({ token })
49+
handleSessionChanges()
50+
},
51+
4552
readyToUpdate() {
4653
setUpdaterStatus({
4754
type: "renderer",

apps/desktop/src/renderer/src/queries/auth.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,7 @@ export const useSession = (options?: { enabled?: boolean }) => {
6565
: "unknown",
6666
}
6767
}
68+
69+
export const handleSessionChanges = () => {
70+
window.location.reload()
71+
}

apps/mobile/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
"@tanstack/react-query-persist-client": "5.73.3",
4848
"@types/qrcode": "1.5.5",
4949
"better-auth": "1.2.6",
50-
"cookie-es": "2.0.0",
5150
"dayjs": "1.11.13",
5251
"dnum": "2.14.0",
5352
"es-toolkit": "1.34.1",

0 commit comments

Comments
 (0)