Skip to content

Commit

Permalink
feat: migrate to better auth (#1951)
Browse files Browse the repository at this point in the history
* feat: add better-auth

* feat: remove hono/auth-js

* fix: authention check

* fix: signout refetch

* feat: init server auth plugins

* feat: remove types/authjs

* feat: server auth create session plugin

* feat: web login returning

* fix: types

* fix: test env
  • Loading branch information
DIYgod authored Dec 3, 2024
1 parent d78f7cd commit 9102203
Show file tree
Hide file tree
Showing 39 changed files with 4,927 additions and 913 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ on:
push:
branches: [main, dev]

env:
VITE_WEB_URL: ${{ vars.VITE_WEB_URL }}
VITE_API_URL: ${{ vars.VITE_API_URL }}

name: CI Format, Typecheck and Lint
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-lint
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ If you prefer to develop in Electron, follow these steps:
pnpm run dev
```

> **Tip:** If you encounter login issues, copy the `authjs.session-token` from your browser's cookies into the app.
> **Tip:** If you encounter login issues, copy the `better-auth.session_token` from your browser's cookies into the app.
## Contribution Guidelines

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ pnpm run dev
Since it is not very convenient to develop in Electron, the first way to develop and contribute is recommended at this stage.

> [!TIP]
> If you can't log in to the app, copy the `authjs.session-token` in the cookie from your browser into the app.
> If you can't log in to the app, copy the `better-auth.session_token` in the cookie from your browser into the app.
## 📝 License

Expand Down
1 change: 0 additions & 1 deletion apps/main/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import "../../types/vite"
import "../../types/authjs"

declare global {
const GIT_COMMIT_HASH: string
Expand Down
22 changes: 7 additions & 15 deletions apps/main/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { updateProxy } from "./lib/proxy"
import { handleUrlRouting } from "./lib/router"
import { store } from "./lib/store"
import { registerAppTray } from "./lib/tray"
import { setAuthSessionToken, updateNotificationsToken } from "./lib/user"
import { setBetterAuthSessionCookie, updateNotificationsToken } from "./lib/user"
import { registerUpdater } from "./updater"
import { cleanupOldRender } from "./updater/hot-updater"
import {
Expand Down Expand Up @@ -189,24 +189,16 @@ function bootstrap() {
const urlObj = new URL(url)

if (urlObj.hostname === "auth" || urlObj.pathname === "//auth") {
const token = urlObj.searchParams.get("token")
const ck = urlObj.searchParams.get("ck")
const userId = urlObj.searchParams.get("userId")

if (token && apiURL) {
setAuthSessionToken(token)
if (ck && apiURL) {
setBetterAuthSessionCookie(ck)
const cookie = atob(ck)
mainWindow.webContents.session.cookies.set({
url: apiURL,
name: "authjs.session-token",
value: token,
secure: true,
httpOnly: true,
domain: new URL(apiURL).hostname,
sameSite: "no_restriction",
})
mainWindow.webContents.session.cookies.set({
url: apiURL,
name: "authjs.callback-url",
value: env.VITE_WEB_URL,
name: cookie.split("=")[0],
value: cookie.split("=")[1],
secure: true,
httpOnly: true,
domain: new URL(apiURL).hostname,
Expand Down
10 changes: 5 additions & 5 deletions apps/main/src/lib/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { hc } from "hono/client"
import { ofetch } from "ofetch"

import { logger } from "../logger"
import { getAuthSessionToken, getUser } from "./user"
import { getBetterAuthSessionCookie, getUser } from "./user"

const abortController = new AbortController()
export const apiFetch = ofetch.create({
Expand All @@ -14,8 +14,8 @@ export const apiFetch = ofetch.create({
signal: abortController.signal,
retry: false,
onRequest({ request }) {
const authSessionToken = getAuthSessionToken()
if (!authSessionToken) {
const betterAuthSessionCookie = getBetterAuthSessionCookie()
if (!betterAuthSessionCookie) {
abortController.abort()
return
}
Expand All @@ -32,12 +32,12 @@ export const apiFetch = ofetch.create({
export const apiClient = hc<AppType>("", {
fetch: async (input, options = {}) => apiFetch(input.toString(), options),
headers() {
const authSessionToken = getAuthSessionToken()
const betterAuthSessionCookie = getBetterAuthSessionCookie()
const user = getUser()
return {
"X-App-Version": PKG.version,
"X-App-Dev": process.env.NODE_ENV === "development" ? "1" : "0",
Cookie: authSessionToken ? `authjs.session-token=${authSessionToken}` : "",
Cookie: betterAuthSessionCookie ? atob(betterAuthSessionCookie) : "",
"User-Agent": `Follow/${PKG.version}${user?.id ? ` uid: ${user.id}` : ""}`,
}
},
Expand Down
26 changes: 16 additions & 10 deletions apps/main/src/lib/user.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { User } from "@auth/core/types"
import type { Credentials } from "@eneris/push-receiver/dist/types"

import { logger } from "~/logger"

import { apiClient } from "./api-client"
import { store } from "./store"

const AuthKey = "authSessionToken"
export const setAuthSessionToken = (token: string) => store.set(AuthKey, token)
export const getAuthSessionToken = (): string | null => store.get(AuthKey)
export const cleanAuthSessionToken = () => store.set(AuthKey, null)
const BetterAuthKey = "betterAuthSessionCookie"
export const setBetterAuthSessionCookie = (cookie: string) => store.set(BetterAuthKey, cookie)
export const getBetterAuthSessionCookie = (): string | null => store.get(BetterAuthKey)
export const cleanBetterAuthSessionCookie = () => store.set(BetterAuthKey, null)

const UserKey = "user"
export const setUser = (user: User) => store.set(UserKey, JSON.stringify(user))
Expand All @@ -24,11 +26,15 @@ export const updateNotificationsToken = async (newCredentials?: Credentials) =>
}
const credentials = newCredentials || store.get("notifications-credentials")
if (credentials?.fcm?.token) {
await apiClient.messaging.$post({
json: {
token: credentials.fcm.token,
channel: "desktop",
},
})
try {
await apiClient.messaging.$post({
json: {
token: credentials.fcm.token,
channel: "desktop",
},
})
} catch (error) {
logger.error("updateNotificationsToken error: ", error)
}
}
}
6 changes: 3 additions & 3 deletions apps/main/src/tipc/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { cleanupOldRender, loadDynamicRenderEntry } from "~/updater/hot-updater"
import { isDev, isWindows11 } from "../env"
import { downloadFile } from "../lib/download"
import { i18n } from "../lib/i18n"
import { cleanAuthSessionToken, cleanUser } from "../lib/user"
import { cleanBetterAuthSessionCookie, cleanUser } from "../lib/user"
import type { RendererHandlers } from "../renderer-handlers"
import { quitAndInstall } from "../updater"
import { getMainWindow } from "../window"
Expand Down Expand Up @@ -167,8 +167,8 @@ export const appRoute = {
quitAndInstall()
}),

cleanAuthSessionToken: t.procedure.action(async () => {
cleanAuthSessionToken()
cleanBetterAuthSessionCookie: t.procedure.action(async () => {
cleanBetterAuthSessionCookie()
cleanUser()
}),
/// clipboard
Expand Down
2 changes: 0 additions & 2 deletions apps/renderer/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import "../../types/authjs"

import type { ElectronAPI } from "@electron-toolkit/preload"

declare global {
Expand Down
1 change: 0 additions & 1 deletion apps/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"@follow/shared": "workspace:*",
"@fontsource/sn-pro": "5.1.0",
"@headlessui/react": "2.2.0",
"@hono/auth-js": "1.0.15",
"@hookform/resolvers": "3.9.1",
"@lottiefiles/dotlottie-react": "0.10.1",
"@microflash/remark-callout-directives": "4.3.2",
Expand Down
6 changes: 4 additions & 2 deletions apps/renderer/src/atoms/user.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { User } from "@auth/core/types"
import type { UserRole } from "@follow/constants"
import type { AuthSession } from "@follow/shared/hono"
import { atom } from "jotai"

import { createAtomHooks } from "~/lib/jotai"

export const [, , useWhoami, , whoami, setWhoami] = createAtomHooks(atom<Nullable<User>>(null))
export const [, , useWhoami, , whoami, setWhoami] = createAtomHooks(
atom<Nullable<NonNullable<AuthSession>["user"]>>(null),
)

export const [, , useLoginModalShow, useSetLoginModalShow, getLoginModalShow, setLoginModalShow] =
createAtomHooks(atom<boolean>(false))
Expand Down
2 changes: 1 addition & 1 deletion apps/renderer/src/components/ui/media/SwipeMedia.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function SwipeMedia({
height: number
}
}) {
const uniqMedia = uniqBy(media, "url")
const uniqMedia = media ? uniqBy(media, "url") : []

const hoverRef = useRef<HTMLDivElement>(null)

Expand Down
14 changes: 7 additions & 7 deletions apps/renderer/src/hooks/biz/useSignOut.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { env } from "@follow/shared/env"
import { signOut } from "@follow/shared/auth"
import { clearStorage } from "@follow/utils/ns"
import { signOut } from "@hono/auth-js/react"
import { useCallback } from "react"

import { setWhoami } from "~/atoms/user"
import { isWebBuild, QUERY_PERSIST_KEY } from "~/constants"
import { QUERY_PERSIST_KEY } from "~/constants"
import { tipcClient } from "~/lib/client"
import { clearLocalPersistStoreData } from "~/store/utils/clear"

Expand All @@ -20,9 +19,10 @@ export const useSignOut = () =>
clearStorage()
window.analytics?.reset()
// clear local store data
await Promise.allSettled([clearLocalPersistStoreData(), tipcClient?.cleanAuthSessionToken()])
await Promise.allSettled([
clearLocalPersistStoreData(),
tipcClient?.cleanBetterAuthSessionCookie(),
])
// Sign out
await signOut({
callbackUrl: isWebBuild ? env.VITE_WEB_URL : undefined,
})
await signOut()
}, [])
8 changes: 4 additions & 4 deletions apps/renderer/src/initialize/helper.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { User } from "@auth/core/types"
import type { UserModel } from "@follow/models"

import { op } from "./op"

export const setIntegrationIdentify = async (user: User) => {
export const setIntegrationIdentify = async (user: UserModel) => {
op.identify({
profileId: user.id,
email: user.email,
avatar: user.image,
lastName: user.name,
avatar: user.image ?? undefined,
lastName: user.name ?? undefined,
properties: {
handle: user.handle,
name: user.name,
Expand Down
8 changes: 0 additions & 8 deletions apps/renderer/src/initialize/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { initializeDayjs } from "@follow/components/dayjs"
import { registerGlobalContext } from "@follow/shared/bridge"
import { IN_ELECTRON } from "@follow/shared/constants"
import { env } from "@follow/shared/env"
import { authConfigManager } from "@hono/auth-js/react"
import { repository } from "@pkg"
import { enableMapSet } from "immer"

Expand Down Expand Up @@ -63,12 +61,6 @@ export const initializeApp = async () => {
window.version = APP_VERSION

const now = Date.now()
// Initialize the auth config first
authConfigManager.setConfig({
baseUrl: env.VITE_API_URL,
basePath: "/auth",
credentials: "include",
})
initializeDayjs()
registerHistoryStack()

Expand Down
18 changes: 2 additions & 16 deletions apps/renderer/src/lib/api-fetch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { env } from "@follow/shared/env"
import type { AppType } from "@follow/shared/hono"
import { getCsrfToken } from "@hono/auth-js/react"
import PKG from "@pkg"
import { hc } from "hono/client"
import { FetchError, ofetch } from "ofetch"
Expand All @@ -13,27 +12,15 @@ import { isDev } from "~/constants"
import { NeedActivationToast } from "~/modules/activation/NeedActivationToast"
import { DebugRegistry } from "~/modules/debug/registry"

let csrfTokenPromise: Promise<string> | null = null

const getPromisedCsrfToken = async () => {
if (!csrfTokenPromise) {
csrfTokenPromise = getCsrfToken()
}

return await csrfTokenPromise
}
export const apiFetch = ofetch.create({
baseURL: env.VITE_API_URL,
credentials: "include",
retry: false,
onRequest: async ({ options }) => {
const csrfToken = await getPromisedCsrfToken()

onRequest: ({ options }) => {
const header = new Headers(options.headers)

header.set("x-app-version", PKG.version)
header.set("X-App-Dev", process.env.NODE_ENV === "development" ? "1" : "0")
header.set("X-Csrf-Token", csrfToken)
options.headers = header
},
onResponse() {
Expand Down Expand Up @@ -92,11 +79,10 @@ export const apiClient = hc<AppType>(env.VITE_API_URL, {
}
throw err
}),
async headers() {
headers() {
return {
"X-App-Version": PKG.version,
"X-App-Dev": process.env.NODE_ENV === "development" ? "1" : "0",
"X-Csrf-Token": await getPromisedCsrfToken(),
}
},
})
Expand Down
14 changes: 0 additions & 14 deletions apps/renderer/src/lib/auth.ts

This file was deleted.

4 changes: 2 additions & 2 deletions apps/renderer/src/modules/auth/LoginModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { FollowIcon } from "@follow/components/icons/follow.jsx"
import { MotionButtonBase } from "@follow/components/ui/button/index.js"
import { LoadingCircle } from "@follow/components/ui/loading/index.jsx"
import { authProvidersConfig } from "@follow/constants"
import type { LoginRuntime } from "@follow/shared/auth"
import { loginHandler } from "@follow/shared/auth"
import { stopPropagation } from "@follow/utils/dom"
import clsx from "clsx"
import { AnimatePresence, m } from "framer-motion"
Expand All @@ -10,8 +12,6 @@ import { useTranslation } from "react-i18next"

import { modalMontionConfig } from "~/components/ui/modal/stacked/constants"
import { useCurrentModal } from "~/components/ui/modal/stacked/hooks"
import type { LoginRuntime } from "~/lib/auth"
import { loginHandler } from "~/lib/auth"
import { useAuthProviders } from "~/queries/users"

interface LoginModalContentProps {
Expand Down
Loading

0 comments on commit 9102203

Please sign in to comment.