# 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
```