# 登入 / 註冊流程 — visionA Cloud > **Phase 0 雛形範圍**:僅做低保真 stub(表單 + 按鈕 + 路由),**不接後端認證**。使用者送出登入 / 註冊表單後直接跳轉到目標頁即可。 > > 完整 auth 流程(OAuth、2FA、密碼重設、Email 驗證、ToS / Privacy Policy、rate limit 等)**Phase 1+ 再設計**。 > > 本檔記錄 Phase 0 雛形規格 + Phase 1+ TODO。 --- ## 1. 雛形範圍決策 | 項目 | Phase 0 | Phase 1 TODO | |------|---------|-------------| | 登入表單 UI | ✅ 做 | 完整化 | | 註冊表單 UI | ✅ 做 | 完整化 | | 登入 API 串接 | ❌ 不做(直接跳轉)| 做 | | 註冊 API 串接 | ❌ 不做 | 做 | | OAuth(Google / GitHub)| ❌ 不做 | 做 | | 密碼強度驗證 | ❌ 不做 | 做 | | Email 驗證 | ❌ 不做 | 做 | | 密碼重設 | ❌ 不做 | 做 | | 2FA | ❌ 不做 | 做(Phase 2) | | ToS / Privacy Policy 勾選 | ❌ 不做 | 做 | | 社群登入 | ❌ 不做 | 做(Phase 2) | | Rate Limit 提示 | ❌ 不做 | 做 | | 帳號鎖定機制 | ❌ 不做 | 做 | --- ## 1.1 雛形 Auth 行為(三方一致) Phase 0 **不接後端認證**,三方已對齊行為如下(對應 Reviewer C3): | 項目 | Phase 0 行為 | |------|-------------| | 登入表單送出 | HTML5 驗證通過後 → `router.push('/')`,**不**打 API,**不**做身分驗證 | | 註冊表單送出 | 同上 → `router.push('/')` | | Session / Token | 不存(前端不發、不讀);重新整理依然可進任何頁面 | | 登出按鈕 | 點擊 → `router.push('/login')`,不清 session(本來就沒存) | | 受保護路由 | 雛形期所有 `/*` 路由對匿名使用者開放,**不做**導向 /login | ### 1.2 雛形 Banner(全域) 為了避免 demo 被誤認為正式版本,所有已登入的主畫面(`(main)/layout.tsx`)頂部顯示常駐 Banner: ``` ┌─────────────────────────────────────────────────────────────┐ │ 🚧 雛形版本 · 登入僅為 UI 示意,未實作身分驗證;資料皆為假資料 │ └─────────────────────────────────────────────────────────────┘ ``` **規格:** - 位置:`Header` 正下方,`sticky top-14 z-30`(在 `NetworkErrorBanner z-40` 之下,以利網路錯誤優先) - 樣式:`bg-amber-100 dark:bg-amber-900/40 text-amber-900 dark:text-amber-100 border-b border-amber-300` - 文字:`text-xs font-medium text-center py-1.5` - 圖示:`Construction` / `Wrench`(Lucide)`h-3.5 w-3.5` - 可關閉?**不可關閉**(雛形階段要讓使用者持續意識到) - `role="status"` + `aria-label="雛形版本提示"` - 響應式:Mobile 文字改為「🚧 雛形版本(demo)」縮短版 ### 1.3 i18n key(雛形 Banner) ``` prototype.banner.label → 雛形版本 · 登入僅為 UI 示意,未實作身分驗證;資料皆為假資料 prototype.banner.short → 雛形版本(demo) prototype.banner.ariaLabel → 雛形版本提示 ``` --- ## 2. 路由與 Layout ``` app/ ├── (auth)/ │ ├── layout.tsx ← 無 Sidebar / Header 的獨立 layout │ ├── login/page.tsx ← /login │ └── register/page.tsx ← /register ├── (main)/ │ ├── layout.tsx ← 有 Sidebar / Header 的主 layout(既有) │ ├── page.tsx ← / │ ├── devices/... │ └── ... └── layout.tsx ← root layout(ThemeSync / LangSync / Toaster) ``` **`(auth)/layout.tsx` 規格:** - 無 Sidebar、無 Header - 全螢幕 `min-h-screen`,`bg-background` - 內容置中:`flex items-center justify-center` - 無 padding wrapper(頁面自己處理) --- ## 3. `/login` — 登入頁 ### 3.1 視覺(Phase 0) ``` ┌────────────────────────────────────────────────────────┐ │ │ │ │ │ [Logo] visionA Cloud │ │ Edge AI Platform │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ Email │ │ │ │ ┌──────────────────────────────────┐ │ │ │ │ │ you@example.com │ │ │ │ │ └──────────────────────────────────┘ │ │ │ │ │ │ │ │ 密碼 │ │ │ │ ┌──────────────────────────────────┐ │ │ │ │ │ •••••••• │ │ │ │ │ └──────────────────────────────────┘ │ │ │ │ │ │ │ │ [ 登入 ] │ │ │ │ │ │ │ │ ──────── 還沒有帳號? ───────── │ │ │ │ │ │ │ │ [ 建立新帳號 ] │ │ │ └──────────────────────────────────────┘ │ │ │ │ 忘記密碼? | 語言:繁中 ▾ │ │ │ └────────────────────────────────────────────────────────┘ ``` ### 3.2 規格 - **容器**:`max-w-md w-full mx-auto px-4` - **品牌區**: - Logo:`h-12 w-12 rounded-lg` - 產品名:`text-2xl font-bold` - 副標題:`text-sm text-muted-foreground` - 區塊間距:`space-y-2 mb-8` - **Card**:既有 `Card` + `CardContent`,`py-8 px-6` - **表單區塊**:`space-y-4` - **Input**:既有 shadcn,`type="email"` / `type="password"` 搭配 `autoComplete` - **登入按鈕**:`w-full` + `variant=default` + `size=default` - **分隔線 + 建立帳號**: - `Separator` + 中間文字(使用 Tailwind 的 flex center hack) - 建立帳號 `Button variant=outline w-full` - **底部**: - 「忘記密碼?」:`Button variant=link size=sm`(disabled) - 語言切換:輕量化 Select(僅 2 選項) ### 3.3 行為(Phase 0 簡化) ```tsx async function handleLogin(e: FormEvent) { e.preventDefault(); // Phase 0: 不接 API,直接跳 dashboard // Phase 1: const res = await api.post('/auth/login', { email, password }) router.push('/'); } ``` - 表單用 HTML5 `required`(Phase 0) - 無 loading state(Phase 1+) - 無 error state(Phase 1+) - 送出直接跳 `/` ### 3.4 鍵盤行為 - Tab 順序:Email → Password → 登入 → 建立新帳號 → 忘記密碼 → 語言 - Email 或 Password focused 時 Enter → 提交表單 - 「建立新帳號」:Enter / Space 導航到 `/register` - 語言切換:Space 開啟 Select,Arrow 選擇,Enter 確認 ### 3.5 響應式 - Mobile:`px-4` 確保左右留白;其他結構不變 - Tablet / Desktop:置中,`max-w-md` 限寬 ### 3.6 i18n key ``` auth.login.title → 登入 auth.login.subtitle → 歡迎回到 visionA Cloud auth.login.email → Email auth.login.emailPlaceholder → you@example.com auth.login.password → 密碼 auth.login.submit → 登入 auth.login.submitting → 登入中... auth.login.forgotPassword → 忘記密碼? auth.login.noAccount → 還沒有帳號? auth.login.createAccount → 建立新帳號 ``` --- ## 4. `/register` — 註冊頁 ### 4.1 視覺(Phase 0) ``` ┌────────────────────────────────────────────────────────┐ │ │ │ [Logo] visionA Cloud │ │ Edge AI Platform │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ 建立帳號 │ │ │ │ │ │ │ │ Email │ │ │ │ [____________________________________] │ │ │ │ │ │ │ │ 密碼 │ │ │ │ [____________________________________] │ │ │ │ │ │ │ │ 確認密碼 │ │ │ │ [____________________________________] │ │ │ │ │ │ │ │ [ 建立帳號 ] │ │ │ │ │ │ │ │ ──────── 已經有帳號? ────────── │ │ │ │ │ │ │ │ [ 登入 ] │ │ │ └──────────────────────────────────────┘ │ │ │ │ 語言:繁中 ▾ │ │ │ └────────────────────────────────────────────────────────┘ ``` ### 4.2 規格差異(相對 /login) - 新增「確認密碼」欄位 - 「建立帳號」按鈕取代「登入」 - 底部連結改為「已經有帳號? → 登入」 ### 4.3 驗證規則(Phase 0 最小化) - Email:HTML5 `type="email" required` - 密碼:`required`,**不驗證強度** - 確認密碼:失去焦點時比對(client-side),不符則顯示紅框 + 下方文字 **簡單驗證範例:** ```tsx const [confirmError, setConfirmError] = useState(''); function handleConfirmBlur() { if (password && confirm && password !== confirm) { setConfirmError(t('auth.register.passwordMismatch')); } else { setConfirmError(''); } } ``` 顯示:`` + `

{confirmError}

` ### 4.4 行為(Phase 0 簡化) ```tsx async function handleRegister(e: FormEvent) { e.preventDefault(); if (password !== confirm) { setConfirmError(t('auth.register.passwordMismatch')); return; } // Phase 0: 不接 API,直接引導到 pairing router.push('/devices/pair'); } ``` **註冊成功後的導航**:跳 `/devices/pair` 引導第一次配對(避免使用者看到空白 Dashboard)。 ### 4.5 i18n key ``` auth.register.title → 建立帳號 auth.register.subtitle → 開始使用 visionA Cloud auth.register.email → Email auth.register.password → 密碼 auth.register.confirmPassword → 確認密碼 auth.register.passwordMismatch → 兩次輸入的密碼不一致 auth.register.submit → 建立帳號 auth.register.submitting → 建立中... auth.register.hasAccount → 已經有帳號? auth.register.signIn → 登入 ``` --- ## 5. 登入狀態管理(Phase 0 雛形) ### 5.1 `useAuthStore`(Zustand) **目的**:Phase 0 讓前端能在「未登入 / 已登入」兩個狀態間切換,即使後端沒接。 ```typescript interface AuthStore { user: { email: string; displayName?: string } | null; isAuthenticated: boolean; login: (email: string) => void; // Phase 0: 只存 localStorage logout: () => void; } ``` **Phase 0 簡化實作**: - `login(email)`:設 `user = { email }`、`isAuthenticated = true`、存 `localStorage` - `logout()`:清空、跳 `/login` - Hydration:頁面載入從 `localStorage` 恢復狀態 **Phase 1 升級**:改為真正的 JWT / Session cookie;`login` 會呼叫 API、存 token;`logout` 會呼叫 API 使 token 失效。 ### 5.2 路由保護(Phase 0 Middleware) **最簡版**:在 `(main)/layout.tsx` 或 Root 層加 hook: ```tsx 'use client'; function MainLayout({ children }) { const { isAuthenticated } = useAuthStore(); const router = useRouter(); useEffect(() => { if (!isAuthenticated) router.replace('/login'); }, [isAuthenticated]); // ... 顯示 Sidebar + Header + children } ``` **注意**:Phase 0 保護邏輯很弱(只是 client-side redirect),**不是真正的安全機制**。任何直接 API 請求都應該由後端驗證。 --- ## 6. 登出流程 **觸發點**: - Sidebar 底部 `UserMenu` → 「登出」 - `/account` 頁面(未來) - Session 過期自動觸發(Phase 1+) **行為(Phase 0)**: 1. `useAuthStore.logout()` 2. 清空 localStorage 的 `user` 3. 顯示 toast「已登出」 4. `router.push('/login')` **Phase 1+**: - 呼叫 `POST /api/auth/logout` 讓 token 失效 - 清除所有 cookies - 若有未儲存的資料,顯示確認 Dialog --- ## 7. 首次登入的引導 **使用者第一次登入完成後**(有個好方法:**從註冊頁進來的**): - 註冊後跳 `/devices/pair`(引導第一次配對) - Dashboard 的 `OnboardingDialog`(local-tool 既有版本假設 USB Dongle,**Phase 0 雲端版暫時 disable**) - Phase 1 做新的雲端版 Onboarding(「歡迎來到 visionA Cloud — 讓我們先配對你的第一台裝置」→ 跳 `/devices/pair`) --- ## 8. 錯誤情境(Phase 1 再實作) 雛形不做,但在這裡列出 Phase 1 要處理的情境: | 情境 | UI 回饋 | |------|---------| | 密碼錯誤 | Input 下方紅字「Email 或密碼不正確」(不明說哪個錯,避免 enumeration attack) | | Email 不存在 | 同上 | | 帳號被鎖(短時間內失敗太多次)| 顯示「此帳號暫時被鎖定,請 15 分鐘後再試」 | | Email 已被註冊(註冊時)| Input 下方紅字「此 Email 已被使用」 | | 密碼強度不足 | 密碼下方即時顯示強度指示器 | | 網路錯誤 | Toast「連線失敗,請稍後再試」 | | ToS 未勾選 | Button disabled + 紅字提示 | --- ## 9. 安全考量(給 Architect) 雛形不做,但 Phase 1 必須: - 密碼 hash:bcrypt / argon2 - Token:JWT(short-lived access + refresh)或 session cookie(httpOnly + secure + SameSite) - CSRF 保護 - XSS 防護(Content-Security-Policy header) - Rate Limit(失敗登入 N 次鎖定 X 分鐘) - 密碼政策(最少長度、需字母 + 數字等,視法規要求) - 記錄登入來源 IP / User-Agent(/account/sessions 頁面顯示) --- ## 10. 無障礙 - 所有表單 `