86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
import { createAuthClient } from 'better-auth/client'
|
|
import { apiUrl } from './env'
|
|
|
|
// Better Auth browser client. It targets the worker's catch-all `/api/auth/**`
|
|
// routes (generated from src/auth.ts in the functions package). Pass the FULL
|
|
// auth base (`<apiUrl>/auth`): better-auth's withPath only appends its default
|
|
// `/api/auth` when baseURL has no path, and apiUrl() already carries `/api`, so
|
|
// a bare apiUrl() would leave the client calling `/api/get-session` (404) and
|
|
// the app would loop back to /login. Cookies ride every request so the session
|
|
// round-trips across origins.
|
|
export const authClient = createAuthClient({ baseURL: `${apiUrl()}/auth` })
|
|
|
|
export type AuthSession = {
|
|
user?: {
|
|
email?: string | null
|
|
name?: string | null
|
|
image?: string | null
|
|
} | null
|
|
expires?: string
|
|
}
|
|
|
|
// Thrown by signInWithPassword when the email/password pair is wrong.
|
|
export const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS'
|
|
// Thrown by registerWithPassword when the email is already taken.
|
|
export const EMAIL_IN_USE = 'EMAIL_IN_USE'
|
|
|
|
export async function getAuthSession(): Promise<AuthSession> {
|
|
const { data } = await authClient.getSession()
|
|
if (!data?.user) {
|
|
return {}
|
|
}
|
|
return {
|
|
user: {
|
|
email: data.user.email,
|
|
name: data.user.name ?? null,
|
|
image: data.user.image ?? null,
|
|
},
|
|
expires: data.session?.expiresAt
|
|
? new Date(data.session.expiresAt).toISOString()
|
|
: undefined,
|
|
}
|
|
}
|
|
|
|
// Sign in with an email and password. On success Better Auth sets the session
|
|
// cookie; on bad credentials this throws INVALID_CREDENTIALS.
|
|
export async function signInWithPassword(
|
|
email: string,
|
|
password: string,
|
|
_redirectPath = '/app',
|
|
) {
|
|
const { error } = await authClient.signIn.email({ email, password })
|
|
if (error) {
|
|
throw new Error(INVALID_CREDENTIALS)
|
|
}
|
|
}
|
|
|
|
// Create a new account. Better Auth signs the user in on success (sets the
|
|
// session cookie), so they land logged in.
|
|
export async function registerWithPassword(
|
|
email: string,
|
|
password: string,
|
|
options: { name?: string; redirectPath?: string } = {},
|
|
) {
|
|
const { error } = await authClient.signUp.email({
|
|
email,
|
|
password,
|
|
// Better Auth requires a name; fall back to the local-part of the email.
|
|
name: options.name?.trim() || email.split('@')[0],
|
|
})
|
|
|
|
if (error) {
|
|
// Better Auth returns 422 (UNPROCESSABLE_ENTITY) when the email is taken.
|
|
if (error.status === 422 || /exist|taken/i.test(error.message ?? '')) {
|
|
throw new Error(EMAIL_IN_USE)
|
|
}
|
|
throw new Error('Unable to create account')
|
|
}
|
|
}
|
|
|
|
export async function signOut() {
|
|
const { error } = await authClient.signOut()
|
|
if (error) {
|
|
throw new Error('Unable to sign out')
|
|
}
|
|
}
|