この投稿は、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
#利用シーン
特にないかな・・・(苦笑)
使いみちなさそうだけど、セキュリティはあまり考慮してないので、ご利用は自己責任で。
- 二要素認証はメール認証にする。
- メール認証の宛先はGmailとし、Fax番号をアドレスのエイリアス(アカウントの末尾に「+」をつけると、そのアカウントでメール受信できる)
- Google Apps Scriptで受信メールを取得し、アドレスからFax番号を、本文から認証コードを取得。
- 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 Serverless側:https://github.com/takeshifurusato/2fafax
- Google Apps Script側:以下、説明に記載
##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つです
- Fax番号を入力させ、それを含んだメールアドレスを生成、Twilio Verifyに渡す。
- 認証コードを入力させ、上記のメールアドレスと一緒に、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
以下、入力画面のイメージです。
ちなみに .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つです。
- Twilio Verifyから送付された認証コードを受信する
- 認証コードを埋め込んだPDFファイルを生成し、公開されている場所に設置する
- 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送信用のテンプレートスプレッドシートは以下のようなものを作っておきます。
なお、このスクリプトの起動は、トリガー設定が必要となります。
※最短の1分間隔で、start()を指定しておきます。設定のイメージは以下。
#まとめ
- 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終了のお知らせです。