【茶番あり】Vonageを使って秘密のアプリを見れないようにしよう

# Vonageとは？
VonageはコミュニケーションAPIを提供している企業で、かなり簡単にSMSや電話での二段階認証や、コミュニケーションシステムの構築ができるサービスを提供しています。
今回、2023年アドベントカレンダーにてこのVonageが目に入ったのでちょっと記事を書いてみようかなと思いました。

# メイン茶番（？）

## 登場人物

- 主人公: 悠斗
- ヒロイン: 美咲

![スクリーンショット 2023-12-25 210045.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/3e82cebf-cec0-55a1-00b8-0f39725a02bb.png)

## プロローグ

俺は今日、ずっと好きだったの美咲の家で遊ぶことになった。
幾度となく想像してきたこの瞬間が、ついに目の前に広がろうとしていた。

「俺は今日...美咲の家で、告白をする！」

そう決めた俺は、美咲の家に向かった。

## 美咲の家

美咲「あれ、悠斗早かったね！」
悠斗「楽しみでちょっと早く来ちゃってさ」
美咲「とりあえず上がってよ」

美咲の家に入ると、親がいる様子はなかった。

美咲「今日は親いないから部屋であそぼっか」
悠斗「う、うん」

美咲は俺の手を引いて、部屋に連れて行った。

## そこに広がっていたのは...

美咲の部屋に入った俺は、そこに広がっていたものに驚きを隠せなかった。
まるでストリーマーのようなセットが整えられていたのだ。
白いケースのゲーミングパソコンに、整理整頓されたデスクトップ。
他にもいろいろ配信する用の機材のようなものが部屋にはおかれていた。

悠斗「すごいねこれ」
美咲「うん、最近配信を始めててさ。意外とみんなが見てくれてうれしいんだよね」
悠斗「そうなんだ、今度俺にもチャンネル教えてよ」
美咲「もっと有名になったらねっ」

美咲「あ、ちょっと飲み物持ってくるね。お茶でいい？」
悠斗「うん、ありがとう」
美咲「じゃあちょっと待ってて」
美咲「あ、パソコンは勝手に触っちゃダメだよ～？　特にChromeはね？」
悠斗「あぁもちろん」

## そそられる興味

美咲は部屋を出ていった。

...

いやこれはもう開くしかないだろ。
**「特にChromeはね？」** とか言われたらChromeを開きたくなってしまう。

![スクリーンショット 2023-12-25 204903.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/6a0829d7-2579-ae05-eab0-37b31ac1f4fe.png)

悠斗「これが美咲のChrome...」

やってはいけないことはわかっている。

だが...開いてしまえ！
ｶﾁｯｶﾁｯ

![スクリーンショット 2023-12-25 204917.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/d617ee27-0531-3d1b-f99d-ebe0f67b01af.png)

ん？
に、認証プロセス？
認証コードを入れろ？？？

わからん！
だがとりあえずコードを入れるしかないっ...！

![スクリーンショット 2023-12-25 204935.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/b5e7efa7-fbd2-76f4-9176-33f4245f3775.png)

**か、管理者に不正アクセスの通知を送信しました。** だってぇ！？

## そのころ美咲は...

![IMG_4600.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/12d68e61-bf2f-5cfb-2030-bc29882f47d8.png)

```
- 原文
Authentication of CHROME failed.
If this was not done by you, someone may have attempted to access your application illegally.

- 日本語訳
CHROME の認証に失敗しました。
これがあなたによって行われなかった場合、誰かがあなたのアプリケーションに不正にアクセスしようとした可能性があります。
```

ふーん...悠斗はChromeを開いたのね。
やっぱり男はみんな同じなのね。

# SMSで送られる認証コードを入れないとアプリケーションが開かないようにしよう

というわけで主題です。

なお、今回は2FAと称していますが2FじゃねぇしあんまAじゃねぇ、なんかよくわからん認証システムってだけです。

## VONAGE APIsからSMSを送信しよう
今回はVonageのアカウント作成などは割愛します。

Vonageのダッシュボードから「はじめに」>「SMS を送信」を選んでみましょう。

![スクリーンショット 2023-12-25 211611.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/df8ebdb4-65aa-f85d-f4c7-8bfb571021e4.png)

物は試しで`Send message`を押してみましょう。

そうするとこんな風に、ご自身のアカウントの電話番号にSMSが送られてくると思います。

![IMG_4601.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/1ca37bed-4bf2-0c45-89b1-e31b7ffea782.png)

## サンプルコードをもとに、自由にSMSを送るコードを書いてみよう

まず`vonage`のインストールが必要です。
`pip install vonage`でインストールできます。

```py
import vonage

client = vonage.Client(key="API_KEY", secret="API_SECRET")
sms = vonage.Sms(client)

def send_sms(from_name: str, send_to: str, message: str)
    return sms.send_message(
        {
            "from": from_name,
            "to": send_to,
            "text": message,
        }
    )

def main():
    res = send_sms("Vonage APIs", "YOUR_PHONE_NUMBER", "Hello, World!")

    if res["messages"][0]["status"] == "0":
        print("Message sent successfully.")
    else:
        print(f"Message failed with error: {res['messages'][0]['error-text']}")

if __name__ == "__main__":
    main()
```

サンプルコードをもとにこんなコードを書いてみました。
これを実行すると、`YOUR_PHONE_NUMBER`にSMSが送られてくると思います。

あとはこれをもとに、認証コード風のコードを発行するコードを書いてみましょう。

## 認証コード発行まで

```py
import random
import vonage

client = vonage.Client(key="API_KEY", secret="API_SECRET")
sms = vonage.Sms(client)

def send_sms(from_name: str, send_to: str, message: str)
    return sms.send_message(
        {
            "from": from_name,
            "to": send_to,
            "text": message,
        }
    )

def main():
    code = random.randint(0, 999999)

    res = send_sms("Vonage APIs", "YOUR_PHONE_NUMBER", f"Your code is {code}.")

    if res["messages"][0]["status"] == "0":
        check_code = input("Please enter the code: ")
        if int(check_code) == code:
            print("Success!")
        else:
            print("Failed!")
    else:
        print(f"Message failed with error: {res['messages'][0]['error-text']}")

if __name__ == "__main__":
    main()
```

このコードを実行すると、`0～999999`の間の数字がランダムに生成され、それがSMSで送られてきます。
その後、コンソールにコードを入力するように求められるので、SMSで送られてきたコードを入力してみましょう。
もし正しいコードを入力した場合は`Success!`と表示されると思います。

あとはこれにアプリケーションを起動するコードを追加すれば...

```py
import subprocess
import os
import sys
import vonage
import dotenv
import random
import enum

dotenv.load_dotenv()

client = vonage.Client(key=os.getenv("VONAGE_KEY"), secret=os.getenv("VONAGE_SECRET"))
sms = vonage.Sms(client)

USER_NUMBER = os.getenv("VONAGE_NUMBER")
MAX_COUNT = 3

class KickStatus(enum.Enum):
    PENDING = enum.auto()
    SUCCESS = enum.auto()
    FAILED = enum.auto()

class Applications(enum.Enum):
    CHROME = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"

def send_sms(from_name: str, send_to: str, message: str):
    return sms.send_message(
        {
            "from": from_name,
            "to": send_to,
            "text": message,
        }
    )

def main():
    if USER_NUMBER is None:
        print("あなたの電話番号がセットされていません。")
        return

    if len(sys.argv) < 2:
        print("アプリケーションIDが指定されていません。")
        return

    APPLICATION_ID = sys.argv[1]

    if APPLICATION_ID is None:
        print("アプリケーションIDがセットされていません。")
        return

    if APPLICATION_ID not in Applications.__members__:
        print("アプリケーションIDが不正です。")
        return

    application = Applications[APPLICATION_ID]

    masked_number = "*******" + USER_NUMBER[-2:]
    onetime_key = random.randint(0, 999999)
    res = send_sms("App 2FA", USER_NUMBER, f"The authentication code to activate {APPLICATION_ID} is {onetime_key} .\nDo not share this code with others.")

    if res["messages"][0]["status"] == "0":
        print("認証プロセスを開始します...")
    else:
        print(f"恐れ入りますが、このアプリケーションは現在使用できません。\nSMS認証が正常に行えませんでした。\n{res['messages'][0]['error-text']}")
        return

    kick = KickStatus.PENDING
    failed_count = 0

    while kick == KickStatus.PENDING:
        check_key = input(f"{masked_number}に送信した認証コードを入力してください: ")
        if int(check_key) == onetime_key:
            print("認証成功...")
            kick = KickStatus.SUCCESS
        else:
            print(f"認証コードが違います。残り{MAX_COUNT - failed_count - 1}/{MAX_COUNT}回。")
            failed_count += 1
            if failed_count >= MAX_COUNT:
                kick = KickStatus.FAILED
                print("認証失敗...")
                break

    if kick == KickStatus.FAILED:
        send_sms("App 2FA", USER_NUMBER, f"Authentication of {APPLICATION_ID} failed.\nIf this was not done by you, someone may have attempted to access your application illegally.")
        print("認証に失敗したため、アプリケーションを終了します。\n管理者に不正アクセスの通知を送信しました。")
        input("Press Enter to exit.")
        return
    else:
        print("アプリケーションを起動します。")
        subprocess.Popen(application.value, cwd="\\".join(application.value.split("\\")[:-1]))

if __name__ == "__main__":
    main()
```

これで、ちゃんとSMSに送信されたコードを入力しないとアプリケーションが起動しないようなものを作ることができました。

ちなみに、SMSの送信の送信名(`App 2FA`としているもの)は、文字数制限があります。多分11文字かな。
もし心配なら「Try the SMS API」の画面の`Try it out`で入力してみてください。`Send message`が押せるかどうかで文字数制限がわかると思います。

# まとめ
今回はVonage APIのとっかかりとして、SMSでアプリの起動を制限するようなものを作ってみました。
認証コードを入力するというのは、今回はコンソールからの入力でしたが、もちろんGUIで入力するようなものも作れます。
ちゃんと作れば、セキュリティー意識が高そうなシステムでも構築できそうですね～

Vonageは、オムニチャンネルを容易に実装可能なAPIをグローバルで提供している会社です。世界中で多くの方が使っているPSTNの電話やSMSをAPIで組み込む事が可能です。電話やSMSのみならず、Videoや各種メッセージチャネルや二要素認証を提供しています。
(引用: [【Vonage】コミュニケーションAPIを使ってみよう、Vonageのことなら何でも共有しよう！ by Vonage Advent Calendar 2023](https://qiita.com/advent-calendar/2023/vonage))

Vonageは無料でお試しもできるので、もし興味があればぜひ使ってみてください。
それでは、よい2FAライフを！

# おまけ

美咲「もう、悠斗ったら...」
悠斗「ごめんごめん、つい...」
美咲「まっ、次からはちゃんと気を付けてよね？」

美咲「（プレゼントの検索履歴見られなくてよかった...）」

*Happy End*