# FR-048 Phase 2a — 授權守門盤點：Misc Group（較小模組）

> 事實盤點，**未做軸線判斷**。URL prefix = `/api/1.0`。@jwt_required 以 route method 上的裝飾器為準。
> 「現況守門」= app-service 層是否有 role/admin/manager guard（`ForbiddenError` on role/is_admin/participant.role）。
> 盤點日期 2026-07-07，branch `fix/v1.8.0-bugs`。
>
> **本檔涵蓋含 write method 或敏感 GET 的模組。** 跳過（無 write / 無敏感 GET）：`api/translate`（POST 實為翻譯查詢、無寫入無敏感檔）、`api/issue`（僅 GET get_members）、`api/label`（僅 GET menu）—— 詳見末段「跳過模組」。

---

## api/cloud_integration

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/integrations/google-drive` | DELETE | `api/cloud_integration/routes/google_drive_integration_route.py:41` | 是 | `google_drive_integration_service.disconnect(is_admin=...)` | 已有: `if not is_admin and not _is_super_admin → ForbiddenError(GRC_NOT_ADMIN)` @ `app/cloud_integration/service/google_drive_integration_service.py:189` | admin/super-admin 守門 |
| `/api/1.0/integrations/google-drive/auth-url` | POST | `api/cloud_integration/routes/google_drive_integration_route.py:58` | 是 | `google_drive_integration_service.build_auth_url(is_admin=...)` | 已有: `if not is_admin → ForbiddenError(GRC_NOT_ADMIN)` @ `app/cloud_integration/service/google_drive_integration_service.py:66` | admin 守門 |
| `/api/1.0/integrations/google-drive/callback` | GET | `api/cloud_integration/routes/google_drive_integration_route.py:73` | **否** | `google_drive_integration_service.handle_callback(code, state)` | 無 JWT | 刻意公開(OAuth callback)；auth 靠 state token（cloud-integrations#2/#4） |
| `/api/1.0/integrations/google-drive/sync-jobs/<uid>/retry` | POST | `api/cloud_integration/routes/google_drive_sync_route.py:84` | 是 | `drive_sync_admin_service.manual_retry(is_admin=...)` | 已有: `if not is_admin → ForbiddenError(GRC_NOT_ADMIN)` @ `app/cloud_integration/service/drive_sync_admin_service.py:110` | admin 守門 |
| `/api/1.0/integrations/google-drive/projects/<project_uid>/init-folders` | POST | `api/cloud_integration/routes/google_drive_sync_route.py:105` | 是 | `drive_sync_admin_service.trigger_init_project_folders(...)` | 僅 JWT | 刻意不加 is_admin gate（避免擋非 tenant-admin 的 project manager）— 註解 @ `drive_sync_admin_service.py:127-129` |
| `/api/1.0/integrations/google-drive/projects/<project_uid>/verify-and-repair-folders` | POST | `api/cloud_integration/routes/google_drive_sync_route.py:136` | 是 | `drive_project_verify_service.verify_and_repair(...)` | 已有: `if participant is None or participant.role != "manager" → ForbiddenError(GRC_NOT_MANAGER)` @ `app/cloud_integration/service/drive_project_verify_service.py:129` | project manager 守門 |
| `/api/1.0/integrations/google-drive/tenant/rebuild-all` | POST | `api/cloud_integration/routes/google_drive_sync_route.py:164` | 是 | `drive_sync_admin_service.rebuild_all_for_tenant(is_admin=...)` | 已有: `if not is_admin → ForbiddenError(GRC_NOT_ADMIN)` @ `app/cloud_integration/service/drive_sync_admin_service.py:157` | admin 守門；破壞性操作 |
| `/api/1.0/integrations/google-drive/webhook/register` | POST | `api/cloud_integration/routes/google_drive_sync_route.py:195` | 是 | `webhook_channel_manager.register_for_tenant(...)` + orchestration | 僅 JWT | doc 註明「admin only」但 service 端未見 is_admin guard — 僅 @jwt_required |
| `/api/1.0/webhooks/google-drive/<int:tenant_id>` | POST | `api/cloud_integration/routes/google_drive_webhook_route.py:39` | **否** | `google_drive_webhook_service.handle_notification(...)` | 無 JWT | 刻意公開(Drive push webhook)；auth 靠 `X-Goog-Channel-Token` header（永遠回 200） |

---

## api/feedback

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/feedbacks` | POST | `api/feedback/routes/feedback_route.py:26` | 是 | `feedback_service.get_feedbacks_and_pager(...)` | 僅 JWT | **POST 實為分頁查詢(讀)** |
| `/api/1.0/feedback` | POST | `api/feedback/routes/feedback_route.py:52` | 是 | `feedback_service.add_feedback(...)` | 僅 JWT | 建立 feedback |
| `/api/1.0/feedback/<uid>` | PUT | `api/feedback/routes/feedback_route.py:70` | 是 | `feedback_service.update_feedback(...)` | 僅 JWT | 無 owner check |
| `/api/1.0/feedback/<uid>` | DELETE | `api/feedback/routes/feedback_route.py:89` | 是 | `feedback_service.delete_feedback(uid, login_name)` | 僅 JWT | 傳 login_name 但無 owner/role gate |
| `/api/1.0/feedback/file/<uid>/<file_uid>` | DELETE | `api/feedback/routes/feedback_route.py:99` | 是 | `feedback_service.delete_feedback_file(uid, file_uid)` | 僅 JWT | 無 owner check |
| `/api/1.0/feedback/export/<export_type>` | POST | `api/feedback/routes/feedback_route.py:108` | 是 | `feedback_service.export_feedbacks(export_type)` | 僅 JWT | 敏感 GET 性質（匯出全站 feedback），但為 POST |

---

## api/system_config

> SystemConfigService = jedi 套件 `jedi_system_config.app.service.system_config_service`；全數 method 皆無 role/admin guard（僅 JWT）。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/system/configs/<group>` | GET | `api/system_config/routes/system_config_route.py:25` | 是 | `system_config_service.get_system_config_by_group(group)` | 僅 JWT | **敏感 GET — value dict 含 secret 無遮罩**，SMTP/LDAP/issue-integration token 明文回傳（`jedi_system_config/.../system_config_service.py:43` 無 mask） |
| `/api/1.0/system/config/<uid>` | GET | `api/system_config/routes/system_config_route.py:44` | 是 | `system_config_service.get_system_config(uid)` | 僅 JWT | 敏感 GET；同上，secret 明文 |
| `/api/1.0/system/config` | POST | `api/system_config/routes/system_config_route.py:58` | 是 | `system_config_service.create_system_config(payload)` | 僅 JWT | 系統級設定寫入，無 admin gate |
| `/api/1.0/system/config/<uid>` | PUT | `api/system_config/routes/system_config_route.py:72` | 是 | `system_config_service.update_system_config(uid, payload)` | 僅 JWT | 系統級設定改寫，無 admin gate |
| `/api/1.0/system/config/<uid>` | DELETE | `api/system_config/routes/system_config_route.py:86` | 是 | `system_config_service.delete_system_config(uid)` | 僅 JWT | 無 admin gate |
| `/api/1.0/system/config/<group>/<key>` | GET | `api/system_config/routes/system_config_route.py:97` | 是 | `system_config_service.get_system_config_by_key(group, key)` | 僅 JWT | 敏感 GET；secret 明文回傳 |
| `/api/1.0/system/config/<group>/<key>` | PUT | `api/system_config/routes/system_config_route.py:111` | 是 | `system_config_service.update_config_by_group_key(group, key, payload)` | 僅 JWT | 無 admin gate |
| `/api/1.0/system/config/<group>/<key>` | DELETE | `api/system_config/routes/system_config_route.py:126` | 是 | `system_config_service.delete_config_by_group_key(group, key)` | 僅 JWT | 無 admin gate |

---

## api/system_menu

> SystemMenuService = jedi 套件 `jedi_system_menu`；write method 皆無 role/admin guard（僅 JWT）。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/system/menus` | POST | `api/system_menu/routes/system_menu_route.py:38` | 是 | `system_menu_service.get_system_manus_and_pager(...)` | 僅 JWT | **POST 實為分頁查詢(讀)** |
| `/api/1.0/system-menu` | POST | `api/system_menu/routes/system_menu_route.py:67` | 是 | `system_menu_service.add_system_menu(**payload)` | 僅 JWT | 系統選單新增，無 admin gate |
| `/api/1.0/system-menu/<int:id>` | PUT | `api/system_menu/routes/system_menu_route.py:78` | 是 | `system_menu_service.update_system_menu(id, **payload)` | 僅 JWT | 無 admin gate |
| `/api/1.0/system-menu/<int:id>` | DELETE | `api/system_menu/routes/system_menu_route.py:88` | 是 | `system_menu_service.delete_system_menu(id)` | 僅 JWT | 無 admin gate |

---

## api/information_system

> InformationSystemService / InformationSystemAppService = 本地 `app/information_system/...`；write method 無 role/owner guard（僅 JWT）。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/information-systems/list` | POST | `api/information_system/routes/information_system_route.py:59` | 是 | `information_system_app_service.get_information_systems_and_pager(...)` | 僅 JWT | **POST 實為分頁查詢(讀)** |
| `/api/1.0/information-systems` | POST | `api/information_system/routes/information_system_route.py:90` | 是 | `information_system_service.add_information_system(...)` | 僅 JWT | 建立，無 role gate |
| `/api/1.0/information-system/<uid>` | PUT | `api/information_system/routes/information_system_route.py:137` | 是 | `information_system_service.update_information_system(...)` | 僅 JWT | 無 owner/role gate |
| `/api/1.0/information-system/<uid>` | DELETE | `api/information_system/routes/information_system_route.py:161` | 是 | `information_system_service.delete_information_system(uid, user)` | 僅 JWT | 傳 login_name 但無 owner/role gate |

---

## api/evidence_classification

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/project/<project_uid>/ap/<ap_uid>/classify-evidence` | POST | `api/evidence_classification/routes/evidence_classification_route.py:42` | 是 | `service.trigger_classify(...)` | 已有: `if participant is None or participant.role != "manager" → ForbiddenError(EC_NOT_MANAGER)` @ `app/evidence_classification/service/evidence_classification_service.py:167` | project manager 守門 |
| `/api/1.0/classification-run/<run_folder_id>/state` | PUT | `api/evidence_classification/routes/evidence_classification_route.py:141` | 是 | `service.put_state(run_folder_id, body, user.id)` | 僅 JWT | 僅 `_resolve_tenant_for_run_folder`（tenant 解析，非 role gate）@ `evidence_classification_service.py:737` |
| `/api/1.0/classification-run/<run_folder_id>/archive` | POST | `api/evidence_classification/routes/evidence_classification_route.py:161` | 是 | `service.archive_run(run_folder_id, user.id)` | 僅 JWT | 同上，僅 tenant 解析非 role gate |
| `/api/1.0/classification-ground-truth` | POST | `api/evidence_classification/routes/evidence_classification_route.py:237` | 是 | `service.import_ground_truth(...)` | 僅 JWT | GT 灌入，無 admin/role gate |

> 敏感 GET（下載/報表/預覽）— 皆 @jwt_required，僅 tenant 解析非 role gate：
> - `GET /classification-run/<id>/file/<file_drive_id>/preview`（route:178，檔案 bytes 串流）
> - `GET /classification-run/<id>/report/validation`（route:203）
> - `GET /classification-run/<id>/report/adjudication`（route:220）

---

## api/device

> DeviceAppService = 本地 `app/device/...`；write method 無 owner check（context 已知坑「device has no owner check」）。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/devices` | POST | `api/device/routes/device_route.py:27` | 是 | `device_app_service.get_devices_and_pager(...)` | 僅 JWT | **POST 實為分頁查詢(讀)** |
| `/api/1.0/device` | POST | `api/device/routes/device_route.py:68` | 是 | `device_app_service.add_device(user, **payload)` | 僅 JWT | 建立 |
| `/api/1.0/device/<uid>` | PUT | `api/device/routes/device_route.py:81` | 是 | `device_app_service.update_device(uid, user, **payload)` | 僅 JWT | **無 owner check** |
| `/api/1.0/device/<uid>` | DELETE | `api/device/routes/device_route.py:93` | 是 | `device_app_service.delete_device(uid, user)` | 僅 JWT | **無 owner check** |

---

## api/bulletin

> BulletinService = 本地 `app/bulletin/...`；write method 無 role/owner guard（裸奔）。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/bulletins` | POST | `api/bulletin/routes/bulletin_route.py:27` | 是 | `bulletin_service.get_bulletins_and_pager(...)` | 僅 JWT | **POST 實為分頁查詢(讀)** |
| `/api/1.0/bulletin` | POST | `api/bulletin/routes/bulletin_route.py:55` | 是 | `bulletin_service.add_bulletin(user, **payload)` | 僅 JWT | 建立公告，無 admin gate |
| `/api/1.0/bulletin/<uid>` | PUT | `api/bulletin/routes/bulletin_route.py:67` | 是 | `bulletin_service.update_bulletin(uid, user, **payload)` | 僅 JWT | 無 owner/role gate |
| `/api/1.0/bulletin/<uid>` | DELETE | `api/bulletin/routes/bulletin_route.py:78` | 是 | `bulletin_service.delete_bulletin(uid)` | 僅 JWT | 無 owner/role gate |

---

## api/uploadfile

> FileUploadService / UploadFileService = jedi 套件 `jedi_file_upload`；upload/delete 無 owner check（僅 JWT）。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/file/upload` | POST | `api/uploadfile/routes/uploadfile_route.py:34` | 是 | `file_upload_service.upload_files([file], login_name, save_dir)` | 僅 JWT | 單檔上傳 |
| `/api/1.0/file/upload/<uid>` | DELETE | `api/uploadfile/routes/uploadfile_route.py:54` | 是 | `file_upload_service.delete_file(uid)` | 僅 JWT | **無 owner check**（`jedi_file_upload/.../file_upload_service.py:93`） |
| `/api/1.0/file/uploads` | POST | `api/uploadfile/routes/uploadfile_route.py:63` | 是 | `file_upload_service.upload_files(files, login_name, save_dir)` | 僅 JWT | 多檔上傳 |
| `/api/1.0/file/download/<uid>` | GET | `api/uploadfile/routes/uploadfile_route.py:81` | **否** | `upload_file_service.get_upload_file(uid)` → `send_file` | 無 JWT | **敏感 GET — 檔案下載，無 JWT 且無 owner check**（任意 uid 可下載） |
| `/api/1.0/file/pdf-preview/<uid>` | GET | `api/uploadfile/routes/uploadfile_route.py:108` | **否** | `file_upload_service.convert_to_pdf/get_upload_file(uid)` → `send_file` | 無 JWT | **敏感 GET — PDF 預覽下載，無 JWT 且無 owner check** |

---

## api/notification

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/notification` | POST | `api/notification/routes/notification_route.py:22` | 是 | `notification_service.send_discord/telegram_notification("test...")` | 僅 JWT | 測試發送端點（寫死 "test from Raymond"），無 admin gate |
| `/api/1.0/mail/test` | POST | `api/notification/routes/mail_route.py:25` | 是 | `test_mail_service.send_test_mail(mail_to, smtp_config, change_pwd)` | 僅 JWT | 測試寄信，收 SMTP config，無 admin gate |

---

## api/notify_config

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/notify-config/<channel>/test` | POST | `api/notify_config/routes/notify_config_route.py:21` | 是 | `notify_config_test_service.test_channel(...)` | 已有: `if not user or not user.is_admin → ForbiddenError(NOTIFY_CHANNEL_TEST_FORBIDDEN)` @ `app/notify_config/service/notify_config_test_service.py:56` | admin 守門 |

---

## api/ai

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/ai-chatbot` | POST | `api/ai/routes/ai_bot_route.py:19` | 是 | `AiBotService().chat(user.id, session_id, message)` | 僅 JWT | 對話端點，作用於自身 user.id |
| `/api/1.0/ai-chatbot` | DELETE | `api/ai/routes/ai_bot_route.py:45` | 是 | `AiBotService().clear_history(user.id, session_id)` | 僅 JWT | 清自身 session 記錄（scoped by user.id） |

---

## api/ai_dashboard

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/ai-dashboard/health` | GET | `api/ai_dashboard/routes/ai_dashboard_route.py:56` | **否** | （無 service，回靜態 config） | 無 JWT | **刻意公開(health)**；前端 onMounted 呼叫；回靜態 provider/speed 清單 |
| `/api/1.0/ai-dashboard/auto-generate` | POST | `api/ai_dashboard/routes/ai_dashboard_route.py:91` | 是 | `ai_dashboard_app_service.generate_dashboard_auto(...)` | 僅 JWT | 呼叫 LLM 生成，作用於自身 user context |

---

## api/report

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/schedule-report/file-list` | POST | `api/report/routes/scheduler_report_route.py:21` | 是 | （route 內直接讀檔系統目錄，無 service）| 僅 JWT | **POST 實為檔案清單查詢(讀)**；依 payload 拼 `SCHEDULE_REPORT_DIR` 路徑 |
| `/api/1.0/schedule-report/preview` | GET | `api/report/routes/scheduler_report_route.py:42` | **否** | （route 內 `send_file(report_path)`，無 service）| 無 JWT | **敏感 GET — 檔案下載/預覽，無 JWT**；path 由 query param 拼接（report_name/type/file_name），無 owner/tenant 隔離 |

---

## api/log

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/log/api-logs` | POST | `api/log/routes/api_log_route.py:26` | 是 | `api_log_service.get_devices_and_pager(...)` | 僅 JWT | **POST 實為分頁查詢(讀)**；操作日誌列表 |
| `/api/1.0/log/api-logs/export` | GET | `api/log/routes/api_log_route.py:53` | 是 | `api_log_service.export_api_log_file()` | 僅 JWT | **敏感 GET — 匯出操作日誌 zip**；`@jwt_required()` 已於 commit 182bb2f7 (SEC-001) 恢復，現況存在（route:52） |

---

## 跳過模組（無 write / 無敏感 GET）

| Module | 唯一端點 | Method | @jwt_required? | 說明 |
|---|---|---|---|---|
| `api/translate` | `/api/1.0/translate` | POST | 是（`translate_route.py:41`）| 翻譯查詢（呼叫 OpenAI），非資料寫入、非敏感檔案匯出 — 依「write / sensitive GET」定義不列入，但 **POST 實為查詢動作**。若採嚴格 write 定義則歸此類跳過。 |
| `api/issue` | `/api/1.0/issue/get_members` | GET | **否**（`issue_member_route.py:8`，class/method 均無 `@jwt_required`）| 讀取成員清單。**注意：無 JWT** — 純讀但完全裸奔。 |
| `api/label` | `/api/1.0/labels/menu/<scope>` | GET | 是（`label_route.py:16`）| 讀取 label menu，非敏感。 |
