32文字のはずの鍵が53文字で入っていた — 買っていない人が5日間「有料会員」に見えていた — 制作日誌 #35
制作日誌

32文字のはずの鍵が53文字で入っていた — 買っていない人が5日間「有料会員」に見えていた — 制作日誌 #35

推し活アプリで、課金していない利用者が有料プランとして扱われていた。原因はサーバーに入れた鍵の余計な空白と、「確認に失敗したら有料扱いにする」という自分の書いた一行だった。どちらに倒すかの判断を、数字と一緒に残す。

KIYODO
#個人開発#アプリ開発#課金#失敗の記録#制作日誌

会員制のジムで、受付の機械が会員証を読めなくなったとする。機械の設定は「読めなかったら、とりあえず通す」。正規の会員は困らない。ただ、会員証を持っていない人も、同じように通れてしまう。しかも、警報は鳴らない。機械にとって「読めなかった」は、異常ではなく想定内の分岐だからだ。

自作の推し活アプリで5日間起きていたのは、これだった。

例え実際
受付の機械アプリのサーバー(裏側で動くプログラム)
会員証を読む装置の鍵課金の状態を問い合わせるための鍵
「読めなかったら通す」「確認に失敗したら有料扱い」という分岐
会員証を持たない人課金していない利用者

ジムと違う所もある。ジムなら入口で誰かが気づく。アプリでは、有料の画面が開くこと自体は何も不自然ではないので、外から見てもおかしさが分からない。

数字で見ると

先に、測った数字を並べておく。

項目値
鍵の本来の長さ32文字
実際にサーバーに入っていた長さ53文字(途中に空白入り)
課金確認の問い合わせへの返事毎回「401」=鍵が違うので断る
鍵を入れてから気づくまで5日(9月28日→10月3日)
有料プランの値段月300円

32文字のはずが53文字。21文字ぶん余計なものが混ざっていて、途中には空白が入っていた。

なぜ鍵が壊れたか

サーバーに秘密の値を入れる時は、専用の道具に値を流し込む。私は Windows の端末から、値を画面に出してそのまま道具へ渡す、というやり方をしていた。

この渡し方だと、見えない改行や空白が値の後ろや途中にくっつくことがある。画面上では鍵はきれいに見える。サーバー側も、入れた値の中身は二度と見せてくれない。秘密の値なので、それが正しい作りだ。結果として、壊れた鍵が入ったことに、入れた瞬間は誰も気づけない。

しかも、この鍵を入れたのは、有料プランを売り始める準備の最中だった。同じ日に、値段の違う商品をいくつも登録し、別のアプリの設定も並行して触っていた。鍵を入れる作業は、その中の1行にすぎなかった。入れた後に「この鍵で本当に問い合わせが通るか」を試す手順は、どこにも書いていなかった。

課金の管理を任せている外部のサービスは、壊れた鍵での問い合わせを、毎回きちんと断っていた。断り方も正しい。悪かったのは、断られた後のこちらの分岐だった。

分かれ道は1行だった

サーバーには、こういう判断が書いてあった。

  • 課金の状態を問い合わせる
  • 返事が来て「有料」なら、有料
  • 返事が来て「無料」なら、無料
  • 問い合わせ自体に失敗したら、有料

最後の1行は、私が意図して書いたものだ。理由は「お金を払ってくれた人を、こちらの不具合で締め出したくない」。外部サービスが一時的に落ちた時、払った人が急に無料に戻されたら、それは最悪の体験になる。そう考えて、失敗した時は甘い方に倒した。

この判断は、鍵が正しい前提では筋が通っている。外部サービスが落ちるのは年に数回、それも数分だろう。その間だけ全員が有料に見えても、被害は小さい。

ところが、鍵が壊れていると「失敗」が数分ではなく、ずっと続く。年に数回のはずの例外が、毎回の通常運転になる。甘い方に倒す分岐は、例外がまれだという前提の上でしか安全ではなかった。

なぜ5日も気づかなかったか

気づけなかった理由は3つ重なっていた。

1つ目。失敗を記録していなかった。記録していたのは、通信そのものが途切れたような「本当の異常」だけだった。外部サービスから「鍵が違う」という返事が正常に届くのは、プログラムにとっては異常ではない。だから、5日間ずっと断られ続けても、記録には何も残らなかった。

2つ目。私自身のアカウントは、開発者として最初から素通りになっていた。自分で有料画面を開いて確かめても、問い合わせの道をそもそも通らない。毎日アプリを触っていても、壊れた道を一度も踏まなかった。

3つ目。症状が「得をする」方向だった。有料の画面が使えなくなったら、誰かがすぐ声を上げる。使えてしまう分には、誰も困らない。

結局、ある利用者の扱いを調べた時に、課金の記録が無いのに有料として扱われていることが見つかった。そこから鍵の長さを測って、53文字だと分かった。

直したこと

何をどう変えたか
失敗した時の倒し方有料扱い → 無料扱い。その代わり失敗を記録する
記録する範囲通信の途切れだけ → 「200(成功)以外の返事」と「鍵が無い」も
鍵の点検無し → 定期的に、サーバーが自分の鍵の長さ・空白の有無・問い合わせ結果を書き残す
鍵の入れ方画面経由で渡す → 改行を付けない書き方で流し込み、直後に本物の問い合わせで成功を確かめる

鍵の点検で、中身そのものは書き残さない。残すのは「長さ」「空白が入っているか」「最後の問い合わせの返事」の3つだけにした。鍵は秘密の値なので、記録に中身を出せば、それ自体が新しい事故の種になる。長さと空白の有無なら、外に漏れても鍵の再現には使えない。それでいて、今回の壊れ方は、この2つを見れば一目で分かった。32と出るはずの所に53と出て、空白ありと出る。中身を見なくても、壊れていることだけは確実に言える。

点検の結果は、サーバーの小さな保管庫に毎回上書きで置く。私が気になった時に、1行の命令で読み出せる。異常を自分から知らせてくる仕組みではないので、ここはまだ半分だと思っている。

倒し方を変えるのは、迷った。払った人を締め出す危険が、今度はこちら側に移るからだ。それでも無料側に倒したのは、2つの失敗の見え方が違うからだ。払った人が締め出されれば、その人は必ず気づいて連絡をくれる。払っていない人に有料が渡っても、誰も気づかない。気づける方の失敗を選んだ。

有料に見えていた利用者

5日間、有料プランとして使えていた利用者が実際にいた。その人には落ち度が何もない。こちらの不具合で一度渡したものを取り上げるのは、筋が悪いと考えた。

そこで、その利用者には、課金管理の画面から有料プランを期限なしで付けた。不具合の後始末としては、取り上げるより、正式に渡し直す方を選んだ。

残ったこと

「失敗したら甘い方に倒す」は、悪い判断ではない。ただ、その判断は「失敗はまれ」という前提とセットでしか成り立たない。前提が崩れた時に気づくには、失敗の回数を数えていなければならなかった。

もう1つ。開発者のアカウントで動作を確かめても、一般の利用者が通る道を確かめたことにはならない。自分だけが通れる近道を作った時点で、その近道の外側は、自分の目には見えなくなる。

コメント (0)

まだコメントはありません。最初の一言を残しませんか?