9.3 KiB
CFDivePlatform 專案審視:高/中優先待辦
審視日期:2026-10-14 範圍:後端授權、部署設定、程式結構、資料一致性。 低優先項目未列入本文(分頁、範本殘留檔、CHANGELOG 等),需要時另行整理。 搭配閱讀:《2026-06-11-spec-implementation-audit.md》。 實作原則:以下每一項只要涉及行為變更,都必須先走 OpenSpec(
/opsx:propose),AC 對應測試後才可實作。
總覽
| # | 優先 | 項目 | 是否走 OpenSpec | 建議 change 名稱 |
|---|---|---|---|---|
| H1 | 🔴 高 | member/provider 路由缺角色 middleware | 是 | add-role-middleware |
| H2 | 🔴 高 | 正式環境 APP_DEBUG 與預設值風險 |
否(設定確認)+ 視情況改 .env.example |
— |
| M1 | 🟡 中 | AuthController 過大、無 FormRequest/Service 層 | 是 | refactor-auth-controller |
| M2 | 🟡 中 | 預約名額邏輯重複 | 是 | extract-booking-service |
| M3 | 🟡 中 | 通知同步發送且例外被吞掉 | 是 | queue-booking-notifications |
| M4 | 🟡 中 | 前端 token 儲存不一致(admin 用 localStorage) | 是 | unify-frontend-token-storage |
| M5 | 🟡 中 | CORS 在正式環境仍允許 localhost | 是(小) | restrict-cors-by-env |
| M6 | 🟡 中 | CI 使用 SQLite,鎖定/超賣測試可能假綠 | 是 | ci-mysql-test-job |
建議順序:H1 → H2 → M6 → M5 → M3 → M2 → M1 → M4。 理由:H1 是安全漏洞;M6 先建立可信的測試基礎,後續重構(M1、M2)才有安全網。
H1 member/provider 路由缺角色 middleware
現況
routes/api.php 中 member 與 provider 兩個群組只有 auth:sanctum,沒有角色檢查;只有 admin 群組有 EnsureAdmin。
已確認的影響
- 任何已登入的 member 或 admin 可呼叫
POST /api/provider/offers、/provider/schedules、/provider/offers/{id}/images,provider_id會被設成呼叫者 id,產生非教練帳號的課程。 - 任何 provider 或 admin 可呼叫
POST /api/member/bookings建立預約。 - 目前只有
ReviewController、refresh 端點自己檢查角色,其餘端點無防護。
待辦
- 新增
EnsureMember、EnsureProvidermiddleware(仿app/Http/Middleware/EnsureAdmin.php),並在bootstrap/app.php註冊 alias - 套用到
member、provider路由群組 - 通知、即時訊息這類「member + provider 共用」群組維持現狀,但需確認 admin 是否應被排除
- 新增 Feature 測試(仿
tests/Feature/AdminEndpointAuthTest.php):每個角色 × 每組路由,期望 403 - 更新 spec:
provider-auth、admin-auth;視需要新增role-authorizationspec - 更新 Swagger 註解(
app/Docs/*)的 403 回應
驗收條件(草案,需轉為 spec Scenario)
- member 呼叫任一
/api/provider/*回 403 - provider 呼叫任一
/api/member/*回 403 - admin 呼叫
/api/member/*、/api/provider/*回 403 - 既有合法角色的行為不變(既有測試全綠)
安全確認(CLAUDE.md 必答)
- 權限檢查層級:middleware(路由層),Controller 內既有所有權檢查保留
- 未授權觸發可能性:修正後不可
H2 正式環境 APP_DEBUG 與預設值
現況
- 本機
.env:APP_ENV=local、APP_DEBUG=true .env.example:APP_DEBUG=true、APP_ENV=localdocker-compose.yml以env_file: .env注入容器
風險
若 VPS 上 .env 沿用相同值,錯誤頁會洩漏堆疊追蹤、環境變數與 SQL。
待辦
- 需要你確認:登入 VPS 檢查實際
.env的APP_DEBUG、APP_ENV(我無法從此環境確認) - 確認
config:cache後的實際值:docker compose exec app php artisan tinker --execute="dump(config('app.debug'));" .env.example加註解說明正式環境必須APP_DEBUG=false- 評估是否在 deploy.yml 加入啟動檢查:
APP_ENV=production且APP_DEBUG為 false,否則部署失敗 - 本機
.env中的 DB 密碼為弱密碼(cfdivepass),確認正式環境未使用
備註
這項大多是設定確認,不一定需要 OpenSpec change;若加入部署檢查則屬 env-cloud-annotations spec 範圍。
M1 AuthController 過大,缺 FormRequest 與 Service 層
現況
app/Http/Controllers/API/AuthController.php1136 行- 三種角色的 register/login/logout/profile/change-password/refresh 大量複製
app/Http/Requests、app/Services目錄不存在,驗證與業務邏輯都在 Controller- 已有
commonLogout、commonChangePassword等雛形,但refreshMember/refreshProvider/refreshAdmin幾乎相同
違反的 CLAUDE.md 規則 單一職責、Function 超過 20 行需說明、Controller 只處理資源、參數過多應封裝 DTO。
待辦
- 先補足現有行為的測試(
AuthLoginTest、AuthLockoutTest、TokenRefreshTest已有基礎),重構前先確認覆蓋率 - 拆分:依角色或依功能拆成多個 Controller(例如
MemberAuthController),共用邏輯抽成AuthService - 引入 FormRequest(
RegisterMemberRequest等) - 合併三個 refresh 方法
- 重構期間 API 路徑、回應格式、Swagger 輸出不得改變
- 重構完成後
app/Docs/AuthApiDoc.php(1261 行)註解位置需一併檢視
風險 改動面大,務必在 M6(CI MySQL)之後進行,並分多個小 PR。
M2 預約名額邏輯重複
現況
「取消後遞減 current_participants,若由 Full 變回 Open」的邏輯同時出現在:
MemberBookingController::destroyProviderBookingController::cancelAdminBookingController(需確認)
同時 MemberBookingController::store 約 70 行,包含驗證、鎖定、建立、通知。
待辦
- 抽出
BookingService(或CourseSchedule的releaseSpots()方法)集中名額增減與狀態轉換 - 確認所有取消路徑使用同一入口
- 保留
BookingOversellTest、BookingLifecycleTest全綠 - 補單元測試:名額釋放、Full → Open 的邊界
M3 通知同步發送,例外被吞掉
現況
MemberBookingController、ProviderBookingController 多處以 try { notify() } catch (\Throwable) { Log::error } 發送通知。
風險
- 若 Notification 未實作
ShouldQueue,會在請求內同步寄信,拖慢回應 - 失敗只留 log,沒有重試,也沒有告警(Sentry 不會收到被吞掉的例外)
待辦
- 檢查
app/Notifications/*是否皆實作ShouldQueue - 改為交由 queue worker 處理(
cfdive-queue容器已存在),並設定tries/backoff - 移除 Controller 內的 try/catch,改用 queue 失敗處理;必要時以
report($e)送 Sentry - 更新
NotificationTriggerTest使用Queue::fake()或Notification::fake() - 更新 spec:
notification-core、notification-triggers、notification-email
M4 前端 token 儲存不一致
現況
- member:
sessionStorage(frontend/src/api/axios.js) - admin:
localStorage(frontend/src/api/adminAxios.js) openspec/config.yaml仍寫 localStorage(見另一份 prompt 文件)
風險
管理員權限最高,卻用持久儲存,XSS 時外洩影響更大。好消息是前端目前沒有 v-html 或 innerHTML。
待辦
- 確認 coach 端(
coachAxios.js)實際使用哪種儲存 - 決定統一策略(至少 admin 改為
sessionStorage) - 評估是否改用 HttpOnly Cookie(需搭配 Sanctum SPA 模式,改動大,另案評估)
- 更新
member-portal-ui、coach-portal-ui、admin-panel-uispec 中關於儲存的描述
M5 CORS 在正式環境仍允許 localhost
現況
config/cors.php 的 allowed_origins 固定包含 http://localhost:5173 與 http://127.0.0.1:5173。
待辦
- 改為依
APP_ENV決定:local 才加入 localhost,production 僅允許FRONTEND_URL - 補一個 Feature 測試驗證 production 設定下的 CORS 標頭
- 同步檢查 Reverb(WebSocket)的 allowed origins 設定
M6 CI 使用 SQLite,鎖定/超賣測試可能假綠
現況
.gitea/workflows/test.yml以cp .env.example .env執行,.env.example為DB_CONNECTION=sqliteBookingOversellTest等測試依賴lockForUpdate(),SQLite 不支援行鎖,該方法在 SQLite 上實質無效- 另外 SQLite 與 MySQL 在 enum、JSON、時間函式上行為不同
風險 CI 綠燈不代表正式環境(MySQL)行為正確,尤其是併發情境。
待辦
- 在 CI 增加 MySQL service,至少讓預約、超賣相關測試在 MySQL 上跑一次
- 檢查
phpunit.xml的 DB 設定,確認 CI 與本機一致 - 評估
BookingOversellTest是否真的測到併發(單程序測試無法驗證行鎖,可能需要多程序測試或接受其限制並在文件註明) deploy.yml與test.yml目前互相獨立;評估是否讓部署依賴測試通過- 更新 spec:
feature-test-coverage或新增ci-pipelinespec
其他提醒
- Git remote
vps的 URL 內含明文 Gitea token(存在.git/config,未進版控)。建議輪換 token,改用 credential helper 或 SSH。 - 本文件為待辦清單,不代表已排入開發;每項啟動前仍需依 CLAUDE.md 完成「Task 執行前」檢查。