前々からMarkdownで日記を書いてGitHubで管理していたのですが、
見づらいし書きづらかったのでWebサイトにしました。
機能
- 日記を暗号化しパスワードを入力することで閲覧可能にする
- マスターパスワードが入力されているとき1エントリーのみ閲覧可能な限定URLを生成する
- 検索
- GitHub OAuthでログインして日記を書く
暗号化まわりAI任せなのでちょっと心配です。もしクラックできた方は恥ずかしいのでこっそり教えてください。
パスワードが合ってるかの確認はできるのですが、合っていなくても復号化失敗して変な文字が出たらわくわくするかなと思ったのでそうしています。
技術的な話
構成はAstroとSvelteで、ホスティングはCloudflare Workersです。
日記本文は別のprivateリポジトリにMarkdownで置いていて、デプロイ時にGitHub Actionsからそのリポジトリを取得し、ビルド前にJSONへ変換しています。
公開してよい日記はそのまま、非公開の日記はタイトル・本文・タグなどを暗号化してpublic/diary.encrypted.jsonにまとめています。
暗号化では、まずマスターパスワードからroot secretを作ります。
そのroot secretから各日記ごとのentry secretを作り、実際の本文はentry secret由来の鍵で暗号化しています。
こうすると、マスターパスワードを知っている人は全部読めますが、entry secretだけを渡された人はその1件だけ読めます。
限定URLはこのentry secretを使っています。
URLにはentrypw-v1.<entry-id>.<secret>という形の値を入れています。
開いた側ではentry idに対応する1件だけを探し、URL内のsecretから復号鍵を作ります。
マスターパスワードそのものを渡さないので、限定URLを共有しても他の日記までは読めません。
この方式にしたかった理由は、日記単位で共有したかったからです。
最初から各エントリーを別の鍵で暗号化しておくと、後から共有用のURLを作るだけで済みます。
書く機能はGitHub OAuthでログインしてから使えるようにしました。
ログイン後に対象の日記リポジトリへのpush権限があるかをGitHub APIで確認し、保存時はGitHub Git Data APIを使ってMarkdownファイルを追加・更新しています。
Contents APIで1ファイルだけ更新する形でもよかったのですが、今回はGit Data APIを使いました。
既存ツリーを元にtreeを作ってcommitを作り、branch refを更新する流れです。
この形にしたので、作成と編集を同じように扱えます。
日記を書くたびにコミットが増えすぎるのも嫌だったので、最新コミットの日付が今日の日付と一致するときは、そのコミットを置き換えるようにしています。
Web UIから細かく追記しても、その日の変更が1つのコミットにまとまるのが目的です。
普通のGit操作でいうとamendに近いことをGit Data APIでやっています。
画像はGitHubリポジトリには入れず、Cloudflare R2に保存しています。
Markdownのfrontmatterには画像のキー、content type、幅、高さ、公開範囲だけを持たせています。
画像本体の取得には/api/diary-imageを使います。
画像アップロード時にはブラウザ側でJPEGに圧縮してからR2へ送っています。
画像の公開範囲は日記本文とは少し分けています。
公開日記の公開画像はそのまま返しますが、公開日記に付けた非公開画像や非公開日記の画像は、パスワードまたは限定URLで権限を確認してから返します。
非公開日記の限定URLでは、その1件の画像メタデータだけを復号し、要求された画像キーが含まれているかを見て配信可否を決めています。