LoginSignup
4
1

More than 3 years have passed since last update.

Fax Verification by Twilio Verify and Twilio Programmable Fax

Last updated at Posted at 2020-12-19

この投稿は、Twilio Advent Calendar 2020の20日目の記事になります。
昨日(19日目)の投稿は @oic0310 さんのTwilioアドベントカレンダー 12月19日 パスワードは電話ででした。

はじめに

昨今、不正ログインなどのセキュリティ対策として、二要素認証を必須とするサービスが増えてきました。一般的なものとしては、SMSや音声電話、Authyなどのアプリを利用することが多いと思います。ただ、これまで二要素認証で「Fax」を利用したケースをみたことがなかったので、今回、作ってみました。

2021年12月17日(太平洋時間)をもちまして、「Twilio Programmable Fax」の提供を終了するとのこと。追悼の意を込めて。
【重要】Twilio Programmable Fax サービス終了について
https://cloudapi.kddi-web.com/news/latest/twilio-programmable-fax-end-of-life-on-december-17-2021
もしかしたら、2020年12月20日現在、過去1年間でTwilio Faxを利用していない場合、新規にFaxを利用できないかも。

デモ動画

こちらをご覧くださいませ。
https://youtu.be/r4k66y_CF2A

利用シーン

特にないかな・・・(苦笑)
使いみちなさそうだけど、セキュリティはあまり考慮してないので、ご利用は自己責任で。

構成

スクリーンショット 2020-12-20 22.16.35.png
ざっくり説明すると、、、

  1. 二要素認証はメール認証にする。
  2. メール認証の宛先はGmailとし、Fax番号をアドレスのエイリアス(アカウントの末尾に「+」をつけると、そのアカウントでメール受信できる)
  3. Google Apps Scriptで受信メールを取得し、アドレスからFax番号を、本文から認証コードを取得。
  4. Google Apps ScriptからTwilio Programmable Fax APIをコールし、Faxを送付する

という感じです。

使ったサービスやツール

  • Twilio Programmable Fax
    • APIでFaxを送付することができるサービス
  • Twilio Verify
    • Twilioが提供している二要素認証の仕組みの一つ。認証コード送信とコード認証機能のAPIが提供される
  • SendGrid
    • みなさんご存知のSendGrid。メール配信サービス。Twilio Verifyでメール認証する場合に必要。
  • Twilio Functions
    • Twilioが提供するFaaS。Node.jsで書いたコードを実行します。
    • いわゆる、AWS Lamdbaのようなものです。
  • Twilio Assets
    • Twilioが提供するウェブホスティング可能なストレージサービスです。
    • いわゆるAmazon S3のようなものです。
  • Twilio CLI
    • Twilioをコマンドラインより操作できる機能
  • Twilio CLI Serverless Toolkit
    • Twilio CLIのプラグイン。TwilioのAssetsやFunctionsを使ったApplicationをローカル環境で開発でき、また、Twilio環境に簡単にデプロイできる機能です。
  • Gmail
    • みなさんご存知のGmail
  • Google Spreadsheet
    • みなさんご存知のSpreadsheet
  • Google Apps Script
    • Googleの各種サービスをプログラムで操作できる機能。Excelでいうところのマクロみたいなやつ。

作ってみる

ソースコード

Twilio Verifyの設定

基本的に、以下のURLに記載されたとおりに行えばOKです。
https://cloudapi.kddi-web.com/magazine/two-factor-authentication/how-to-implement-twilio-verify
とてもわかりやすくまとめてくださってます。KDDIウェブコミュニケーションズ様に感謝。
ちなみに、メール認証をする場合は、Twilioの傘下に入っているSendGridを利用する必要があります。
手順は以下のドキュメントを参照すればOKです。
https://www.twilio.com/docs/verify/email

途中、認証メール送付用のテンプレートを作成する必要がありますが、今回は、Google Apps Scriptが処理をするので以下のようにシンプルにしておきます。

{{twilio_code}}

Twilio Serverlessの作成

ここでやることは以下の2つです

  1. Fax番号を入力させ、それを含んだメールアドレスを生成、Twilio Verifyに渡す。
  2. 認証コードを入力させ、上記のメールアドレスと一緒に、Twilio Verifyに渡す。

そして、注意点としては、Twilio Verify APIをどこでコールするかということです。APIをコールするためには、AUTH_TOKENが必要となります。これは外部に漏らしてはいけない情報なので、Javascript側に記述してはダメです。
従って、Twilio Verify APIのコールは、Twilio Serverless環境のFunctionsでこの処理を記載することにします。

つまり、認証コード送信は、
Twilio Assetsで公開したHTMLを利用しFax番号を入力
→JavaScriptでTwilio Functionsを呼び出しFax番号を送付
→Twilio FunctionsでFax番号をメールアドレスに変換、Twilio Verify APIをコール

確認コード認証は、
Twilio Assetsで公開したHTMLを利用し認証コードを入力
→JavaScriptでTwilio Functionsを呼び出しFax番号と認証コードを送付
→Twilio FunctionsでFax番号をメールアドレスに変換、認証コードを使って、Twilio Verify APIをコール

ということです。以下、ソースコードです。
https://github.com/takeshifurusato/2fafax
以下、入力画面のイメージです。
スクリーンショット 2020-12-20 3.20.25.png
ちなみに .envは以下のような内容です。

ACCOUNT_SID=Twilio ACCOUNT SID
AUTH_TOKEN=Twilio AUTH TOKEN
VERIFY_SERVICE_SID=Twilio VerifyのID
MAIL_ACCOUNT=Gmailのアカウント
MAIL_DOMAIN=gmail.com

Google Apps ScriptでFax送信

Twilio Programmable Faxは、送信する内容を、Twilioからアクセスが可能な公開されている場所にPDF形式で設置する必要があります。
ここでやることは以下の3つです。

  1. Twilio Verifyから送付された認証コードを受信する
  2. 認証コードを埋め込んだPDFファイルを生成し、公開されている場所に設置する
  3. Twilio Programmable Fax APIをコールし、Fax送付

特に悩んだのは、 「認証コードを埋め込んだPDFファイルを生成し、公開されている場所に設置する」です。これは、Google Apps Scriptで、Gmailを取得、本文から認証コードを取得、その内容をGoogle Spreadsheetに埋め込み、そのGoogle SpreadsheetをPDF保存、そのPDFのダウンロードリンクをTwilio Programmable Faxに送付しました。
また、Google Apps ScriptでGoogle Spreadsheetの内容を変更した場合、そのGoogle Apps Scriptのプロセスが終了するまで、Google Spreadsheetの内容が反映されないという特徴があります。これは「認証コード埋め込み」と「PDF保存」とが1つのプロセスでできないということです。
そして、生成したPDFファイルはGoogleDriveに保存され、それをsetSharingでTwilioからアクセスできるように公開にしています。また、URLの一部を書き換えることで、Viewモード(GoogleDriveで表示する)から、直ダウンロードできるようにしています。そうしないとTwilioからアクセスしたときにPDFとして認識されません。

以下、スクリプトです。

const accountSid = Twilio_ACCOUNT_SIDを記入;
const authToken = Twilio_AUTH_TOKENを記入;
const from_number = '+8150******** Fax送付元のTwilio番号';

function start() {
    // PDF作成→送信は2巡目で。
    // GASの仕様でスクリプトが終わるまでは書き換えたスプレッドシートが保存されない(PDF化できない)
    makePdfFax()
    getMail()
}

function makePdfFax() {
    var sheetObj = SpreadsheetApp.openById('管理用一覧スプレッドシートのID');
    var sheet = sheetObj.getSheetByName('history');
    const lastRow = sheetObj.getLastRow();
    for (let i = 1; i <= lastRow; i++) {
        if (sheet.getRange(i, 2).getValue()) {
            continue;
        }

        var files = DriveApp.getRootFolder().getFilesByName(sheet.getRange(i, 1).getValue());
        var file = files.next();
        var pdf_sheetObj = SpreadsheetApp.openById(file.getId());
        var pdf = pdf_sheetObj.getAs('application/pdf');
        var pdfname = sheet.getRange(i, 1).getValue() + ".pdf";
        var shareUrl = DriveApp
            .createFile(pdf)
            .setName(pdfname)
            .setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW)
            .getUrl();

        shareUrl = shareUrl.replace("https://drive.google.com/file/d/", "https://drive.google.com/uc?id=");
        shareUrl = shareUrl.replace(/\/view.*/, "");

        var fax_number = sheet.getRange(i, 3).getValue()
        var fax_number_E164 = "+81" + (/^0([0-9]+)/g).exec(fax_number)[1];
        sendFax(fax_number_E164, shareUrl);

        sheet.getRange(i, 2).setValue(1)
    }
}

function getMail() {
    var sheetObj = SpreadsheetApp.openById('管理用一覧スプレッドシートのID');
    var sheet = sheetObj.getSheetByName('history');

    var thds = GmailApp.search("label:inbox is:unread");

    var row = sheet.getLastRow() + 1;
    var row_first = row;
    var oldIds = new Array();
    for (var i = 1; i <= row; i++) {
        oldIds.push(sheet.getRange(i, 1).getValue());
    }

    for (var n in thds) {
        var thd = thds[n];
        var msgs = thd.getMessages();
        for (m in msgs) {
            var msg = msgs[m];
            var date = msg.getDate();
            var body = msg.getBody();
            var id = msg.getId();
            var to = msg.getTo();
            var from = msg.getFrom();

            if (oldIds.indexOf(id) > -1) {
                continue;
            }

            var number = (/\+([0-9]+)/g).exec(to)
            var auth_code = (/^([0-9]+)/g).exec(body)

            var org = SpreadsheetApp.openById('Fax送信用のテンプレートスプレッドシートのID');
            var ss = org.copy(id);
            ss.getRange("A2").setValue(auth_code[1]);

            sheet.getRange(row, 1).setValue(id);
            sheet.getRange(row, 2).setValue(0);
            sheet.getRange(row, 3).setValue(number[1]);
            sheet.getRange(row, 4).setValue(auth_code[1]);
            row++;

        }
    }
}

function sendFax(to, media) {
    const url = "https://fax.twilio.com/v1/Faxes"
    const data = {
        From: from_number,
        To: to,
        MediaUrl: media
    };
    const options = {
        "method": "post",
        "payload": data,
        "headers": {"Authorization": " Basic " + Utilities.base64Encode(accountSid + ":" + authToken)},
        "muteHttpExceptions": true
    };

    try {
        var response = UrlFetchApp.fetch(url, options);
        var content = response.getContentText("UTF-8");
        Logger.log(content);
    } catch (e) {
        Logger.log(e.message);
    }
}

管理用一覧スプレッドシートは、空のシートでよいです。「history」という名前でシートを作っておきます。また、Fax送信用のテンプレートスプレッドシートは以下のようなものを作っておきます。
スクリーンショット 2020-12-20 3.40.08.png

なお、このスクリプトの起動は、トリガー設定が必要となります。
※最短の1分間隔で、start()を指定しておきます。設定のイメージは以下。
スクリーンショット 2020-12-20 3.48.58.png

まとめ

  • Twilio Verify 超簡単に実装できる(料金は1回の認証で$0.05)
  • Twilio Programmable Fax がサービス終了なのは、ちょっと残念。セツナイ。

余談

  • Fax実機はTwilio SIP Registrationと、Grandstream HT802を利用してTwilio番号で受信しています。
  • そして、Fax実機が壊れそうです。Twilio Programmable Fax のサービス終了が早いか、Fax実機が壊れるのが早いか・・・。

明日(21日目)は、@asaborake さんの問い合わせが来たらFAXで受信。。。というネタを書こうと思ったらTwilio Fax終了のお知らせです。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1