Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate to better auth #1951

Merged
merged 12 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 { 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 @@ -62,7 +62,7 @@
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(async () => {

Check warning on line 65 in apps/main/src/index.ts

View workflow job for this annotation

GitHub Actions / auto-fix

Async arrow function has no 'await' expression

Check warning on line 65 in apps/main/src/index.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
Expand Down Expand Up @@ -189,24 +189,16 @@
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
Loading