Mituke — 忘れ物管理システム

システム全体像

WPFデスクトップアプリ・Tailscale Funnel・PHP/MySQLサーバーが、どう繋がってログインからデータ表示までを成立させているかをまとめています。

01 — 構成

クライアントからDBまでの経路

アプリはインターネット上の固定IPやポート開放を一切使わず、Tailscale Funnelが発行する1つのURLだけを通じてサーバーと通信します。

WPFクライアント (Windows PC) Views / ViewModels Services (HttpClient) CurrentUserService (token) HTTPS Bearer token Tailscale Funnel mix2k.icefish-ayu.ts.net ポート開放・固定IP不要 tailnetの外にも公開するトンネル tailnet 経由 Ubuntuサーバー (mix2k) — Apache + PHP login.php get_items.php add_inquiry.php upload_item.php  ほか計7本 config.php /var/www/serverconfig/ — Web非公開 PDO MySQL (mix2k) users items inquiries
クライアントは 公開IPを持たないサーバー に、Tailscale Funnelが払い出すmix2k.icefish-ayu.ts.netという1つのURL経由でのみ到達する。DB接続情報とトークン署名鍵は、Webから読める場所(/var/www/html)の外に置かれている。
Why Funnel

サーバーはTailscaleのプライベートネットワーク(tailnet)の中にしか存在せず、ルーターのポート開放も固定グローバルIPの取得もしていません。

Funnel機能がmix2k.icefish-ayu.ts.netというHTTPS URLを生成し、そこ宛のリクエストだけをトンネル越しにサーバーへ届けます。公開範囲を1つのホスト名に絞れるため、個人運用のサーバーでも安全に外部公開できます。

02 — 認証

ログインからAPI呼び出しまでのトークンの流れ

セッションCookieではなく、ログイン時に発行される署名付きトークンを毎回のリクエストに添えることで本人確認しています。

クライアント サーバー (login.php / auth.php) MySQL POST /login.php {user_id, password} SELECT password, type WHERE user_id=? 該当ユーザーの行 password_verify() で検証 (平文の旧パスワードは検証成功時に自動でハッシュへ移行) token 発行(HMAC署名・8時間有効) 以降のAPIリクエスト全般 GET /get_items.php Authorization: Bearer <token> auth.php が署名と有効期限を検証 管理者専用APIは type も確認 200 OK + データ(成功時) 401 Unauthorized(期限切れ・不正時) HandleSessionExpired() ログイン画面へ自動的に戻す
トークンはログイン時に一度だけ発行され、以降は毎リクエストのAuthorizationヘッダーで検証される。8時間で失効するか、署名が合わない場合は401が返り、クライアントはその場でログイン画面へ戻す。
03 — 機能

役割ごとにできること

同じログイン画面から入り、アカウントの種別(一般ユーザー/管理者)に応じてメニューの内容が変わる。

一般ユーザー

type = 0

  • 拾得物一覧の閲覧・カテゴリ絞り込み・キーワード検索
  • 拾得物の詳細表示
  • 落とし物のお問い合わせ送信(場所・日付・詳細情報)
  • ログアウト

管理者

type = 1

  • 一般ユーザーの全機能に加えて —
  • 拾得物の登録(画像アップロード込み)
  • お問い合わせ一覧の確認
  • ユーザー登録(管理者権限の付与可否を選択)
04 — 設計

セキュリティ設計のポイント

パスワードはハッシュ化 password_hashで保存。旧・平文パスワードはログイン成功時に自動移行
秘密情報はWeb非公開の場所へ DB接続情報・トークン署名鍵は/var/www/htmlの外に配置
管理者専用APIをサーバー側で強制 UIの表示制御だけでなく、auth.phpがtypeを都度検証
アップロード画像を検証 拡張子でなく実ファイルのMIMEタイプを判定し許可形式のみ受理
エラー詳細を外部に漏らさない 例外の内容はサーバーログにのみ記録し、汎用メッセージだけ返す
なりすまし防止 お問い合わせのuser_idはクライアント申告でなくトークンの持ち主を使用