diff --git a/.gitignore b/.gitignore index 4a7f73a..2e236e6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ node_modules logs *.log +# Brainstorming mockups +.superpowers + # Misc .DS_Store .fleet diff --git a/docs/superpowers/specs/2026-05-15-registration-page-design.md b/docs/superpowers/specs/2026-05-15-registration-page-design.md new file mode 100644 index 0000000..8b1f792 --- /dev/null +++ b/docs/superpowers/specs/2026-05-15-registration-page-design.md @@ -0,0 +1,88 @@ +# Registration Page Design + +## Overview + +Add a registration page at `/register` with username, password, confirm password, and SVG captcha. On success, redirect to `/login` with a query param. Login page is out of scope. + +## Stack + +- Nuxt 4.4 + Nuxt UI 4.6 + Tailwind v4 +- Drizzle ORM + SQLite (users table already exists) +- bcryptjs (already installed), svg-captcha, zod (already installed) + +## Files + +### New + +| File | Purpose | +|---|---| +| `app/pages/register.vue` | Registration page component | +| `server/api/auth/captcha.get.ts` | Generate SVG captcha, return token + svg | +| `server/api/auth/register.post.ts` | Validate input, create user | +| `server/utils/auth/captcha.ts` | Server-side captcha generation/validation | +| `server/utils/auth/validation.ts` | Zod schemas for registration input | + +### Modified + +| File | Change | +|---|---| +| `app/layouts/default.vue` | Wire header button to `/register` | + +## API Endpoints + +### GET /api/auth/captcha + +Generates a captcha, stores token→text in a server-side Map (5-min expiry), returns the SVG and token. + +``` +Response: { code: 0, data: { token: "uuid", svg: "..." } } +``` + +### POST /api/auth/register + +``` +Request: { username, password, confirmPassword, captchaToken, captchaText } +Response: { code: 0, data: { id: 1, username: "foo" } } +``` + +Steps: +1. Zod validation (username 3-30, password 8+, passwords match) +2. Verify captcha: lookup token in Map, compare text case-insensitive, delete on match +3. Check username uniqueness in DB +4. bcryptjs hash password (salt rounds 10) +5. Insert into users table with role="user", status="active" +6. Return success — no auto-login + +## Data Flow + +1. Page mounts → GET /api/auth/captcha → render SVG +2. User submits → client Zod validation → POST /api/auth/register +3. On success → `navigateTo('/login?registered=1')` +4. On error → show toast, keep form state; if captcha fail, auto-refresh captcha + +## Error Handling + +Client-side: UForm + Zod validation for empty fields, length, password match. + +Server-side: + +| Case | Message | +|---|---| +| Zod validation fail | "密码至少需要8个字符" | +| Captcha wrong | "验证码错误" | +| Captcha expired | "验证码已过期" | +| Username taken | "用户名已存在" | +| Server error | "注册失败,请稍后重试" | + +Captcha is consumed (deleted from Map) on match to prevent replay. If registration fails after captcha check, a new captcha is fetched. + +## Form Fields + +- **Username** — required, 3-30 characters, unique in DB +- **Password** — required, min 8 characters, masked input with toggle +- **Confirm Password** — required, must match password +- **Captcha** — SVG image + text input, refresh button + +## Layout + +Card-centered form (380px max-width), single column. Header already has the "登录/注册" button. Form shows a "Already have an account? Log in" link at the bottom.