feat: add BetterAuth authentication

- Add better-auth to backend and frontend
- Create auth tables (users, sessions, accounts, verifications)
- Mount BetterAuth handler on /api/auth/*
- Protect GET /api/tasks with session auth
- Add login page with email/password
- Add invite route for creating users
- Add logout button to header
- Cross-subdomain cookies for .donovankelly.xyz
- Fix page title to 'Hammer Queue'
- Keep bearer token for admin mutations (separate from session auth)
- Update docker-compose with BETTER_AUTH_SECRET and COOKIE_DOMAIN
This commit is contained in:
2026-01-28 23:19:52 +00:00
parent 52b6190d43
commit 96d81520b9
16 changed files with 408 additions and 42 deletions

View File

@@ -3,8 +3,8 @@ import type { Task } from "./types";
const BASE = "/api/tasks";
export async function fetchTasks(): Promise<Task[]> {
const res = await fetch(BASE);
if (!res.ok) throw new Error("Failed to fetch tasks");
const res = await fetch(BASE, { credentials: "include" });
if (!res.ok) throw new Error(res.status === 401 ? "Unauthorized" : "Failed to fetch tasks");
return res.json();
}
@@ -15,6 +15,7 @@ export async function updateTask(
): Promise<Task> {
const res = await fetch(`${BASE}/${id}`, {
method: "PATCH",
credentials: "include",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
@@ -28,6 +29,7 @@ export async function updateTask(
export async function reorderTasks(ids: string[], token: string): Promise<void> {
const res = await fetch(`${BASE}/reorder`, {
method: "PATCH",
credentials: "include",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
@@ -43,6 +45,7 @@ export async function createTask(
): Promise<Task> {
const res = await fetch(BASE, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
@@ -56,6 +59,7 @@ export async function createTask(
export async function deleteTask(id: string, token: string): Promise<void> {
const res = await fetch(`${BASE}/${id}`, {
method: "DELETE",
credentials: "include",
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error("Failed to delete task");