Repository navigation
自分のシフトだけをGoogleカレンダーに表示できるようにする - #73
Merged
Merged
Conversation
通常ユーザーの「自分のシフト一覧」タブに、自分のシフトだけのiCal購読URLを 表示する。Googleカレンダーの「URLで追加」に貼り付けると、以後シフトの増減が 自動で反映される。 あわせて、購読トークンの設計上の問題を修正する。 これまで /api/ical/user/:user_id は .env の ICAL_TOKEN 1個だけで検証しており、 そのトークンは /api/config からログイン前の全ブラウザへ配布されていた。 このため購読URLの user_id を書き換えるだけで他人のシフトを閲覧でき、 /ical/all で全員分も取得できる状態だった。 変更点: - ICAL_TOKEN をサーバー秘密鍵として扱い、/api/config からの配布を廃止 - 購読トークンを HMAC-SHA256(ICAL_TOKEN, "ical-user:" + user_id) で導出し、 user_id と対でのみ有効にする(DBマイグレーション不要) - GET /api/ical/my-url で本人の購読URLを発行 - GET /api/ical/all-url で管理者用の購読URLを発行(AUTHORIZED_EMAILS で認可) - 購読を想定し Content-Disposition を attachment から inline に変更 - クリップボードが使えない環境では手動コピーを促すフォールバックを追加 - help.html に学生向けの手順(キャプチャ付き)を第6章として追加 回帰修正: config.ICAL_TOKEN の廃止により、管理者の「iCal 購読URL(全シフト)」欄が 常に「未設定」表示になる不具合が発生したため、サーバーから取得する方式へ変更。 再発防止テストを test/admin-ical-url.test.js に追加した。 テスト: 412 passed (30 suites) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
通常ユーザーの「自分のシフト一覧」タブに、自分のシフトだけの iCal 購読URLを表示するようにしました。Googleカレンダーの「URLで追加」に貼り付けると、以後シフトを申請・削除するたびにカレンダー側へ自動で反映されます。
あわせて、購読トークンの設計上の問題を修正しています。
調査でわかったこと
iCal 配信の仕組み(
ICalService/routes/ical.ts)と、特定ユーザーのシフトだけを返すエンドポイントはすでに実装済みでした。そのため本 PR は新規実装ではなく、既存の仕組みを一般ユーザーに露出させる作業が主になっています。ただし、そのまま露出させると問題がありました。
修正したセキュリティ上の問題
これまで
/api/ical/user/:user_idの検証は.envのICAL_TOKEN1個だけを見ており、しかもそのトークンは/api/config経由でログイン前の全ブラウザへ配布されていました。購読URLはカレンダーアプリに貼られてユーザーの手元に長く残るため、この状態で学生に配ると次のことができてしまいます。
user_idを書き換えるだけで他人のシフトを閲覧できる/ical/allに変えれば全員分のシフトを取得できる対応
ICAL_TOKENをサーバー秘密鍵として扱い、各ユーザーの購読トークンをHMAC-SHA256(ICAL_TOKEN, "ical-user:" + user_id)で導出する方式に変更しました。トークンは user_id と対でのみ有効です。ICAL_TOKENを再生成すれば全ユーザーの購読URLを一括失効できるtimingSafeEqualを使用変更内容
サーバー
server/src/utils/icalToken.ts(新規) — HMAC によるトークン導出・検証GET /api/ical/my-url?userId=(新規) — 本人の購読URLを発行GET /api/ical/all-url?email=(新規) — 管理者用の購読URLを発行(AUTHORIZED_EMAILSで認可)/api/configからicalTokenの配布を廃止Content-Dispositionをattachment→inlineに変更(購読用途のため)フロントエンド
index.htmlに静的配置auth.js)とタブ切替パス(loadMyShifts)の両方から初期化UI の変更点
「自分のシフト一覧」タブの上部に、自分のシフトだけの購読URL欄を追加しました。
「コピー」ボタンを押すと文言が「コピーしました」に変わります。
ドキュメント
help.htmlに第6章「Googleカレンダーに表示する」を追加(学生向け・キャプチャ付き)README.mdのエンドポイント表・トークン設計・セキュリティ注意を更新回帰修正
config.ICAL_TOKENを廃止した結果、管理者の「iCal 購読URL(全シフト)」欄が常に「ICAL_TOKEN が未設定です」表示になる不具合を作り込んでいました。既存テストでは検出できなかったため、サーバーから取得する方式に変更したうえで再発防止テストを追加しています。テスト
CLAUDE.md の TDD 方針に従い、失敗するテストを先に書いてから実装しました。
新規テスト:
server/src/__tests__/utils/icalToken.test.ts— トークン導出・検証server/src/__tests__/routes/ical.test.ts— 他人の user_id で 401 になること等server/src/__tests__/routes/config.test.ts— 共通トークンを配布しないことtest/my-shifts-ical-url.test.js— URL組み立て・コピーtest/admin-ical-url.test.js— 上記回帰の再発防止テストが実装を実際に検証していることを確認するため、ロジックを一時的に旧挙動へ戻して該当テストが落ちることも確認済みです。
実サーバーでの動作確認
確認をお願いしたい点
/api/ical/all-urlの認可はメールアドレスをクエリで受け取る方式にしました。既存 API に認可チェックがないため最小限に留めていますが、本来はサーバー側で JWT 検証をするのが筋だと思います(別 PR 向け)。help.htmlの章番号を振り直しています(6〜8 → 7〜9)。🤖 Generated with Claude Code