# Username Password Auth Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build only username/password register-login with HttpOnly Cookie session, plus logout and current-user APIs. **Architecture:** Keep auth logic in `server/service/auth/index.ts`, expose four thin API handlers under `server/api/auth/`, and persist sessions in PostgreSQL via Drizzle schema. Frontend only updates `app/pages/login/index.vue` and adds `app/pages/register/index.vue` to call real APIs; no extra auth features are added. **Tech Stack:** Nuxt 4 + Nitro server routes, Drizzle ORM (PostgreSQL), Bun scripts, Nuxt UI form components. --- ## File Structure Map - Modify: `packages/drizzle-pkg/database/pg/schema/auth.ts` (add `sessions` table) - Modify: `packages/drizzle-pkg/lib/schema/auth.ts` (export `sessions`) - Create: `server/api/auth/register.post.ts` - Create: `server/api/auth/login.post.ts` - Create: `server/api/auth/logout.post.ts` - Create: `server/api/auth/me.get.ts` - Modify: `server/service/auth/index.ts` (register/login/logout/me service functions) - Modify: `app/pages/login/index.vue` (username+password real API) - Create: `app/pages/register/index.vue` --- ### Task 1: Prepare DB session model **Files:** - Modify: `packages/drizzle-pkg/database/pg/schema/auth.ts` - Modify: `packages/drizzle-pkg/lib/schema/auth.ts` - Test: `packages/drizzle-pkg/database/pg/schema/auth.ts` (type-check + migration generation output) - [ ] **Step 1: Write the failing schema expectation** ```ts // Expected new table in auth schema: // export const sessions = pgTable("sessions", { ... }) // // Expected exported symbol: // export { users, sessions } from '../../database/pg/schema/auth' ``` - [ ] **Step 2: Verify current state is missing sessions** Run: `rg "sessions" packages/drizzle-pkg/database/pg/schema/auth.ts packages/drizzle-pkg/lib/schema/auth.ts` Expected: no `sessions` table export in current output. - [ ] **Step 3: Add minimal schema implementation** ```ts // packages/drizzle-pkg/database/pg/schema/auth.ts import { sql } from "drizzle-orm"; import { integer, pgTable, timestamp, varchar } from "drizzle-orm/pg-core"; export const sessions = pgTable("sessions", { id: varchar().primaryKey(), userId: integer().notNull(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), }); ``` ```ts // packages/drizzle-pkg/lib/schema/auth.ts export { users, sessions } from "../../database/pg/schema/auth"; ``` - [ ] **Step 4: Generate migration and verify** Run: `bun run db:generate -- auth-sessions` Expected: new migration created for `sessions` table with correct columns. - [ ] **Step 5: Commit** ```bash git add packages/drizzle-pkg/database/pg/schema/auth.ts packages/drizzle-pkg/lib/schema/auth.ts packages/drizzle-pkg/database/pg/migrations git commit -m "feat: add sessions schema for auth" ``` --- ### Task 2: Implement auth service (register/login/logout/me) **Files:** - Modify: `server/service/auth/index.ts` - Test: `server/service/auth/index.ts` (manual function-level behavior check via API handlers in next task) - [ ] **Step 1: Write the failing behavior checklist** ```ts // register(username, password) // - validates username /^[a-zA-Z0-9_]{3,20}$/ // - validates password length >= 6 // - throws on duplicate username // // login(username, password) // - validates credentials // - returns session payload (sessionId, expiresAt, user) // // logout(sessionId) // - deletes session record // // getMe(sessionId) // - returns null when missing/expired // - returns { id, username } when valid ``` - [ ] **Step 2: Confirm current service cannot satisfy checklist** Run: `rg "register|login|logout|getMe" server/service/auth/index.ts` Expected: these functions are absent. - [ ] **Step 3: Add minimal implementation** ```ts // server/service/auth/index.ts (key shape) export async function registerUser(input: { username: string; password: string }) { /* validate + insert */ } export async function loginUser(input: { username: string; password: string }) { /* verify + create session */ } export async function logoutUser(sessionId: string) { /* delete session */ } export async function getCurrentUser(sessionId: string) { /* validate session + fetch user */ } ``` Implementation notes in this step: - password hashing/comparison: `bun add bcryptjs` then use `hash` / `compare`. - session id generation: `crypto.randomUUID()`. - session expiry: `new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)`. - return only minimal user fields. - [ ] **Step 4: Run type check/dev build sanity** Run: `bun run build` Expected: build completes without TypeScript errors from auth service changes. - [ ] **Step 5: Commit** ```bash git add server/service/auth/index.ts package.json bun.lock git commit -m "feat: implement auth service for username-password flow" ``` --- ### Task 3: Add auth API routes with HttpOnly Cookie **Files:** - Create: `server/api/auth/register.post.ts` - Create: `server/api/auth/login.post.ts` - Create: `server/api/auth/logout.post.ts` - Create: `server/api/auth/me.get.ts` - Test: these four route files through local HTTP calls - [ ] **Step 1: Write failing API contract examples** ```ts // register.post.ts: accepts { username, password }, returns success json // login.post.ts: accepts { username, password }, sets cookie "pp_session" // me.get.ts: reads cookie, returns current user or unauthorized // logout.post.ts: clears cookie and invalidates session ``` - [ ] **Step 2: Verify routes do not yet exist** Run: `ls server/api/auth` Expected: folder/files missing (or missing required four handlers). - [ ] **Step 3: Implement handlers with thin controller pattern** ```ts // login.post.ts (core shape) const body = await readBody(event); const result = await loginUser(body); setCookie(event, "pp_session", result.sessionId, { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production", path: "/", maxAge: 60 * 60 * 24 * 7, }); return { code: 1, message: "登录成功", data: result.user }; ``` ```ts // me.get.ts (core shape) const sessionId = getCookie(event, "pp_session"); const user = await getCurrentUser(sessionId ?? ""); if (!user) throw createError({ statusCode: 401, statusMessage: "未登录" }); return { code: 1, message: "ok", data: user }; ``` - [ ] **Step 4: Run API smoke checks** Run: `bun run dev` then in another terminal: ```bash curl -i -X POST http://localhost:3000/api/auth/register \ -H "content-type: application/json" \ -d '{"username":"demo_user","password":"123456"}' curl -i -c cookie.txt -X POST http://localhost:3000/api/auth/login \ -H "content-type: application/json" \ -d '{"username":"demo_user","password":"123456"}' curl -i -b cookie.txt http://localhost:3000/api/auth/me curl -i -b cookie.txt -X POST http://localhost:3000/api/auth/logout ``` Expected: register/login/me/logout all return success on happy path; `Set-Cookie` appears on login. - [ ] **Step 5: Commit** ```bash git add server/api/auth git commit -m "feat: add auth api routes with cookie session" ``` --- ### Task 4: Update login page to username/password real login **Files:** - Modify: `app/pages/login/index.vue` - Test: login page manual browser check - [ ] **Step 1: Write failing UI expectation** ```vue ``` - [ ] **Step 2: Confirm current UI still email + mock delay** Run: `rg "email|setTimeout|you@example.com" app/pages/login/index.vue` Expected: current file still uses email and simulated success. - [ ] **Step 3: Implement minimal page changes** ```ts type LoginFormState = { username: string; password: string }; const res = await $fetch("/api/auth/login", { method: "POST", body: state }); resultType.value = "success"; resultMessage.value = `登录成功,欢迎 ${res.data.username}`; ``` ```vue ``` - [ ] **Step 4: Verify behavior in browser** Run: `bun run dev` and open `/login` Expected: validation matches username/password rules; successful login calls backend and shows success. - [ ] **Step 5: Commit** ```bash git add app/pages/login/index.vue git commit -m "feat: wire login page to username-password auth api" ``` --- ### Task 5: Add register page and complete acceptance checklist **Files:** - Create: `app/pages/register/index.vue` - Test: end-to-end acceptance via browser and curl - [ ] **Step 1: Write failing acceptance checklist** ```md - register success - duplicate username rejected - login success sets cookie - invalid credentials rejected - me returns user when logged in - logout clears session ``` - [ ] **Step 2: Confirm register page is missing** Run: `ls app/pages/register/index.vue` Expected: file not found. - [ ] **Step 3: Implement minimal register page** ```ts // app/pages/register/index.vue (core submit) await $fetch("/api/auth/register", { method: "POST", body: state }); await navigateTo("/login"); ``` ```vue ``` - [ ] **Step 4: Execute full acceptance checks** Run: - `bun run dev` - Browser: `/register` -> `/login` happy path - Browser/API: repeat register for same username should fail - `curl -b cookie.txt http://localhost:3000/api/auth/me` after logout should return unauthorized Expected: all six acceptance points pass exactly as spec defines. - [ ] **Step 5: Commit** ```bash git add app/pages/register/index.vue git commit -m "feat: add register page for username-password flow" ``` --- ### Task 6: Final verification and integration commit **Files:** - Modify: all files from Tasks 1-5 (no new scope) - Test: migration + build + manual auth flow - [ ] **Step 1: Run migration** Run: `bun run db:migrate` Expected: migrations apply successfully, including `sessions` table. - [ ] **Step 2: Run production build check** Run: `bun run build` Expected: build succeeds. - [ ] **Step 3: Re-run smoke auth flow** ```bash # happy path curl -i -X POST http://localhost:3000/api/auth/register -H "content-type: application/json" -d '{"username":"final_user","password":"123456"}' curl -i -c cookie.txt -X POST http://localhost:3000/api/auth/login -H "content-type: application/json" -d '{"username":"final_user","password":"123456"}' curl -i -b cookie.txt http://localhost:3000/api/auth/me curl -i -b cookie.txt -X POST http://localhost:3000/api/auth/logout curl -i -b cookie.txt http://localhost:3000/api/auth/me ``` Expected: first `me` success, final `me` unauthorized. - [ ] **Step 4: Create integration commit** ```bash git add . git commit -m "feat: implement username-password auth with cookie sessions" ``` - [ ] **Step 5: Record delivered scope** ```md Delivered only: - register/login/logout/me - username/password auth - HttpOnly cookie session Not delivered: - forgot password, oauth, captcha, sms, email auth ```