← ガイダンス一覧へ戻る

1アクセス確認と共通の応答

Zettelkasten Inbox / ブロック詳細

12あるAPIすべての先頭に立つ門。サインイン済みかを1ビットだけ見て通し、どこで失敗しても同じ形のエラーに揃えて返す。8工程しかないが、このアプリの安全性はここの前提に全部乗っている。

抽象度 Lv.4(技術用語+システム上のやりとりまで/製品名・ライブラリ名は書かない)

1. 概要

12あるAPIすべての、いちばん最初に置かれている門。やることは2つだけで、 サインイン済みかどうかを見て通すか止めるか決めることと、 途中でどんな失敗が起きても、利用者に同じ形のエラーを返すこと

ここが薄いのは意図的な割り切りに見える。このアプリは「誰が使ったか」を一切区別しない。 サインインできる人は全員、同じNotionワークスペースの同じ3つのデータベースを読み書きする。 つまりこの門は「知らない人を入れない」ためだけにあり、「人ごとに見せるものを変える」ためには使われていない。

2. 位置づけ

利用者(ブラウザ) → 1. アクセス確認と共通の応答 → 2〜11のすべてのブロック

3. 処理内容(Lv.4)

4. なぜこの位置にあるか(設計意図)

ここより後ろには置けない。2番以降はどれも、いきなりNotionへ要求を投げる。 身元を確かめる前にNotionを呼んでしまうと、サインインしていない人の要求でも Notion側の呼び出し回数を消費し、上限(429)に近づいてしまう。だから門は必ず手前に立つ。

戻り道の応答づくりも同じ場所に置かれている理由。 12本のAPIがそれぞれ勝手にエラーの形を決めると、画面側は12通りの読み方を覚えることになる。 ここで {"error": "…"} という1つの形に揃えているので、画面側は 「error という項目があればその文字を出す、無ければ既定の文言」という1つの処理で済む。

逆に、ここに置かれていないものもある。要求の回数制限(レート制限)、 操作の記録(ログ)、利用者ごとの権限の切り分け。どれもこの門には無い。 個人用の道具として、必要になるまで作らない判断がされている。

5. 詳細プロセス分解

工程主体やりとりの内容方向
1-1ブラウザ → APIの入口HTTPの要求そのもの
1-2APIの入口 → アクセス確認確認の依頼(関数呼び出し)
1-3アクセス確認(内部)実行環境が本番かどうかの判定
1-4アクセス確認(内部)身元ヘッダが存在するかの判定
1-5アクセス確認 → APIの入口通過、または401相当の例外
1-6APIの入口 → 応答づくり捕まえた例外(失敗したときだけ)
1-7応答づくり(内部)例外の種類からステータスコードを決定
1-8応答づくり → ブラウザ同じ形のJSONエラー

各行を文章にすると

1-1 利用者がボタンを押すか画面を開くと、ブラウザからサーバーへ1回の要求が飛ぶ。 このアプリでは、画面の枠組みだけをサーバーが組み立てて返し、 中身のデータは全部、画面が読み込まれたあとにブラウザから取りに行く作りになっている。

技術的には 12本のAPIはすべて /api/ の下にあり、 読み取りは GET、書き込みや大きな入力は POST、削除は DELETE を使う。 GETとPOSTを分けているのは、GETが「何度呼んでも状態が変わらない」約束の付いたメソッドだから。 候補の下読み(6番)と原典プレビュー(4番)だけは、読み取りなのに POST を使っている。 対象のIDやURLをまとめて本文に入れて送るためで、URLの長さの制限を避ける目的。 12本すべてに「毎回サーバーで作り直す」印が付いており、生成した結果を使い回さない。

1-2 APIの本体は、自分の仕事を始める前に、まず共通の確認係へ「この要求、通していいか」と尋ねる。 この問い合わせはネットワークを通らない。同じサーバーの中での呼び出しである。

技術的には HTTPではなく関数呼び出し。 通信が起きないので、形式の変換も、認証のやり直しも、待ち時間もほぼ発生しない。 この確認係は12本すべてから同じ形で呼ばれ、通れば何も返さず、通らなければ例外を投げる。 「値を返して呼び出し側に判定させる」のではなく例外にしているのは、 呼び出し側が確認結果を見忘れても、処理が先へ進めないようにするため。

1-3 確認係はまず「いま自分が本番で動いているのか」を見る。 本番でなければ、そこで確認は終わり。誰でも通る。

技術的には 実行環境の種類を示す環境変数を見て、 本番以外なら即座に戻る。手元での開発中に、毎回サインインの仕組みを立ち上げなくて済むようにするための逃げ道。 裏を返すと、この分岐を持つアプリを本番以外の設定でインターネットに公開すると、認証が完全に無くなる。 公開時の環境設定が、そのままアクセス制御の一部になっている。

1-4 本番なら、要求に付いてきた「あなたは誰々です」という札が有るか無いかだけを見る。 札の中身が誰なのかは読まない。

技術的には 要求ヘッダの oai-authenticated-user-id を取り出し、 空でなければ通す。この札はアプリが作るのではなく、 アプリを載せている土台(ホスティング側)がサインイン済みの要求に差し込むもの。 アプリ側は署名の検証も有効期限の確認もしない。「土台が差し込んだ札は正しい」という前提に全面的に乗っている。 PINの照合、パスワードのハッシュ、Cookieへの署名、OAuth 2.0 のような外部の認可の流れは、 このアプリのコードには一切入っていない。サインイン用の補助コード一式はファイルとして置かれているが、 どのページからも呼ばれていない未使用のコードである。

1-5 通れば何も起きず、APIの本体が自分の仕事を始める。通らなければ、 その場で処理が中断されて、外側へ飛ばされる。

技術的には 例外には 401 という数字と 「このアプリを利用するにはサインインが必要です。」という日本語が乗っている。 投げられるのは要求本文を読む前なので、 サインインしていない相手から大きな画像を送りつけられても、受け取り切らずに済む。

1-6 APIの本体は、自分の中で起きた失敗を全部ひとまとめに受け止めて、 応答づくりの係へ渡す。認証の失敗も、Notion側の失敗も、想定外の不具合も、同じ入口を通る。

技術的には 12本すべてが処理全体を try で囲み、 例外を1か所に集めてから応答づくりの関数へ渡す。 途中で個別にエラー応答を組み立てている場所は、入力検査(400)だけ。 それ以外は必ずここへ落ちてくる。

1-7 応答づくりは、渡された失敗が「サインインの問題」か「Notion側の問題」か 「それ以外」かを見分けて、返す番号を決める。

技術的には 3段の分岐になっている。 (1) 認証の例外なら 401。 (2) Notion由来の例外なら、Notionが返したコードをそのまま使う―― Notionが 429 なら 429、404 なら 404、403 なら 403。 メッセージも日本語に置き換えたうえで、Notionの言い分を伝える (「Notion APIキーが無効です。」「対象のNotionデータベースが連携に共有されていません。」など)。 (3) それ以外は、詳細をサーバー側の記録にだけ出して、利用者には 500 と 当たり障りのない一文を返す。内部の作りが漏れる情報を外に出さないための定石。

1-8 どのAPIで何が起きても、ブラウザには同じ形のエラーが届く。 だから画面側は、エラーの読み方を1つ覚えるだけで全部に対応できる。

技術的には 本文は必ず application/json{"error": "日本語の一文"} の形。画面側は 「error という文字列の項目があればそれを表示、無ければ画面ごとの既定文」という 共通の関数1つで処理している。

ワークフロー図

← 図は横にスクロールできます

ブラウザ
APIの入口
アクセス確認
応答づくり
1-1 要求(メソッド・パス・本文)
1-2 確認の依頼(関数呼び出し)
1-3 実行環境が本番かを見る
1-4 身元ヘッダの有無だけを見る
1-5 通過、または例外を投げる
1-6 捕まえた例外を渡す
1-7 種類を見分けてコードを決める
1-8 JSONのエラー(401 / 4xx / 500)

6. この分解から見えること

  • 認証は「有るか無いか」の1ビットしか見ていない。 利用者IDの値を使えば「この人が作ったノートだけ」といった絞り込みもできるが、していない。 個人用の道具である、という前提が設計に埋め込まれている。
  • 本番かどうかの判定が、事実上のスイッチになっている。 本番以外=認証なし、という作りなので、公開設定を間違えると門が丸ごと開く。 アプリの中で最も壊れやすい前提はここ。
  • Notionのコードを透過させるのは、素直だが両刃。 利用者は「Notion側が混んでいる(429)」「共有されていない(404)」を正しく知れる一方で、 アプリ側の都合とNotion側の都合が同じ番号で混ざる。 404が返ったとき、URLを間違えたのかNotionの共有漏れなのかは、番号だけでは判別できない。
  • この門には回数の制限が無い。サインインさえ通れば、 全体マップ(10番)のような重い全件取得を何度でも呼べる。 Notion側の 429 に当たると自動で最大2回まで待って再送するので、 混んでいるときほど1回の要求が長く居座る形になる。

7. 失敗時の挙動

  • サインインしていない:401{"error": "このアプリを利用するにはサインインが必要です。"}。 画面には「Notionを読み込めませんでした」と再読み込みボタンが出る。
  • Notionのトークンが未設定:503 +「Notion接続情報が設定されていません。」。 Notionへは1度も要求が飛ばない。
  • Notionが 429 を返した:Retry-After の秒数を1〜5秒に丸めて待ち、 最大2回まで自動で送り直す。それでも駄目なら 429 + 「Notionへのアクセスが混み合っています。少し待って再実行してください。」
  • 想定外の不具合:500。詳細はサーバー側の記録にしか残らない。 利用者側からは何が起きたか分からない。
  • どこまで進んでから失敗すると何が残るか:このブロック単体では何も残らない。 まだNotionを1度も触っていないため。危険なのは8番の途中で失敗したときで、 そちらは書きかけの状態がNotionに残る(8番の7章参照)。

8. 未確認事項

  • 土台(ホスティング側)が身元ヘッダをどう検証して差し込んでいるか。アプリ側からは確認できない
  • 身元ヘッダを外から偽って送り込めるかどうか。土台が上書きしている前提だが、未確認
  • 500 で隠した詳細が、実際にどこへ記録され、誰が読めるのか(未確認)
  • 本番以外の設定でこのアプリが公開されたことがあるかどうか
  • 使われていないサインイン用の補助コードを、今後使う予定があるのか

※ 2026年8月14〜15日時点の zettelkasten-inbox のプログラムを読んで作成しました。過去の設計資料は参照していません。

※ 番号は全体図・この解説・シーケンス図で共通です。工程番号は「ブロック番号-連番」で書いています。

全体図へ戻る / ブロック目次へ

akuramochi1.com — personal tools for learning and thinking.