# AssetPilot — 資產管理

AssetPilot Logo

🏠 自托管、🔒 資料加密的個人資產管理平台(Web + Android App)
記帳、預算、台股投資、報表、稽核日誌 — 一站搞定,資料完全掌控在自己手上。

version node next.js auth openapi docker license

--- ## 目錄 - [專案優勢](#專案優勢) - [螢幕截圖](#螢幕截圖) - [快速部屬教學](#部屬教學) - [環境變數](#環境變數) - [反向代理](#反向代理) - [認證機制](#認證機制) - [功能總覽](#功能總覽) - [使用指南](#使用指南) - [資料治理](#資料治理) - [安全性](#安全性) - [外部 API 來源](#外部-api-來源) - [專案治理](#專案治理) - [授權](#授權) --- ## 專案優勢 > 為什麼要自托管一個資產管理平台,而不是用現成的記帳 App?因為 **你的財務資料,應該由你自己掌控。** | 優勢 | 說明 | | ---- | ---- | | 🏠 **自托管,資料自控** | 資料庫以 ChaCha20-Poly1305 加密儲存在**你自己的**伺服器,財務資訊**不上傳任何外部伺服器**,所有權完全屬於你。 | | 🐳 **一鍵 Docker 部部署** | 單行指令啟動;`JWT_SECRET` 等敏感金鑰**首次啟動自動產生**並寫入持久化 Volume,重啟自動讀取,不用手動設定密鑰。 | | 📊 **台股深度整合** | 串接 TWSE OpenAPI:即時股價、除權息自動同步、FIFO **全精度逐筆損益**(decimal.js)、定期定額自動順延休市日、下市凍結處理。 | | 💱 **多幣別支援** | 串接 exchangerate-api.com 即時匯率,ISO 4217 白名單驗證,台股/美股/外幣一體管理。 | | 🔐 **無密碼多重認證** | Google SSO / LINE Login / Passkey(WebAuthn)三種方式,**不提供本機密碼**——不需要記密碼,也不用怕密碼被盜。 | | 🛡️ **安全可審計** | CSP、SRI、CORS 白名單、記憶體內速率限制、路徑遊走偵測、Formula Injection 防護;匯出匯入、備份還原、登入嘗試全部留下稽核軌跡。 | | 🤖 **AI 助理整合** | 支援 **MCP OAuth 2.1**,可將 ChatGPT、Claude 等 AI 工具直接連到你的帳本,讓 AI 幫你記帳;PAT 相容模式與逐憑證寫入權限控制。 | | 🧭 **URL-first SPA** | 任何頁面可直連、可書籤、可分享;F5 重整不掉頁;上一頁/下一頁完整還原。 | | 📦 **純伺服端 CSV** | 匯出含 UTF-8 BOM + Formula Injection 防護;匯入採 DB transaction 原子化,失敗整批回滾。 | | 🌗 **三模式主題** | system / light / dark 跨裝置同步;登入頁無 FOUC 樂觀渲染。 | | 📱 **響應式 + PWA + Android App** | 桌機側邊欄常駐、行動裝置漢堡選單;可安裝為 PWA、**離線也能先記帳**(恢復連線自動同步);另有 Android 原生 App,隨時隨地記帳。 | --- ## 螢幕截圖 > 以下為 Google Play 商店行銷截圖(App 深色/淺色)與網頁版行銷截圖,皆為真實 AssetPilot 介面搭配標題文案,深色/淺色主題皆為系統內建即時切換,非後製合成。 ### App(Android)— 深色主題
儀表板
儀表板
資產全貌,一手掌握
記帳
記帳
3 秒記一筆帳
預算
預算
設定預算,避免超支
持股
持股
台股持股,一手掌握
已實現損益
已實現損益
清楚透明
統計報表
統計報表
圖表看懂現金流向
登入
登入
安全連線,資料加密(Google / Passkey)
總結
一站掌握
記帳、預算、股票、報表
### App(Android)— 淺色主題
儀表板
儀表板
資產全貌,一手掌握
記帳
記帳
3 秒記一筆帳
預算
預算
設定預算,避免超支
持股
持股
台股持股,一手掌握
已實現損益
已實現損益
清楚透明
統計報表
統計報表
圖表看懂現金流向
登入
登入
安全連線,資料加密(Google / Passkey)
總結
一站掌握
記帳、預算、股票、報表
### Web 行銷截圖
儀表板
儀表板
資產全貌,一手掌握
記帳
記帳
3 秒記一筆帳
預算
預算
設定預算,避免超支
持股
持股
台股持股,一手掌握
已實現損益
已實現損益
清楚透明
統計報表
統計報表
圖表看懂現金流向
登入
登入
安全連線,資料加密(Google / Passkey)
--- ## 部部署教學 > 以下是完整部部署步驟,依你的環境選擇一種方式。**所有方式皆可在一小時內完成上線。** ### 需求 | 需求 | 說明 | | ---- | ---- | | Docker | Docker 20.10+(或 Podman) | | 記憶體 | ≥ 512 MB(建議 1 GB) | | 磁碟 | 資料量小,一般家庭 NAS/VPS 皆足夠 | | 資料庫 | PostgreSQL(Compose 內建,亦支援外部) | --- ### 方式 A:單行 Docker 快速啟動(最簡單) 不需要 PostgreSQL 環境變數也可跑(會以內建 SQLite 相容層啟動;正式建議用 PostgreSQL,見方式 B)。 ```bash docker run -d \ --name assetpilot \ --restart unless-stopped \ -p 3000:3000 \ -v assetpilot-data:/app/data \ es94111/assetpilot:latest ``` 開啟 **** 即可使用。`JWT_SECRET` 首次啟動自動產生並寫入 `/app/data/.env`,之後重啟自動讀取。 **部部署完成的驗收檢查:** ```bash docker ps | grep assetpilot # 狀態為 healthy docker exec assetpilot wget -qO- http://localhost:3000/api/config | head # 回傳 200 ``` --- ### 方式 B:Docker Compose(含 PostgreSQL,建議正式環境) 建立 `docker-compose.yml`: ```yaml services: postgres: image: postgres:17-alpine container_name: assetpilot-postgres restart: unless-stopped environment: - POSTGRES_DB=assetpilot - POSTGRES_USER=assetpilot - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-assetpilot} volumes: - assetpilot-postgres:/var/lib/postgresql/data assetpilot: image: es94111/assetpilot:latest container_name: assetpilot restart: unless-stopped depends_on: - postgres ports: - "3000:3000" volumes: - assetpilot-data:/app/data environment: - PORT=3000 - HOSTNAME=0.0.0.0 - DATABASE_URL=${DATABASE_URL:-postgres://assetpilot:assetpilot@postgres:5432/assetpilot} - ENV_PATH=/app/data/.env - GOOGLE_CLIENT_ID= # 選配,設定後啟用 Google 登入 # - LINE_CHANNEL_ID= # 選配,LINE 登入 volumes: assetpilot-data: driver: local assetpilot-postgres: driver: local ``` 啟動並驗證: ```bash docker compose up -d docker compose logs -f assetpilot # 看到 "Ready" 即完成 ``` > 帳號採用「外部服務登入」模式:**第一位完成 Google/LINE 登入的使用者自動成為管理員**,不需要後台手動建帳號。 --- ### 方式 C:Synology NAS(專用教學) 1. 開啟 **Container Manager → Registry**,搜尋 `es94111/assetpilot` → **下載**。 2. 到 **Container → 新增**,選擇剛下載的映像,名稱設 `assetpilot`。 3. **連接埠設定**:本機 `3000` 對應容器 `3000`。 4. **儲存空間設定**:新增 Volume,掛載 `/app/data`(並可另掛 PostgreSQL 資料)。 5. **環境變數**:加入 `PORT=3000`、`HOSTNAME=0.0.0.0`,以及選配的 `GOOGLE_CLIENT_ID` 等。 6. 點擊 **啟動**。 自訂網域請用 **DSM → 控制台 → 登入入口 → 反向代理**,並在自訂標題加入 `X-Forwarded-For: $proxy_add_x_forwarded_for`。 --- ### 方式 D:Node.js 直接執行(開發/本機) ```bash npm install # 需 Node.js ≥ 24 cp .env.example .env # 依需求編輯(至少設定 DATABASE_URL) npm run dev # 開發模式 ``` **正式環境(build / start):** ```bash npm run build npm run start ``` --- ### 6. 反向代理(上 HTTPS,正式必備) AssetPilot 本身不需掛在 HTTPS 後即可運作,但要對外上線(尤其用 Google / LINE 登入)強烈建議用反向代理。三種常見選項任選一:
Nginx ```nginx server { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ```
Caddy(自動 HTTPS) ``` your-domain.com { reverse_proxy localhost:3000 } ``` Caddy 自動申請並續期 HTTPS 憑證,最省事。
Cloudflare Tunnel(可配 API Shield) 直接掛 Cloudflare Tunnel 後,可啟用 API Shield → 上傳 `openapi.yaml` 啟用 schema 驗證。
--- ### 7. 資料備份 / 還原 ```bash # 備份應用設定與 SSL 憑證(PostgreSQL 資料請備份 assetpilot-postgres volume) docker run --rm -v assetpilot-data:/data -v $(pwd):/backup alpine \ tar czf /backup/assetpilot-backup.tar.gz -C /data . # 還原 docker run --rm -v assetpilot-data:/data -v $(pwd):/backup alpine \ tar xzf /backup/assetpilot-backup.tar.gz -C /data ``` > ⚠️ **PostgreSQL 資料位於資料庫 volume**;`assetpilot-data` 只保存應用設定與 SSL 憑證。強烈建議在管理員頁使用**整檔 SQL 備份/還原**功能(支援下載、還原失敗自動回滾、MEGA S4 雲端備份)。 **映像檔資訊**:[`es94111/assetpilot`](https://hub.docker.com/r/es94111/assetpilot) — 支援 `linux/amd64` + `linux/arm64`,基底 `node:24-alpine`,約 180 MB,內建每 30 秒 `/api/config` 健康檢查,以非 root 使用者執行。 --- ## 環境變數 Docker 多數參數已有合理預設,只需關心「自動產生」與「功能選配」兩區塊。Node.js 直接執行請複製 `.env.example`。 | 變數 | 類別 | 說明 | 預設值 | | ---- | ---- | ---- | ------ | | `PORT` | 基本 | 伺服器埠號 | `3000` | | `DATABASE_URL` / `POSTGRES_URL` | 基本 | PostgreSQL 連線字串 | — | | `JWT_EXPIRES` | 基本 | 瀏覽器登入 JWT 有效期限 | `7d` | | `APP_JWT_EXPIRES` | 基本 | App 登入 JWT 有效期限(Token 存於裝置端加密儲存) | `90d` | | `JWT_SECRET` | 🔑 自動 | JWT 簽章金鑰,64 字元 hex(首次啟動自動產生) | — | | `ENV_PATH` | 🔑 自動 | 自動產生金鑰的存放路徑 | `/app/data/.env` | | `GOOGLE_CLIENT_ID` | SSO | Google OAuth Client ID(留空停用 Google 登入) | — | | `GOOGLE_CLIENT_SECRET` | SSO | Google OAuth Client Secret | — | | `LINE_CHANNEL_ID` | SSO | LINE Login Channel ID(留空停用) | — | | `LINE_CHANNEL_SECRET` | SSO | LINE Login Channel Secret | — | | `ALLOWED_ORIGINS` | 安全 | CORS 白名單,逗號分隔(正式環境建議設定) | — | | `ADMIN_IP_ALLOWLIST` | 安全 | 管理員 IP 白名單,逗號分隔 | — | | `SETUP_BOOTSTRAP_TOKEN` / `SETUP_BOOTSTRAP_TTL_MINUTES` | 安全 | 首次部署 Passkey 初始化憑證(至少 32 字元;留空則啟動時自動產生並於日誌顯示一次)與有效分鐘數 | `1440` | | `EXCHANGE_RATE_API_KEY` | 選配 | exchangerate-api.com Key | `free` | | `IPINFO_TOKEN` | 選配 | ipinfo.io Token,提升 IP 查詢配額 | — | | `STOCK_AUTO_UPDATE_ENABLED` | 選配 | 股價自動更新總開關 | — | | `STOCK_AUTO_UPDATE_INTERVAL_MIN` | 選配 | 股價自動更新間隔(分鐘) | `10` | | `MEGA_S4_*` | 選配 | MEGA S4 雲端備份的 S3 相容 bucket 設定 | — | | `TRANSACTION_PHOTO_*` | 選配 | 交易照片本機或 S3 儲存設定 | — | | `RECEIPT_OCR_PROVIDER` / `RECEIPT_OCR_ENDPOINT` / `RECEIPT_OCR_API_KEY` | 選配 | 收據 OCR 自動帶入的供應商設定:`http`(自訂 OCR 端點;設定 API Key 時必須使用 HTTPS)或 `ocrspace`([OCR.space](https://ocr.space/ocrapi),只需 API Key,可用 `RECEIPT_OCR_LANGUAGE` 指定語言,預設 `cht`)。未設定供應商時維持手動輸入 | — | | `EMAIL_PROVIDER_PRIMARY` / `EMAIL_PROVIDER_FALLBACK` | 寄信 | 主要/備用寄信通道:`smtp` / `zeabur` / `resend` | — | | `SMTP_*` / `ZEABUR_*` / `RESEND_*` | 寄信 | 各寄信通道的連線設定 | — | | `APP_URL` | MCP OAuth / 寄信 | 正式環境的對外 HTTPS origin | — | | `EINVOICE_ENCRYPTION_KEY` | 🔑 自動 | 雲端發票載具驗證碼的加密主金鑰(首次需要時自動產生) | — | | `EINVOICE_API_ENDPOINT` | 雲端發票 | 電子發票供應商 API 端點(https,三者齊備才啟用) | — | | `EINVOICE_API_APP_ID` / `EINVOICE_API_KEY` | 雲端發票 | 供應商 API 憑證(僅以標頭送出,不寫入日誌) | — | > 💡 **寄信通道**(v4.30.0 起)一律由環境變數設定。`EMAIL_PROVIDER_PRIMARY` 留空則寄信停用;`EMAIL_PROVIDER_FALLBACK` 僅在 primary 執行期失敗時觸發。對應通道的設定變數需配合補齊。 --- ## 認證機制 支援三種登入方式,皆可同時啟用。**AssetPilot 不提供本機 Email/密碼登入**——使用者只能透過 Google、LINE、Passkey 等外部或無密碼方式建立帳號;第一位完成外部登入的使用者自動成為管理員。 ### Passkey-first 首次部署初始化(Setup Wizard) 全新實例(使用者數為 0 且尚未初始化)不需設定 Google/LINE OAuth 即可完成首次部署: 1. 啟動服務後,於啟動日誌取得一次性初始化憑證(或以 `SETUP_BOOTSTRAP_TOKEN` 指定,至少 32 字元;僅儲存雜湊與到期時間,預設 24 小時有效)。 2. 開啟 `/setup`(未登入時 `/login` 會自動導向),輸入憑證、管理員 Email 與顯示名稱後註冊 Passkey(需使用者驗證)。 3. 建立管理員、Passkey、預設資料與憑證消耗在單一資料庫交易內完成,並發請求僅一者成功;失敗不留殘餘資料,可重試。 4. 初始化完成後 `/setup` 與 `/api/setup/*` 永久關閉(即使日後刪除所有使用者);既有實例升級後視為已初始化。 5. 失敗嘗試受到每 IP/全域節流,且事件寫入稽核紀錄。請於完成後新增備用 Passkey 或復原碼。 API:`GET /api/setup/status`、`POST /api/setup/passkey/challenge`、`POST /api/setup/passkey/register`。 ### Google SSO(建議) 1. 至 [Google Cloud Console](https://console.cloud.google.com/) 建立 OAuth 2.0 用戶端 ID(類型:網頁應用程式) 2. **已授權 JavaScript 來源**:本機 `http://localhost:3000`、正式 `https://your-domain.com` 3. **已授權重新導向 URI**:本機 `http://localhost:3000/`、正式 `https://your-domain.com/`(含尾端 `/`);Android App 另需加入 `https://your-domain.com/app/google-callback` 4. 設定 `GOOGLE_CLIENT_ID` 與 `GOOGLE_CLIENT_SECRET` 後啟動 5. 未設定時 Google 登入按鈕自動隱藏,不影響其他登入方式 > ⚠️ 若登入後停在 `/?code=...`,請確認重新導向 URI 與網域完全一致(含 `https://` 與尾端 `/`)。 ### LINE Login 1. 至 [LINE Developers Console](https://developers.line.biz/console/) 建立 Provider 與 LINE Login channel,啟用 email 權限 2. **Callback URL**:本機 `http://localhost:3000/auth/line/callback`、`http://localhost:3000/app/line-callback`;正式 `https://your-domain.com/auth/line/callback`、`https://your-domain.com/app/line-callback` 3. 設定 `LINE_CHANNEL_ID`、`LINE_CHANNEL_SECRET`、`LINE_OAUTH_REDIRECT_URIS` 4. 到「管理員設定 → 系統設定」勾選「啟用 LINE 登入」 ### Passkey(WebAuthn) 登入後可至「個人設定 → 安全」綁定。支援 Touch ID / Face ID / Windows Hello / 硬體金鑰。 ### LINE 官方帳號記帳 設定 `LINE_MESSAGING_CHANNEL_SECRET`、`LINE_MESSAGING_CHANNEL_ACCESS_TOKEN` 與 `APP_URL`,在 Messaging API 的 Webhook URL 填入 `https://your-domain.com/api/line/webhook`。使用者綁定 LINE 帳號後,即可在官方帳號輸入 `選單`、`支出 120 午餐`、`收入 5000 薪資` 等文字記帳。 --- ## 功能總覽 ### 收支與預算 - **儀表板**:月份切換、總資產/收入/支出/淨額卡片、雙圓餅(含「(其他)」群組)、最近交易 - **交易管理**:CRUD、跨帳戶轉帳(雙向配對)、批次刪除/變更分類/變更日期、未來交易標記 - **預算管理**:月度總預算 + 分類預算;四段配色進度條;歷史與未來月份切換 - **帳戶管理**:多幣別、銀行/信用卡/證券/現金,可排除特定帳戶於總資產統計 - **分類管理**:父子兩層、自訂顏色、CSV 匯出匯入 - **固定收支**:週期性收支自動產生交易,支援外幣;登入時 server-side 觸發、並發冪等保護 ### 雲端發票載具匯入(#253) - **綁定手機條碼載具**:於「系統設定 → 雲端發票」輸入手機條碼(`/ABC1234` 格式)與驗證碼即可綁定,最多 5 組;驗證碼以 AES-256-GCM 加密後才落地,對外摘要與列表一律只回傳遮蔽後的手機條碼(`/ABC••••`)。 - **匯入為草稿,確認後才入帳**:手動按「立即同步」或由登入時的排程(每個載具每小時最多一次,冷卻記錄於資料庫以跨實例共用)拉取近 30 天(上限 90 天)發票,寫入**交易草稿**並對應發票號碼/店家/金額/日期;使用者選好帳戶與分類、按「確認入帳」才會建立真正的交易,略過則保留紀錄不再提示。 - **以發票號碼去重**:`(user_id, invoice_number)` 唯一鍵,重複同步不會產生第二筆草稿;確認入帳另以 `client_ref` 冪等,重試不會重複記帳。 - **同步失敗保留錯誤狀態,不重試風暴**:失敗會記錄錯誤訊息、連續失敗次數與下次重試時間(指數退避 30 秒起、上限 1 小時);網路/408/429/5xx 暫時錯誤只會在下一次排程檢查且退避到期後重試,401/403 與格式錯誤不再排程自動重試。不可重試錯誤仍套用短退避以防手動連點,退避到期後可手動再試,重新綁定則立即重置。單一載具同步有原子租約避免併發重複查詢;供應商未設定時優雅降級為 `skipped`,不計入失敗也不退避。 - **稽核**:綁定、解除綁定、同步與入帳皆寫入稽核日誌,且日誌中不含任何憑證明文。 - **帳本邊界**:載具與發票匯入屬個人整合,不隨共享帳本共享;此頁的帳戶/分類選項固定讀取登入者的個人資料,即使目前切換到共享帳本,發票確認也只會建立在個人帳本,不讀寫共享帳本。 - **供應商可設定**:端點與憑證全部由環境變數提供;未設定時功能停用但 UI 仍可操作(顯示降級訊息)。測試全程以 stub 覆蓋,不連線真實財政部 API。 ### 共享帳本(家庭/情侶) - 在「系統設定 → 共享帳本」建立**全新的空白帳本**;既有個人帳戶、分類與交易不會自動分享。 - 每個共享帳本有一位 `owner`,可邀請 `editor`(可編輯)或 `viewer`(唯讀)、變更角色、移除成員、撤銷邀請與移交擁有權。邀請以 Email 寄送,7 天失效且只能由受邀信箱登入後接受一次。 - 邀請寄送沿用 SMTP/Zeabur/Resend 設定;正式部署請設定 `APP_URL` 為可信任的 HTTPS 公開網址。未設定寄信服務或寄送失敗時,API 回覆 503 並撤銷邀請,不會顯示寄送成功。 - 左側帳本選單切換帳戶、分類、交易(含附件/轉帳/還款)、預算、固定收支、CSV 匯入匯出及記帳報表的資料範圍。API 使用 `X-Ledger-Id`;直接開啟附件連結可使用 `ledgerId` query,兩者皆須通過即時成員授權。未指定時仍使用個人帳本。 - 所有記帳寫入會記錄帳本、操作者 ID/Email/角色及成功、失敗或拒絕結果;擁有者可查閱近期活動。共享帳本的自動固定收支採建立時的帳本時區,viewer 的唯讀請求不會觸發共享帳本自動記帳。 - **資料始終屬於帳本**:成員離開、被移除或刪除自己的帳號後,既有共享資料與稽核紀錄保留;該成員的新請求立即失去存取權。擁有者離開或刪除帳號前,必須移交給另一位有效成員。 - 投資資料、登入憑證、使用者偏好、MCP/API Token/LINE 整合、個人資料 bundle 與通知排程仍維持個人範圍;Flutter 本次維持個人帳本。後續範圍見 [投資共享 #280](https://github.com/es94111/AssetPilot/issues/280) 與 [用戶端/整合 #281](https://github.com/es94111/AssetPilot/issues/281)。離線記帳固定送回**建立佇列項目時的帳本**,不會因切換帳本而改變歸屬。 - 驗證:`npm test` 納入純授權測試;`DATABASE_URL=... npm run test:shared-ledger` 使用 PostgreSQL 與本機 SMTP fixture 驗證真實認證路由、邀請、角色、跨帳本參照及離開/移交/刪除。未設定 DB 時此整合測試明確略過。 ### PWA 與離線記帳 - **可安裝**:附 Web App Manifest 與安裝提示,加到主畫面後以 standalone 模式開啟 - **離線可用**:Service Worker 快取靜態資源(manifest、圖示、字型、Next chunk),離線時開啟 `/offline` 說明頁;**絕不快取 API 回應或已登入頁面**,避免在共用裝置外洩財務畫面 - **離線記帳**:離線新增的收支/轉帳存入依登入者分隔的本機佇列(localStorage),恢復連線後自動送出;切換帳號時清除前一位使用者未同步資料,且寫入後會回讀驗證,儲存空間不可用時明確提示而不謊稱已保存 - **同步衝突策略**:每筆離線交易帶 client 產生的 `clientRef`,伺服器以 `(user_id, client_ref)` 唯一索引去重——重送不產生重複交易(伺服器為最後寫入權威);可重試錯誤(離線/5xx/429)以指數退避重試,不可重試錯誤(4xx 驗證失敗)標記失敗並提示使用者選擇「重試」或「捨棄」 ### 股票投資 - **持股總覽**:即時市值、未實現損益、整體報酬率、三段策略抓股價(盤中即時/盤後收盤/備援) - **股價自動更新**:交易時段自動抓 TWSE/TPEx 最新價寫回現價;跨使用者去重、管理員可調整間隔 - **股票交易**:手續費(0.1425%,最低 20 元)/證交稅(0.3%/ETF 權證 0.1%)自動計算可手動覆寫 - **股利紀錄**:TWSE 除權息自動同步;現金股利對應入款交易帳戶反查 - **實現損益**:FIFO 全精度(decimal.js)逐筆計算、今年/總損益彙總、賣出鏈式約束驗證 - **定期定額**:遇 TWSE 休市日自動順延、歷史收盤價回填、idempotency 保證 - **下市標記**:凍結最後價格,後續查價自動跳過 - **美股支援**:支援美股小數股數 ### 統計與排程 - **統計報表**:分類圓餅(單/雙)+ 月度趨勢 + 每日消費 + 自訂時間範圍;同型前一段對比 pill;圓餅扇區可點擊跳轉 - **排程寄送**:多筆排程(每日/每週/每月,每月可指定 1–28 日或「最後一天」);寄信通道支援 SMTP/Zeabur Email/Resend,主要通道失敗自動退備用 ### AI 助理整合(MCP) - **MCP OAuth 2.1**:在 ChatGPT、Claude 等工具輸入 `https://<你的網域>/api/mcp`,透過瀏覽器登入 AssetPilot、確認權限並完成連線;完整支援 PKCE S256、rotating refresh token - **AI 記帳寫入**(可選):AI 可透過 `create_transaction` 新增交易、`update_transaction_note` 更新備註;僅能新增/改備註,無法修改或刪除既有資料 - **逐憑證權限**:寫入能力預設關閉,於「設定 → MCP 連線」逐憑證開關 - **AI 標記與還原**:交易列表為 AI 建立的交易顯示徽章,可一鍵還原 > 正式環境啟用 MCP OAuth 前,務必將 `APP_URL` 設為對外 HTTPS origin。 ### API Token 與 Webhook(第三方自動化整合) 除 MCP 之外,也提供通用 REST 整合介面,讓使用者自行串接自動化流程(issue #258)。 - **API Token**:於 `POST /api/user/api-tokens` 建立,明文僅回傳一次,儲存時只留 SHA-256 雜湊; 逐 Token 的權限範圍(`transactions:read`/`transactions:write`/`webhooks:manage`),以 `Authorization: Bearer ap_api_…` 呼叫。每個帳號最多 20 組啟用中的 Token,可設定到期時間與撤銷。 - **公開 REST 端點**:`GET /api/v1/transactions`(需 `transactions:read`)列出交易、 `POST /api/v1/transactions`(需 `transactions:write`)新增交易並觸發 `transaction.created` Webhook。 這組端點不使用 `authToken` Cookie,僅接受 Bearer 權杖;未帶/失效權杖回 401,缺少對應 scope 回 403。 - **Webhook 訂閱**:於 `POST /api/user/webhooks` 訂閱 `transaction.created`/`transaction.updated`/ `transaction.deleted`。目標必須為公開 HTTPS 網址(拒絕 `http://`、loopback 與私網位址)。 - **HMAC 簽章**:每次投遞帶 `X-AssetPilot-Signature: t=,v1=`,簽章內容為 `${timestamp}.${rawBody}`(HMAC-SHA256,密鑰以 AES-256-GCM 加密後存於資料庫); 接收端可比對時間戳(容忍 5 分鐘)抵抗重放。 - **重試與紀錄**:失敗依指數退避重試(最多 5 次,30 秒起、上限 1 小時);429/5xx 重試, 其餘 4xx 視為永久失敗。待投遞佇列會在每次已驗證的請求中順帶排空; 投遞紀錄可於 `GET /api/user/webhooks/deliveries` 查詢。 - **稽核**:Token 與 Webhook 的建立/更新/刪除皆寫入稽核日誌(僅記錄識別碼與名稱, 絕不記錄 Token 明文或簽章密鑰)。 - **設定頁 UI**:於「設定 → API 整合」(`/settings/api-integration`)以介面完成上述操作, 不需 `curl`。可勾選權限範圍建立 Token、建立/編輯/刪除 Webhook 訂閱、查詢投遞紀錄; Token 明文與簽章密鑰都只在建立後的一次性提示中顯示(需確認已複製才能關閉), 簽鑰提示同時附上 `X-AssetPilot-Signature` 格式與時間戳比對說明。 > Webhook 簽章密鑰的加密主金鑰為 `API_TOKEN_ENCRYPTION_KEY`;未設定時首次啟動會自動產生 > 並寫入 `.env`(比照 `JWT_SECRET`)。一旦有訂閱後請勿更換。 ### 資料治理 - **CSV 匯出**:交易/分類/股票交易/股利紀錄;純伺服器端 stream + UTF-8 BOM + Formula Injection 防護 - **Excel 匯出**:同一組匯出端點加上 `?format=xlsx`,欄位標題、日期與數字(金額)為正確型別,沿用 Formula Injection 防護;伺服器以 `xlsx-stream-writer` 逐列串流產生、資料庫以固定 1,000 列 keyset 分頁;支援 File System Access API 的瀏覽器直接串流寫檔,其他瀏覽器使用 Blob 下載 - **CSV 匯入**:互斥鎖(重入回 409)+ 全 DB transaction 原子化 + 進度回饋 - **整檔備份/還原**:SQL 備份下載/上傳、還原失敗自動回滾、管理員列管 5 份/90 天 - **稽核日誌**:管理員與使用者分開檢視;過濾、CSV 匯出、清空、保留天數設定 - **交易照片附件**:最多 5 張、本機或 S3 儲存、讀取一律走登入授權 API ### 系統管理 - **使用者管理**:管理員可開關外部服務註冊、設定 Email 白名單、IP 白名單 - **登入稽核**:時間、IP、國家、方式、成功/失敗 - **API 使用與授權頁**:動態列出所有外部 API 來源、配額、合規授權字樣 --- ## 使用指南 ### 首次使用 1. 開啟 `http://localhost:3000` → 選擇 **Google** 或 **LINE** 登入 2. **第一位完成外部登入的使用者自動成為管理員** 3. 系統自動建立預設分類(食、衣、住、行…)與預設帳戶(現金、銀行帳戶) ### URL 直連 任何頁面皆可直連、加入書籤;未登入訪客導向 `/login?next=<原 URL>`,登入後自動跳回。 | URL | 說明 | | --- | --- | | `/` | 公開首頁 | | `/login` | 登入頁 | | `/dashboard` | 儀表板 | | `/finance/transactions`, `/finance/reports`, `/finance/budget`, `/finance/accounts`, `/finance/categories`, `/finance/recurring`, `/finance/goals` | 收支管理與目標儲蓄 | | `/stocks`, `/stocks/transactions`, `/stocks/dividends`, `/stocks/realized` | 股票投資 | | `/settings/account`, `/settings/admin`, `/settings/export` | 設定(admin 僅管理員可見) | | `/settings/mcp`, `/settings/mcp-connections`, `/settings/api-integration` | MCP 連線、已連接 AI 工具、API 整合(Token 與 Webhook) | | `/api-credits` | API 使用與授權 | ### 信用卡繳費 使用信用卡消費時記為「**支出**」+ 信用卡帳戶(餘額負數=真實負債)。繳費時選「**轉帳**」,從銀行轉到信用卡帳戶即可沖銷負債,不重複計算為支出。 ### CSV 匯出欄位 匯出頁可選擇 CSV 或 Excel(.xlsx);兩種格式欄位一致,Excel 版本的金額、股數與日期為原生數值/日期型別。 | 類型 | 欄位 | | ---- | ---- | | 交易記錄 | 日期、類型、金額、幣別、分類、帳戶、備註 | | 分類 | 父分類/子分類、類型、顏色 | | 股票交易 | 日期、代號、名稱、類型、股數、成交價、手續費、交易稅、帳戶、備註 | | 股利紀錄 | 日期、代號、名稱、現金股利、股票股利、備註 | --- ## 安全性 | 機制 | 說明 | | ---- | ---- | | 資料庫加密 | ChaCha20-Poly1305 AEAD + PBKDF2-SHA256 金鑰推導 | | 身份驗證 | 僅接受 Google、LINE、Passkey 等外部/無密碼方式;不建立本機密碼 | | XSS 防護 | 所有使用者輸入經 `escHtml()` 跳脫後才插入 DOM | | 安全標頭 | HSTS、X-Content-Type-Options、Referrer-Policy | | CSP | 限制 inline script 與外部資源來源;`worker-src 'self'`/`manifest-src 'self'` 供 PWA 使用 | | Service Worker | 僅快取公開靜態資源;不快取 API 回應與已登入頁面 | | 速率限制 | 登入/Passkey 端點每 IP 每 15 分鐘最多 20 次;公開頁面每分鐘最多 120 次 | | OAuth 防 CSRF | Google/LINE 使用一次性 state;LINE 額外 nonce 驗證 ID Token | | MCP OAuth 2.1 | PKCE S256、精確 redirect URI、short-lived token、refresh rotation、token 僅存 SHA-256 | | API Token | 明文僅回傳一次、僅存 SHA-256 雜湊、逐 Token scope、停用帳號立即失效 | | Webhook | 僅接受公開 HTTPS 目標(防 SSRF)、HMAC-SHA256 簽章附時間戳、密鑰 AES-256-GCM 加密儲存、指數退避重試 | | `?next=` 防護 | 相對路徑白名單、拒 protocol-relative、拒 `://`、pathname 必須命中前端 ROUTES 表 | | 路徑遊走偵測 | catch-all 偵測 `..`/`%2e%2e`/`%252e%252e`,寫稽核日誌 | | Formula Injection 防護 | CSV 匯出對以 `=` `+` `-` `@` 開頭欄位前置撇號;Excel 匯出同樣前置撇號並套用文字格式(`@`),確保不會被當成公式執行 | | SRI | 外部 CDN 腳本完整性驗證 | | 登入稽核 | 記錄時間、IP、國家、方式;管理員可查失敗嘗試與帳號鎖定狀態 | --- ## 外部 API 來源 | 服務 | 用途 | | ---- | ---- | | TWSE OpenAPI | 台股即時股價、收盤、除權息 | | exchangerate-api.com | 全球即時匯率(基礎 TWD) | | Google Identity Services | Google SSO 登入 | | LINE Login API | LINE 登入與帳號綁定 | | IPinfo Lite | IP 國家查詢(合規授權字樣固定顯示於 API 使用頁) | | SMTP(Nodemailer) | 排程信件/系統通知 | | Zeabur Email(ZSend) | 排程信件/系統通知 | | Resend | 排程信件/系統通知 | | MEGA S4 Object Storage | 管理員整備 PostgreSQL SQL 備份的 S3 相容目的地 | | 電子發票整合服務平台(可設定端點) | 依手機條碼載具查詢雲端發票,轉為交易草稿(未設定時停用) | 完整列表與授權字樣可在執行中應用程式的 `/api-credits` 頁面查看。 --- ## 專案治理 本專案採用 **Spec-Driven Development** 工作流程。每個功能皆有完整的 spec → research → plan → contracts → tasks → implementation 軌跡,存於 `specs//` 下。 | 工件 | 用途 | | --- | --- | | `spec.md` | 使用者導向的功能規格與驗收條件 | | `research.md` | 技術選型決策記錄 | | `plan.md` | 實作計畫與檔案影響面 | | `data-model.md` | 資料模型與 schema 變更 | | `contracts/*.openapi.yaml` | API 契約 delta(OpenAPI 3.2.0) | | `quickstart.md` | 手動驗證劇本 | | `tasks.md` | 可執行任務清單 | | `checklists/` | 上線前檢查表 | 專案憲章:[`.specify/memory/constitution.md`](.specify/memory/constitution.md) 完整變更歷史:[`changelog.json`](changelog.json) | 軟體需求規格:[`SRS.md`](SRS.md) --- ## 授權 [GNU AGPL v3](LICENSE) > 本軟體採 AGPL v3 授權;**只要任何人能透過網路使用您運行的這個程式(即使您只是自托管給朋友家人用)**,您就有義務以同樣 AGPL v3 條款公開您的修改原始碼。商業授權請另行洽談。