2 changed files with 91 additions and 0 deletions
@ -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: "<svg>...</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. |
||||
Loading…
Reference in new issue