feat(backend): add BetterAuth email/password authentication

- Add PostgreSQL connection (src/db.ts)
- Configure BetterAuth with email/password (src/auth/index.ts)
- Add session middleware for route protection (src/auth/session-middleware.ts)
- Add registerAuthHandlers for mounting auth routes (src/auth/http.ts)
- Rename index.ts to server.ts
- Add .env.example with required environment variables

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2026-01-25 16:21:00 +00:00
parent fffcccc227
commit c10c8a553a
9 changed files with 128 additions and 9 deletions

View File

@@ -0,0 +1,7 @@
import type { Hono } from "hono"
import { auth } from "./index.ts"
export function registerAuthHandlers(app: Hono): void {
app.on(["POST", "GET"], "/api/auth/*", (c) => auth.handler(c.req.raw))
}

View File

@@ -0,0 +1,10 @@
import { betterAuth } from "better-auth"
import { pool } from "../db.ts"
export const auth = betterAuth({
database: pool,
emailAndPassword: {
enabled: true,
},
})

View File

@@ -0,0 +1,54 @@
import type { Context, Next } from "hono"
import { auth } from "./index.ts"
type SessionUser = typeof auth.$Infer.Session.user
type Session = typeof auth.$Infer.Session.session
export interface SessionVariables {
user: SessionUser | null
session: Session | null
}
/**
* Middleware that attaches session and user to the context.
* Does not reject unauthenticated requests - use requireSession for that.
*/
export async function sessionMiddleware(c: Context, next: Next): Promise<void> {
const session = await auth.api.getSession({ headers: c.req.raw.headers })
if (session) {
c.set("user", session.user)
c.set("session", session.session)
} else {
c.set("user", null)
c.set("session", null)
}
await next()
}
/**
* Middleware that requires a valid session. Returns 401 if not authenticated.
*/
export async function requireSession(c: Context, next: Next): Promise<Response | void> {
const session = await auth.api.getSession({ headers: c.req.raw.headers })
if (!session) {
return c.json({ error: "Unauthorized" }, 401)
}
c.set("user", session.user)
c.set("session", session.session)
await next()
}
/**
* Get session from headers. Useful for WebSocket upgrade validation.
*/
export async function getSessionFromHeaders(
headers: Headers,
): Promise<{ user: SessionUser; session: Session } | null> {
const session = await auth.api.getSession({ headers })
return session
}

View File

@@ -0,0 +1,5 @@
import { Pool } from "pg"
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
})

View File

@@ -1,9 +1,13 @@
import { Hono } from "hono"
import { registerAuthHandlers } from "./auth/http.ts"
const app = new Hono()
app.get("/health", (c) => c.json({ status: "ok" }))
registerAuthHandlers(app)
export default {
port: 3000,
fetch: app.fetch,