chore: server-and-serverless template
This commit is contained in:
85
apps/app/src/lib/auth.ts
Normal file
85
apps/app/src/lib/auth.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
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')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user