You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

11 KiB

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

// 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
// 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(),
});
// 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
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

// 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
// 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
git add server/service/auth/index.ts package.json bun.lock
git commit -m "feat: implement auth service for username-password flow"

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

// 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
// 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 };
// 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:

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

<!-- expected -->
<!-- form uses username field (not email), submits to /api/auth/login -->
<!-- success message based on actual API response -->
  • 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
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}`;
<UFormField label="用户名" name="username" required>
  <UInput v-model="state.username" autocomplete="username" />
</UFormField>
  • 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
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

- 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
// app/pages/register/index.vue (core submit)
await $fetch("/api/auth/register", { method: "POST", body: state });
await navigateTo("/login");
<!-- fields -->
<UFormField label="用户名" name="username" required />
<UFormField label="密码" name="password" required />
  • 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
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
# 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
git add .
git commit -m "feat: implement username-password auth with cookie sessions"
  • Step 5: Record delivered scope
Delivered only:
- register/login/logout/me
- username/password auth
- HttpOnly cookie session
Not delivered:
- forgot password, oauth, captcha, sms, email auth