本文へ移動

受け取りの流れ/ Receiving

URL を開いてから、手元にファイルが届くまで

受取人がすることは「送られてきた URL を開く」だけです。その先で、リンクがまだ生きているかを確かめ、必要な確認を順に通し、ファイルを渡す。この一連は 8 本の公開 API で構成されていて、それぞれが「何をして、何をしないか」をはっきり分けて持っています。ここではその動きを、全体像・時系列・画面の状態、の 3 つの角度から追います。

01

受け取りの全体像End-to-End Flow

発行の図と同じく、上段が業務としての流れ、下段がそれを実現している技術としての処理です。受取人はログインしていないため、身元の手がかりは「URL を知っていること」から始まります。

図 04 · 受け取りの背骨 — 業務と技術の対応
業務の流れ技術の処理STEP 1URL を開くログインは不要STEP 2確認を通すパスワード → 本人確認STEP 3中身を確かめる名前・大きさ・件数・期限STEP 4受け取るファイル or ZIP状態の解決見つからない・期限切れ無効化・回数上限は確認より先に伝える4 層のレート制限を通る照合と通過の記録パスワードは scrypt で照合確認コードは 6 桁・10 分通過ごとに認可 Cookie を更新(短時間)範囲を限定した読み取り対象範囲 ∩ 検査済み∩ 最新版 だけが見えるフォルダは配下一覧を100 件ずつ返す配布と記録署名付き URL を発行ファイル・ZIP とも短時間回数を再検証してから1 増やし、記録を残すファイルの中身が API を通ることはない。API が渡すのは短時間だけ有効な URL で、実際の取得はブラウザと Cloud Storage が直接行う。

受取ページの表示・確認・配布はすべて同じオリジンの API(/api/share-links/*)を呼びます。本体ホストの API は呼びません。

02

状態の解決順Status Resolution

URL を開くと、まず「このリンクは今どういう状態か」を 1 回の問い合わせで解決します。上から順に当てはまった最初のものを返します。

順状態受取人に見えるもの記録
1見つからない「リンクが見つかりません」+ URL の確認をお願いする案内監査ログのみ(記録先の行が存在しないため)
2期限切れ「有効期限が切れています」+ 発行者へ再発行を依頼する案内アクセス記録
3無効化済み「無効化されています」+ 発行者へ問い合わせる案内アクセス記録
4回数上限「ダウンロード回数の上限に達しました」アクセス記録
5パスワードが必要パスワード入力フォーム(ファイル名は出さない)—
6本人確認が必要メールアドレス入力 → 確認コード入力—
7受け取り可対象名・発行者名・期限・残り回数・大きさ(フォルダは件数と中身)アクセス記録(開封)

期限切れ・無効化・回数上限を、確認より先に伝える

素直に作ると「まず本人だと確かめてから、状態を教える」順になります。しかしそれをすると、受取人はパスワードを探し回った末に「期限切れでした」に着地します。パスワードが分からないのか、そもそも開けないのかが分からないまま時間を使わせることになります。

そこで、期限切れ・無効化・回数上限は確認を通す前に開示します。これらはリンクを持っている人にとって「探しても解決しない」情報なので、早く伝えるほど親切です。逆に、対象のファイル名は確認を通すまで出しません。URL だけを手に入れた第三者に、中身のヒントを与えないためです。

「見つからない」だけ記録の置き場所が違う理由: アクセス記録は「どのリンクへのアクセスか」を必ず持つ形になっています。存在しないリンクにはその結び付け先が無いため、アクセス記録には書けません。代わりに監査ログへ「不明なリンクへのアクセス」として残します。
03

二段の確認Two Gates

パスワードと本人確認はそれぞれ独立に設定できます。両方が設定されたリンクは、両方を通らなければ開きません。「どちらか片方が通れば全部開く」形にはしていません。

リンクの設定受取人が通る道
どちらも無しURL を開く → 受け取り可
パスワードのみURL を開く → パスワード → 受け取り可
本人確認のみURL を開く → メール入力 → 確認コード → 受け取り可
両方URL を開く → パスワード → メール入力 → 確認コード → 受け取り可

通過を持ち歩く仕組み

確認を 1 つ通るたびに、「何を通ったか」を書いた短命の Cookie を発行します。これは署名が付いていて改ざんできず、有効期間は 短時間です。次の確認を通ると、その分が足されて有効期間も更新されます。ダウンロードの時点で「このリンクに設定されている確認がすべて含まれているか」を見ます。

Cookie の中身はリンクの識別子・通過済みの確認・失効時刻だけで、照合のためにデータベースを引き直す必要がありません。属性は HttpOnly(スクリプトから読めない)・SameSite=Strict(他サイトからの遷移では送られない)・Secure(本番)で、リンクごとに別の Cookie として分かれています。

順番を「パスワードが先」に固定している理由: パスワードが設定されたリンクでは、確認コードの送信もパスワードを通ってからでないと受け付けません。逆にすると、URL を手に入れただけの第三者が受取人のメールボックスへコードを何度も送らせられるようになります。パスワードのロック機構を、メール送信の踏み台に使わせないための順序です。
04

処理シーケンスSequence

同じ流れを、誰が誰に何を渡すか、という時間順で追います。破線で囲んだ 4 つの区画は条件によって通る/通らないものです(パスワードが設定されていなければ 1 つ目の区画は丸ごと飛ばします)。

図 05 · 処理シーケンス — URL を開いてからファイルが届くまで
受取人のブラウザCloud Armor+ 負荷分散matsuba-api公開 8 端点PostgreSQLResendCloud StorageCloud Run Jobs① 受け取った URL を開く② 外周の上限(IP ごとのレート制限)を通す③ リンクを引いて判定(期限・無効化・回数)IF — パスワードが設定されているとき④「パスワードが必要」(ファイル名は返さない)⑤ パスワードを送る⑥ 直近 1 時間の失敗数を数えてロックを判定 → scrypt で照合⑦ 通過を認可 Cookie に記録(短時間)IF — 受取人の本人確認が設定されているとき⑧「本人確認が必要」⑨ メールアドレスを送る(一致しなくても同じ応答)⑩ 確認コードを保存(10 分・照合用の値だけ)⑪ 6 桁のコードを送信⑫ 受取人にコードが届く⑬ コードを送る → 照合して認可 Cookie を更新⑭ 受取画面を表示(対象名・発行者・期限・残り回数)+ 開封を記録IF — 対象がファイルのとき⑮ ダウンロードを要求⑯ 行を押さえて回数を再検証 → 1 増やす → 記録を残す⑰ 署名付き URL を発行(短時間・原ファイル名で保存される)⑱ URL を返す⑲ ブラウザが Cloud Storage から直接取得(中身は API を通らない)IF — 対象がフォルダのとき(ZIP)⑳ ZIP の準備を要求(受取画面に着いた時点で自動的に)㉑ 状態を「準備中」に進める(同時に来ても 1 回だけ通る)㉒ ZIP 生成を起動(完了は待たない)㉓ 原本を読み ZIP を書く㉔ 進捗を確認(5 秒ごと・何も変えない読み取り)㉕ 準備できたら 1 回だけダウンロードを要求 → 署名付き URL(短時間)

⑯ で「行を押さえてから再検証する」のは、同時に複数のダウンロードが走ったときに回数の上限を超えさせないためです。㉑ の「同時に来ても 1 回だけ通る」も同じ考えで、ZIP の二重生成を防いでいます。

05

画面の状態遷移View States

受取ページが取りうる状態です。エラーの 4 つは行き止まりですが、いずれも次にどうすればよいかを必ず添えています。

図 06 · 受取ページの状態遷移
① 確認中状態を解決② パスワード入力残り回数を表示③ 本人確認メール入力 → コード入力④ 受け取り可開封を記録⑤ ダウンロード開始をお知らせ⑥ ZIP 準備中5 秒ごとに確認⑦ ZIP 失敗再試行できるフォルダ準備できた失敗再試行開けないとき(確認より先に伝える)期限切れ再発行の依頼を案内無効化済み発行者への連絡を案内見つからないURL の確認を案内回数上限再発行の依頼を案内通過済みの確認は 短時間 で切れる。切れた状態でダウンロードや進捗確認を行うと、「もう一度ご確認ください」を表示して ② または ③ からやり直す(最初からではない)。

②③ は、リンクに設定されているときだけ現れます。どちらも設定が無ければ ① から ④ へ直行します。

(画面の画像は掲載していません)
② パスワード入力。誤ると残りの試行回数を添えて伝え、上限に達するとロックの案内に切り替わります。対象のファイル名はこの段階では出しません。
(画面の画像は掲載していません)
③ 本人確認(確認コードの入力)。宛先は伏せ字で表示し、6 桁を入れ終えた時点で自動的に送信します。再送は 60 秒の間隔を空けます。
(画面の画像は掲載していません)
④ 受け取り可(ファイル)。対象名・発行者・種別と大きさ・期限(絶対時刻と残り日数)・残り回数。閲覧とダウンロードが記録される旨もここで伝えます。
(画面の画像は掲載していません)
④ 受け取り可(フォルダ)。まとめてダウンロードのボタンと、中身を確認できる折りたたみリスト(件数つき)。
(画面の画像は掲載していません)
エラーの表示(無効化済み)。原因と、次にどうすればよいかを必ず添えます。
06

ファイルのダウンロードFile Download

ダウンロードボタンを押すと、API はファイルの中身ではなく、短時間だけ有効な URL を返します。ブラウザはその URL へ移動し、Cloud Storage から直接受け取ります。

段やること
1確認がすべて通っているかを Cookie で見る(足りなければ確認へ戻す)
2範囲を限定した読み取りで対象と最新版を取る(範囲外の指定はそもそも 0 件になる)
3署名付き URL を発行(ファイルも ZIP も短時間だけ有効。保存時のファイル名は元の名前)
4行を押さえて回数を再検証 → 超えていれば発行した URL を捨てて上限エラー
5回数を 1 増やし、アクセス記録と監査ログを残す
6URL を返す。ブラウザが移動して取得を開始する

段 3 と段 4 の順序が逆に見えるかもしれませんが、これは意図したものです。署名付き URL の発行は外部サービスへの呼び出しなので、データベースの行を押さえたまま行いたくありません。先に URL を作っておき、行を押さえるのは最後の一瞬だけにして、そこで条件を満たさなければ作った URL を捨てます。

ブラウザ側でも回数を守っています: 一度取得した URL は、ブラウザ側で 有効期限より少し短い間保持します。保存ダイアログをキャンセルしてもう一度押したときは、取り直さず同じ URL へ移動します。押し直しのたびに回数が減る、という体験を避けるためです。

ダウンロードを開始したことは、ボタンの下に文章でお知らせします。ブラウザのダウンロードバーが目立たない環境では「押したのに何も起きていない」ように見えるためです。

07

フォルダの ZIPFolder ZIP

フォルダを共有した場合は、配下のファイルを ZIP にまとめて渡します。ただし ZIP の生成には時間がかかるため、「準備」と「配布」を別々の入口に分けています。

図 07 · ZIP の状態と、3 つの入口の役割分担
未生成ZIP はまだ無い準備中生成が動いている準備済み配布できる失敗受取人に再試行を案内準備を要求生成が完了72 時間で失効 → 日次の掃除処理が「未生成」へ戻す生成に失敗もう一度「準備」で復帰準備の入口・状態を進める唯一の入口・回数は数えない・受取画面に着いた時点で先読み起動(画面には出さない)配布の入口・回数を数える唯一の入口・「準備済み」のときだけ渡す・ZIP 一式で 1 回(個別なら 1 ファイルで 1 回)進捗の入口 ・何も変えない読み取り。5 秒ごとに確認し、2 分で待機表示を打ち切る(生成そのものは続く)

保管期限は 72 時間、配布に使えるのは それより少し手前までです。この差は、自動削除の直前に配ってしまい「開けない ZIP の URL」を渡す事故を避けるための余裕です。

入口を分けた理由

受取画面に着いた時点で、裏側では静かに ZIP の準備を始めています(先読み)。押したときには多くの場合できあがっているので、待ち時間が実質ゼロになります。

(画面の画像は掲載していません)
先読みが効いている状態でボタンを押したところ。ZIP はすでにできあがっているため、待ち時間なくそのまま配布が始まり、残り回数がこの時点で 1 つ減ります。準備が間に合わなかったときだけ、同じ位置に進行状況と経過時間が出ます(60 秒を超えると「ファイル数が多いため時間がかかっています」を添えます)。

ここで、もし「準備」と「配布」が同じ入口だったらどうなるか。先読みの呼び出しが、すでに準備済みだったときに配布として扱われ、誰も押していないのに回数を 1 消費します。「1 回だけ」のリンクなら、受取人が画面を開いた瞬間に使い切られてしまいます。準備は状態を進めるだけ、配布は渡すだけ、と役割を分けることで、この事故を構造的に起こせなくしました。

進捗の確認も同じ理由で「何も変えない読み取り」に徹しています。5 秒ごとの確認や、画面に戻ったときの再取得で回数が減っては困るからです。

しきい値を超えるフォルダ

合計 200 MB または 10 ファイルを超えるフォルダは ZIP にせず、配下の一覧を出して 1 件ずつダウンロードしていただきます。一覧は 100 件ずつ読み込み、101 件目以降にも到達できます。このとき回数は 1 ファイルにつき 1 回消費されるため、その旨を一覧の上に添えています。

08

開封の記録Opened

受取人が必要な確認をすべて通り、受取画面に着いた瞬間を「開封」として記録します。発行者が「渡した相手が開いてくれたか」を確認できるようにするためです。

ただし、受取画面は再読み込みやタブの切り替えで何度も表示されます。そのたびに記録すると、1 人の受取人が何十件もの記録を作ってしまいます。そこで、同じリンク × 同じ接続元について、直近の一定時間に 1 件だけ記録します。

この記録は、発行者側の一覧で「最終アクセス」として見えます。ダウンロードがまだ 0 件でも最終アクセスに時刻が入っていれば、「開いてはもらえたが、まだ受け取っていない」と分かります。

ダウンロードだけを記録しない理由: 「送ったのに反応がない」とき、相手が URL を開けていないのか、開いたが受け取っていないのかで、次にすべきことが違います。前者ならリンクの再送、後者なら相手の判断待ちです。開封を分けて記録しているのは、この 2 つを区別するためです。