Skip to content

Releases: Jeng-linLi/student-absence-system

v1.1.0 — 交易原子化、併發守衛、待辦分頁、通知可靠性

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 23 Oct 06:20

主要變更

  • 開放重定向修正:400/413 的導回目標只沿用 Host 與本站相同的 referrer(referrer 是攻擊者可控的完整 URL)
  • 審批/撤回併發守衛:action='pending'/status='pending' 寫進 UPDATE 條件,雙擊或併發只生效一次
  • 交易原子化:新增 db.transaction(),提交/審批/撤回的多表寫入要麼全部生效、要麼全部不發生
  • SMTP 逾時:連線預設 10 秒逾時(LEAVE_SMTP_TIMEOUT),郵件伺服器不可達時不再掛死 worker
  • 空院系審批死角:學生/院系帳號新增與編輯強制填寫院系
  • 待辦清單分頁:/approvals todo 視圖每頁 20 筆、越界收斂
  • 單則通知標為已讀:POST /notifications/<id>/read
  • 撤回院系級假單同步通知院系;登入限流 429 補 Retry-After;電話格式校驗;debug 模式改由 LEAVE_DEBUG 控制

測試

  • 單元測試 82 → 96(新增 tests/test_v11.py)
  • 冒煙測試 139 → 165(新增 [+13] 段)

完整記錄見 CHANGELOG.md。

v1.0.0 — 越權缺口收官、後台稽核日誌、邊界收拾

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 22 Oct 09:00

v1.0 里程碑:v0.7.0–v0.9.1 先後補了統計/匯出/審批清單/假單詳情的範圍隔離,本版複審發現兩個漏網的越權缺口與一批邊界問題,一次收清;同時把後台觀測性補上。

Fixed |修正

  • /approvals 學生越權(真實漏洞):路由只有 login_required,任何登入的學生打開「待我審批」就能看到全系統所有待審假單。v0.9.1 修了 ?scope=all,漏了預設的 todo 分支。現已加上 role_required。
  • 儀表板彙總數字越權:導師/院系的儀表板一律顯示全系統統計,現依 role_scope 收斂,標籤改為範圍感知。
  • 同日「下午→上午」矛盾時段:天數算 0.5、受影響課程卻按全天計,兩個口徑對不起來——入口阻擋。
  • 請求體過大沒有友善處理:附件超過 8MB 不再回白底 413 錯誤頁,改 flash 提示並導回。
  • 超長附件檔名可能 500:主檔名截斷至 80 字元,不再撞檔案系統 255 位元組上限。

Added |新增

  • 後台稽核日誌頁 /admin/logs:動作篩選+分頁,僅管理員。
  • 撤回假單通知導師:待辦數字與實際一致。
  • 密碼長度上限 128 字元;管理員重設密碼不可與舊密碼相同。

Tests |測試

  • 單元測試 71 → 82(tests/test_v1.py);冒煙測試 127 → 139。本機與 CI 全綠。

v0.9.1 — 範圍隔離補齊、假別管理收拾

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 22 Oct 06:53

v0.9.0 發佈後的複審:把「範圍隔離」這條線補完整,並收拾假別管理的幾個坑。資料庫結構不變。

Fixed |修正

  • /approvals?scope=all 越權:套用 role_scope(u)——導師只看自己的學生、院系只看本院系、管理員看全部(v0.7.0 修了 /stats、/export,唯獨漏了這裡)
  • 假單詳情頁越權:/request/<rid> 新增導師與院系範圍校驗。查看權限不應寬於審批權限——decide 本就限定範圍,詳情頁卻能翻開別人的病假事由、聯絡電話與附件
  • 重複假別代號變 500:admin_add_type 先預檢代號是否已存在,不再直 INSERT 撞 leave_types.code 的 UNIQUE 約束
  • 停用假別仍可提交:new_request 改查 active=1,後台停用的假別不只從表單隱藏,直 POST 也提交不進來
  • 上午/下午時段無白名單:start_period/end_period 限定 AM/PM

Added |新增

  • 假別停用/啟用:/admin/types/<id>/toggle,管理介面補上操作按鈕(帶確認對話框),動作寫入 audit log;既有假單不受影響
  • 假別代號格式校驗(1–20 字元,僅字母、數字與 - _)與名稱長度上限(30 字)
  • 單元測試 64 → 71、冒煙測試 113 → 127:範圍隔離(審批清單、假單詳情、跨院系)、重複代號拒絕、假別停用後不出現於表單且直 POST 被擋、時段白名單

完整記錄見 CHANGELOG.md。

v0.9.0 — 弱密碼擋鎖、一次性密碼呈現、通知分頁、附件內容嗅探

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 22 Oct 06:14

主軸

把密碼與通知這條體驗鏈補齊,並收拾最後幾處裝飾性防護。資料庫結構不變,舊庫無需遷移。

Added

  • 常見弱密碼擋鎖:COMMON_PASSWORDS 靜態清單(約 40 組外洩檔高頻密碼,忽略大小寫)。Password1、Pass@123 這類「合格卻爛大街」的組合同樣拒絕;舊示範預設密碼 Pass@123 正在鎖內,示範密碼改為 Demo@2026
  • 通知分頁與未讀篩選:/notifications?page=N&filter=unread,每頁 20 筆,越界頁碼自動收斂
  • 附件內容嗅探:上傳時讀取檔案頭魔術位元組(%PDF/PNG/JFIF/OLE2/ZIP),副檔名說謊直接拒絕
  • 單元測試 42 → 64(新增 tests/test_robustness.py)

Changed

  • 管理端密碼改「一次性呈現」:明文密碼只在回應本文顯示一次,不再寫進 session cookie(Flask cookie 只簽名不加密,任何人都能讀出 payload)
  • 登入限流增加「僅帳號」鍵:換 IP/漫遊 Proxy 也躲不掉;登入成功兩把鎖一併清除
  • 管理員新增帳號未填密碼時自動產生臨時密碼(舊版是寫死的 Pass@123)
  • 冒煙測試 93 → 113 項;頁腳版本號改由 APP_VERSION 注入

Fixed

  • 明文密碼落進 session cookie(簽名 cookie payload 可讀)
  • 後台 advisor_id/email 零校驗(亂填撞外鍵約束直接 500)
  • 限流提示文案寫死「5 分鐘」與常數脫節

Security

  • 動態頁面加 Cache-Control: no-store(登出後「後退」不復現資料)
  • 附件防護從「副檔名白名單」升級為「副檔名+內容頭」雙重檢查

本地驗證:pytest 64 passed、smoke 113 passed。

v0.8.0 — 認證與帳號管理加固:限流持久化、session 失效、後台防呆

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 21 Oct 09:40

v0.8.0 — 認證與帳號管理加固 🔐

複審 v0.7.0 時發現一個規律:好幾個「以為已經有」的防護其實是裝飾——記在記憶體裡的限流、
不會作廢的 session、沒有防呆的後台開關。本版把它們補成真的。

✅ 新增

  • 閒置 session 逾時:預設 480 分鐘無操作即需重新登入(LEAVE_SESSION_IDLE_MINUTES 可調)
  • 帳號/角色白名單校驗:新增帳號時強制(1–50 字元,僅字母數字與 ._-@;角色限四種)
  • 單元測試 26 → 42 項:新增 tests/test_auth_hardening.py 與 tests/conftest.py

♻️ 變更

  • 登入限流改資料庫持久化(login_attempts 表):舊版進程內 dict 重啟清零、多 worker 各記一份,
    限流形同虛設;現在跨進程、重啟後仍有效
  • 冒煙測試 85 → 93 項;/healthz 追加核心表查詢

🐛 修正

  • 管理員重設密碼後,目標的舊 session 仍然有效——密碼都換了,舊 cookie 還能繼續用;
    現在 pwd_changed_at 讓早於變更時間登入的 session 一律作廢(自行改密碼同樣生效)
  • 管理員可停用自己/最後一位管理員——誤操作即鎖死整個後台;現在兩者皆攔阻,
    並補存在性檢查與 audit log
  • 帳號枚舉時序攻擊:帳號不存在時也跑一次固定 dummy hash 比對,拉平回應時間差
  • 修改密碼允許新舊密碼相同(等於沒改)→ 已攔阻

🗄️ 資料庫

  • 新增 login_attempts 表與 users.pwd_changed_at 欄位;舊庫啟動時經由通用 migrate
    框架自動升級,既有資料不受影響(已驗證遷移路徑與冪等性)

🧪 品質

  • 本機 + GitHub Actions 三 job(unit 42 項 / smoke 93 項 / lint)全綠
  • 時間戳記精細到微秒,消滅「重設後舊 session 仍有效」的秒級競態(冒煙連跑 4 次驗證穩定)

Full changelog: https://github.com/Jeng-linLi/student-absence-system/blob/main/CHANGELOG.md

v0.7.0 — 模組拆分落地、統計範圍修正、CSV 注入防護、密碼策略補齊

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 21 Oct 06:08

主軸是把 v0.4.0 聲稱的模組拆分真正落地,外加一輪安全與正確性加固。資料庫結構未變動,既有資料可直接沿用;舊庫補欄位改走新的通用 migrate 框架,升級更穩。

Added |新增

  • 健康檢查 GET /healthz:無需登入,回傳 JSON 健康狀態(含資料庫探測),供負載均衡/容器探測/CI 使用;資料庫異常時回 503。
  • 通用 migrate 框架:db.py 以 MIGRATIONS 清單驅動「舊庫缺欄位自動補上」,affected_courses 遷移改走此框架;日後新增欄位只需在清單加一行。
  • 單元測試 +10 項(16 → 26):tests/test_password_policy.py(強度策略、不可與帳號相同、臨時密碼產生器)、tests/test_csv_safe.py(公式注入防護)。

Changed |變更

  • app.py 真正模組化:常數與純函式一律 from utils import ...,SQLite 連線/查詢 from db import ...,刪除 app.py 內所有重複定義——v0.4.0 的文件聲稱已拆分,但實際上 app.py 仍留著第二份實作(改一邊不會同步到另一邊,是潛在的漂移來源)。順帶刪除從未使用的 paginate()。
  • 冒煙測試由 73 項擴充至 85 項(密碼策略補齊、CSV 公式注入、統計範圍隔離、/healthz)。
  • 「修改密碼」頁與「新增帳號」的密碼提示同步更新為新策略。

Fixed |修正

  • 統計頁假別彙總越權:/stats 的「依假別統計」查詢原本沒有套用導師/院系範圍,導師會看到全系統的假別天數彙總;現在與逐學生統計共用同一範圍條件(role_scope)。
  • 學生儀表板白查全系統統計:學生身份也會執行 4 個全系統 COUNT 查詢——頁面雖不顯示,但既浪費也擴大資訊暴露面;現在只有審批者/管理員才查。
  • 提交假單時 overlapping_requests 被呼叫兩次(一次判斷、一次取衝突單號)→ 只查一次。
  • 企業版 DDL 補齊課表模型:saas/schema.sql 新增 timetable 表與 absence_requests.affected_courses JSONB——v0.6.0 只在原型加了課表功能,企業版 PostgreSQL DDL 一直沒同步,正式環境據此落地會缺欄位。

Security |安全性

  • CSV 公式注入防護:CSV 匯出對 = + - @ 開頭的自由文字(事由等)加 ' 前綴,避免匯出檔在 Excel/Google Sheets 開啟時被當公式執行。
  • 密碼強度策略補齊(兌現 v0.5.0 的 Notes):validate_password(pw, username) 新增「不得與帳號相同」「須同時含大寫、小寫字母與數字」。管理員重設密碼的自動產生器改用 temp_password()——舊的 secrets.token_urlsafe() 有機率產生不符合新政式的密碼,導致重設被自己的校驗擋下。

Notes |備註

  • 密碼策略暫不要求特殊符號(可用性優先);上生產前可再收緊(常見弱密碼擋鎖、強度評分)。
  • /healthz 只探測程序與資料庫可達性,不含業務層語義。

Full Changelog: v0.6.1...v0.7.0

v0.6.1 — 受影響課程隱藏教室

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 21 Oct 04:31

v0.6.0 的小幅修正:受影響課程清單不再顯示教室欄位。資料庫結構未變動,既有資料可直接沿用。

Changed |變更

  • 受影響課程隱藏教室:提交頁即時預覽與假單詳情頁的「受影響課程(依課表推算)」清單,改為只列課程代碼、類型與起訖時間,不再顯示教室;timetable.room 資料欄位保留不變,正式環境仍可完整留存教室資訊。
  • 同步更新根目錄 README.md 與 leave-system/README.md 的版本號與功能描述。

v0.6.0 — 課表 × 請假(受影響課程)

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 21 Oct 04:31

在 v0.4.0/v0.5.0 之上,於請假流程加入「課表 × 請假」連動:學生填寫起訖日期與上午/下午時段時,即時推算並顯示會錯過的課程;假單送出時也把受影響課程一併存檔,讓導師審批時一眼看到課程衝突。

Added |新增

  • 課表(timetable):新資料表 timetable(學生、星期 0–4、起訖時間、課程代碼、類型、教室、備註)。正式環境應由 SIS / LMS 同步;此版本內建示範課表(demo 學生共用,週一至週五)。
  • 提交頁即時預覽:選定起訖日期與時段後,前端呼叫 GET /api/affected_courses(需登入),即時列出該區間受影響的課程(日期/星期/時段/課程+教室)。
  • 假單存檔受影響課程:leave_requests.affected_courses(JSON)寫入推算結果;假單詳情頁新增「受影響課程(依課表推算)」區塊。
  • 按半天粒度推算:同日 AM→PM = 全天;單一時段只列對應上午或下午的課;中間日一律全天。

Database |資料庫結構

  • leave_requests 新增 affected_courses TEXT NOT NULL DEFAULT ''。既有資料庫於啟動時自動 migrate 補欄位(ALTER TABLE,不影響既有資料)。
  • 新增 timetable 資料表與 idx_tt_student 索引。
  • 冒煙測試由 66 項擴至 73 項(課表寫入、整天/半天受影響課程、API、提交後存檔)。

v0.2.0 — 請假系統原型:安全性與完整性強化

Choose a tag to compare

@Jeng-linLi Jeng-linLi released this 29 Sep 03:02

v0.2.0 — leave-system 原型:安全性與完整性強化

資料庫結構未變動,既有資料可直接沿用。完整記錄見 CHANGELOG.md。

Fixed |修正

  • 附件權限漏洞:/uploads/<file> 原本只驗證「已登入」,任何學生都能下載他人的病假證明(個資)。現僅限本人、該生導師、同院系審批人與管理員。
  • 可重複請假:同一學生原本可在重疊區間提交多張假單,現在會阻擋並提示衝突的假單編號(已駁回/已撤回不佔用區間)。
  • 新增單張假單 90 天上限、最多回溯補請 30 天的校驗;移除通知已讀處的重複 commit。

Added |新增

  • 修改密碼頁;管理員可重設密碼(留空自動產生隨機臨時密碼)與編輯帳號資料。
  • 我的假單 / 全部假單分頁(每頁 20 筆);提交頁即時天數試算;學生儀表板顯示本學年已核准天數。
  • LEAVE_DB / LEAVE_UPLOAD_DIR 環境變數,供測試隔離。

Security |安全

  • CSRF 防護:所有 POST 表單需帶 session token,不符者拒絕且不寫入資料庫。
  • Session cookie HttpOnly + SameSite=Lax;登入成功重設 session 防 fixation。
  • 登入限流:同一 IP + 帳號 5 分鐘內失敗 8 次即鎖定(進程內計數)。
  • 附件檔名加入隨機字串,避免 URL 被猜測枚舉。

Test |測試

  • 冒煙測試改用全新暫存資料庫,檢查項目由 10 項擴充至 49 項。
  • 新增 GitHub Actions CI:每次 push / PR 自動執行冒煙測試與語法檢查。