# Registration Page 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:** Add a registration page at `/register` with username, password, confirm password, and SVG captcha. On success, redirect to `/login?registered=1`. **Architecture:** Server-side captcha generation via `svg-captcha` stored in an in-memory Map with 5-min TTL. Registration API validates input with Zod, verifies captcha, hashes password with bcryptjs, and inserts into the existing SQLite `users` table. Client-side uses Nuxt UI `UForm` with Zod schema validation and `useToast` for error feedback. **Tech Stack:** Nuxt 4.4, Nuxt UI 4.6, svg-captcha, bcryptjs (already installed), zod (already installed), Drizzle ORM + SQLite --- ### Task 1: Install svg-captcha **Files:** - Modify: `package.json` - [ ] **Step 1: Install the dependency** Run: `cd /home/dash/coding/nuxt-app && bun add svg-captcha` - [ ] **Step 2: Commit** ```bash git add package.json bun.lock git commit -m "chore: add svg-captcha dependency" ``` --- ### Task 2: Create captcha utility module **Files:** - Create: `server/utils/auth/captcha.ts` - [ ] **Step 1: Create the captcha utility** ```ts import svgCaptcha from 'svg-captcha' interface CaptchaRecord { text: string createdAt: number } const captchaStore = new Map() const CAPTCHA_TTL = 5 * 60 * 1000 function cleanupExpired(): void { const now = Date.now() for (const [token, record] of captchaStore) { if (now - record.createdAt > CAPTCHA_TTL) { captchaStore.delete(token) } } } export function generateCaptcha(): { token: string; svg: string } { cleanupExpired() const { text, data: svg } = svgCaptcha.create({ noise: 3, color: true, background: '#f8f9fa', }) const token = crypto.randomUUID() captchaStore.set(token, { text, createdAt: Date.now() }) return { token, svg } } export function verifyCaptcha(token: string, text: string): boolean { const record = captchaStore.get(token) if (!record) return false if (Date.now() - record.createdAt > CAPTCHA_TTL) { captchaStore.delete(token) return false } const match = record.text.toLowerCase() === text.toLowerCase() if (match) { captchaStore.delete(token) } return match } ``` - [ ] **Step 2: Commit** ```bash git add server/utils/auth/captcha.ts git commit -m "feat: add captcha generation and verification utility" ``` --- ### Task 3: Create registration validation schema **Files:** - Create: `server/utils/auth/validation.ts` - [ ] **Step 1: Create the validation module** ```ts import { z } from 'zod' export const registerSchema = z .object({ username: z .string() .min(3, '用户名至少需要3个字符') .max(30, '用户名最多30个字符'), password: z.string().min(8, '密码至少需要8个字符'), confirmPassword: z.string(), captchaToken: z.string().min(1, '验证码令牌不能为空'), captchaText: z.string().min(1, '验证码不能为空'), }) .refine((data) => data.password === data.confirmPassword, { message: '两次输入的密码不一致', path: ['confirmPassword'], }) ``` - [ ] **Step 2: Commit** ```bash git add server/utils/auth/validation.ts git commit -m "feat: add registration Zod validation schema" ``` --- ### Task 4: Create captcha API endpoint **Files:** - Create: `server/api/auth/captcha.get.ts` - [ ] **Step 1: Create the captcha GET endpoint** ```ts import { generateCaptcha } from '../../utils/auth/captcha' export default defineWrappedResponseHandler(async () => { const { token, svg } = generateCaptcha() return R.success({ token, svg }) }) ``` - [ ] **Step 2: Verify the endpoint works** Run the dev server: `cd /home/dash/coding/nuxt-app && bun run dev` Test: `curl -s http://localhost:3000/api/auth/captcha | head -c 200` Expected: JSON response with `{"code":0,"data":{"token":"","svg":"..."}}` - [ ] **Step 3: Commit** ```bash git add server/api/auth/captcha.get.ts git commit -m "feat: add GET /api/auth/captcha endpoint" ``` --- ### Task 5: Create registration API endpoint **Files:** - Create: `server/api/auth/register.post.ts` - [ ] **Step 1: Create the register POST endpoint** ```ts import { registerSchema } from '../../utils/auth/validation' import { verifyCaptcha } from '../../utils/auth/captcha' import { dbGlobal } from 'drizzle-pkg/lib/db' import { users } from 'drizzle-pkg/lib/schema/auth' import { eq } from 'drizzle-orm' import { hash } from 'bcryptjs' export default defineWrappedResponseHandler(async (event) => { const body = await readBody(event) const parsed = registerSchema.safeParse(body) if (!parsed.success) { return R.error(parsed.error.errors[0]?.message || '表单验证失败', null) } const { username, password, captchaToken, captchaText } = parsed.data if (!verifyCaptcha(captchaToken, captchaText)) { return R.error('验证码错误或已过期', null) } const existing = await dbGlobal .select() .from(users) .where(eq(users.username, username)) if (existing.length > 0) { return R.error('用户名已存在', null) } const hashedPassword = await hash(password, 10) const result = await dbGlobal .insert(users) .values({ username, password: hashedPassword, role: 'user', status: 'active', }) .returning({ id: users.id }) return R.success({ id: result[0].id, username }) }) ``` - [ ] **Step 2: Test registration with curl** ```bash # First get a captcha CAPTCHA=$(curl -s http://localhost:3000/api/auth/captcha) TOKEN=$(echo $CAPTCHA | jq -r '.data.token') # The captcha text is human-readable from the SVG — you'll need to decode it manually # For an automated test, just verify validation works: curl -s -X POST http://localhost:3000/api/auth/register \ -H 'Content-Type: application/json' \ -d "{\"username\":\"ab\",\"password\":\"12345\",\"confirmPassword\":\"12345\",\"captchaToken\":\"\",\"captchaText\":\"\"}" # Expected: {"code":1,"message":"用户名至少需要3个字符","data":null} ``` - [ ] **Step 3: Commit** ```bash git add server/api/auth/register.post.ts git commit -m "feat: add POST /api/auth/register endpoint" ``` --- ### Task 6: Create registration page component **Files:** - Create: `app/pages/register.vue` - [ ] **Step 1: Create the registration page** ```vue ``` - [ ] **Step 2: Commit** ```bash git add app/pages/register.vue git commit -m "feat: add registration page with captcha support" ``` --- ### Task 7: Wire header button to /register **Files:** - Modify: `app/layouts/default.vue:15` - [ ] **Step 1: Make the login/register button a link** Find the line: ```html ``` Replace with: ```html ``` - [ ] **Step 2: Stop dev server (if running) and run type check** Run: `cd /home/dash/coding/nuxt-app && bunx nuxi typecheck 2>&1 | tail -5` Expected: No new type errors. - [ ] **Step 3: Commit** ```bash git add app/layouts/default.vue git commit -m "feat: wire header login button to /register" ``` --- ### Task 8: End-to-end verification - [ ] **Step 1: Start dev server** ```bash cd /home/dash/coding/nuxt-app && bun run dev & ``` - [ ] **Step 2: Verify the page loads** Run: `curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/register` Expected: `200` - [ ] **Step 3: Verify captcha endpoint** Run: `curl -s http://localhost:3000/api/auth/captcha | python3 -m json.tool | head -10` Expected: Valid JSON with `code: 0`, `data.token`, `data.svg` - [ ] **Step 4: Verify registration validation (short username)** ```bash curl -s -X POST http://localhost:3000/api/auth/register \ -H 'Content-Type: application/json' \ -d '{"username":"ab","password":"12345678","confirmPassword":"12345678","captchaToken":"dummy","captchaText":"dummy"}' ``` Expected: `{"code":1,"message":"用户名至少需要3个字符","data":null}` - [ ] **Step 5: Verify registration validation (password mismatch)** ```bash curl -s -X POST http://localhost:3000/api/auth/register \ -H 'Content-Type: application/json' \ -d '{"username":"testuser","password":"12345678","confirmPassword":"different","captchaToken":"dummy","captchaText":"dummy"}' ``` Expected: `{"code":1,"message":"两次输入的密码不一致","data":null}` - [ ] **Step 6: Verify invalid captcha returns error** ```bash curl -s -X POST http://localhost:3000/api/auth/register \ -H 'Content-Type: application/json' \ -d '{"username":"testuser","password":"12345678","confirmPassword":"12345678","captchaToken":"invalid-token","captchaText":"abc"}' ``` Expected: `{"code":1,"message":"验证码错误或已过期","data":null}`