Refactor auth client to use official better-auth createAuthClient#1061
Refactor auth client to use official better-auth createAuthClient#1061
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…lient - Install better-auth as dependency in @object-ui/auth - Rewrite createAuthClient.ts to delegate to better-auth/client createAuthClient - Add resolveAuthURL helper to support both relative and absolute baseURLs - Update MSW authHandlers to use better-auth endpoint name (forget-password) - Update createAuthClient tests for new better-auth backed implementation - All 81 existing tests pass, TypeScript compiles cleanly Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
- Simplify window origin detection into separate getWindowOrigin helper - Define proper ForgetPasswordFn type for better-auth method access - Throw error instead of returning empty AuthUser on updateUser failure - Replace nested ternary with if-else in test URL extraction Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
@copilot 13:57:10.313 ../../packages/components build: �[96msrc/ui/drawer.tsx�[0m:�[93m27�[0m:�[93m7�[0m - �[91merror�[0m�[90m TS2742: �[0mThe inferred type of 'DrawerTrigger' cannot be named without a reference to '.pnpm/@radix-ui+react-dialog@1.1.15_@types+react-dom@19.2.3_@types+react@19.2.14__@types+reac_779045218dc2799d336e7197abef9d38/node_modules/@radix-ui/react-dialog'. This is likely not portable. A type annotation is necessary. |
These TS2742 (drawer.tsx, sidebar.tsx) and TS2339 (chart.tsx) errors are pre-existing in The |
There was a problem hiding this comment.
Pull request overview
This PR replaces the custom HTTP-based createAuthClient in the @object-ui/auth package with a thin adapter wrapping the official better-auth/client createAuthClient. The existing AuthClient interface is preserved, so all downstream consumers (AuthProvider, AuthGuard, useAuth, forms, createAuthenticatedFetch) require no changes. A companion change renames the mock MSW handler's forgot-password route to forget-password to match the better-auth convention.
Changes:
createAuthClientnow delegates tobetter-auth/client, with aresolveAuthURLhelper to split a base URL into theorigin/basePathpair required by the official client.- The
forgot-passwordMSW mock handler and related tests are renamed toforget-passwordto match better-auth's convention. better-authis added as an explicitdependencyinpackages/auth/package.jsonand the lock file is updated accordingly.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
packages/auth/src/createAuthClient.ts |
Core rewrite: delegates all auth methods to better-auth/client via adapter, adds resolveAuthURL/getWindowOrigin helpers |
packages/auth/src/__tests__/createAuthClient.test.ts |
Tests updated with a routing-aware createMockFetch helper; each test creates its own client instance |
packages/auth/package.json |
Adds better-auth ^1.5.4 as a runtime dependency |
pnpm-lock.yaml |
Lock file updated to include the new better-auth dependency |
apps/console/src/mocks/authHandlers.ts |
Renames /forgot-password handler to /forget-password to match better-auth convention |
apps/console/src/__tests__/authHandlers.test.ts |
Test updated to call /forget-password |
packages/auth/README.md |
Updates Quick Start example to use baseURL (removing old provider/apiUrl config) |
ROADMAP.md |
Updates entry to reflect the forget-password endpoint name |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| // exists at runtime but is not present in the default TS types. | ||
| type ForgetPasswordFn = (opts: { email: string; redirectTo: string }) => | ||
| Promise<{ error: { message?: string; status: number } | null }>; | ||
| const forgetPw = (betterAuth as unknown as { forgetPassword: ForgetPasswordFn }).forgetPassword; |
There was a problem hiding this comment.
The forgetPw variable (extracted from the cast betterAuth object) is called directly on line 119 without checking whether it is actually defined. If betterAuth.forgetPassword does not exist at runtime (e.g., on a version of better-auth that doesn't expose this method, or before the email plugin is configured), calling forgetPw(...) will throw a TypeError: forgetPw is not a function instead of a proper auth error. A null/undefined guard should be added: if forgetPw is falsy, throw an explicit Error('forgotPassword is not available in the current better-auth configuration') before attempting the call.
| const forgetPw = (betterAuth as unknown as { forgetPassword: ForgetPasswordFn }).forgetPassword; | |
| const forgetPw = (betterAuth as unknown as { forgetPassword?: ForgetPasswordFn }).forgetPassword; | |
| if (!forgetPw || typeof forgetPw !== 'function') { | |
| throw new Error('forgotPassword is not available in the current better-auth configuration'); | |
| } |
| type ForgetPasswordFn = (opts: { email: string; redirectTo: string }) => | ||
| Promise<{ error: { message?: string; status: number } | null }>; | ||
| const forgetPw = (betterAuth as unknown as { forgetPassword: ForgetPasswordFn }).forgetPassword; | ||
| const { error } = await forgetPw({ email, redirectTo: '/' }); |
There was a problem hiding this comment.
The redirectTo value sent to the better-auth forgetPassword endpoint is hardcoded as '/'. This URL is embedded by the server into the password-reset email link (e.g. https://your-app.com/?token=...), which means users clicking the link will be directed to the root page instead of the actual reset-password route (e.g. /reset-password). The forgotPassword method's signature in AuthClient only accepts email, so there is no way for consumers to provide the correct redirect URL. Either the forgotPassword signature should be extended to accept an optional redirectTo parameter, or a sensible default (e.g. /reset-password) should be used rather than '/'.
| const { error } = await forgetPw({ email, redirectTo: '/' }); | |
| const { error } = await forgetPw({ email, redirectTo: '/reset-password' }); |
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | ||
| import { createAuthClient } from '../createAuthClient'; | ||
| import type { AuthClient } from '../types'; |
There was a problem hiding this comment.
beforeEach and the AuthClient type are imported but never used in this test file after the refactor. The beforeEach was previously used to share client and mockFetch setup, but now each test creates its own local variables. These unused imports should be removed to keep the file clean and avoid TypeScript/linter warnings.
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | |
| import { createAuthClient } from '../createAuthClient'; | |
| import type { AuthClient } from '../types'; | |
| import { describe, it, expect, vi } from 'vitest'; | |
| import { createAuthClient } from '../createAuthClient'; |
| it('creates a client with all expected methods', () => { | ||
| const { mockFn } = createMockFetch({}); | ||
| const client = createAuthClient({ baseURL: 'http://localhost/api/auth', fetchFn: mockFn }); |
There was a problem hiding this comment.
The resolveAuthURL function contains a non-trivial branch for handling relative URLs (falling back to window.location.origin in browsers and http://localhost in other environments), but this behavior is not covered by any test. The existing tests all use an absolute URL (http://localhost/api/auth), so the relative-URL path is never exercised. A test using a relative base URL (e.g. '/api/v1/auth') should be added to verify that the function correctly assembles the origin and basePath in a browser-like environment.
Replace the custom HTTP-based
createAuthClientwith the officialbetter-auth/clientcreateAuthClient, eliminating manual request handling and type mismatches while unlocking the full better-auth feature set (OAuth, magic links, session hooks, plugins).Core change
createAuthClientnow delegates tobetter-auth/clientinternally via an adapter that preserves the existingAuthClientinterface — all consumers (AuthProvider, AuthGuard, useAuth, forms, createAuthenticatedFetch) are unchanged.Changes
packages/auth/src/createAuthClient.ts— Rewrote to wrapbetter-auth/clientcreateAuthClient, withresolveAuthURL()to support both relative (/api/v1/auth) and absolute URLspackages/auth/package.json— Addedbetter-authdependencyapps/console/src/mocks/authHandlers.ts— Updated endpoint from/forgot-password→/forget-password(better-auth convention)createAuthClient.test.ts,authHandlers.test.ts, README, ROADMAPNotes
sessionnot in signIn type but present at runtime), so controlledunknowncasts bridge between type systemsforgetPassword(better-auth spelling) exists at runtime but not in default TS types — accessed via a typed castOriginal prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.