samples-js/functions/smsPhoneVerification.js (47 lines of code) (raw):
const { output } = require("@azure/functions");
const df = require("durable-functions");
const { DateTime } = require("luxon");
const sendSmsChallengeActivityName = "sendSmsChallenge";
df.app.orchestration("smsPhoneVerification", function* (context) {
const phoneNumber = context.df.getInput();
if (!phoneNumber) {
throw new Error("A phone number input is required.");
}
const challengeCode = yield context.df.callActivity(sendSmsChallengeActivityName, phoneNumber);
// The user has 90 seconds to respond with the code they received in the SMS message.
const expiration = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ seconds: 90 });
const timeoutTask = context.df.createTimer(expiration.toJSDate());
let authorized = false;
for (let i = 0; i <= 3; i++) {
const challengeResponseTask = context.df.waitForExternalEvent("SmsChallengeResponse");
const winner = yield context.df.Task.any([challengeResponseTask, timeoutTask]);
if (winner === timeoutTask) {
// Timeout expired
break;
}
// We got back a response! Compare it to the challenge code.
if (challengeResponseTask.result === challengeCode) {
authorized = true;
break;
}
}
if (!timeoutTask.isCompleted) {
// All pending timers must be complete or canceled before the function exits.
timeoutTask.cancel();
}
return authorized;
});
const twilioOutput = output.generic({
type: "twilioSms",
from: "%TwilioPhoneNumber%",
accountSidSetting: "TwilioAccountSid",
authTokenSetting: "TwilioAuthToken",
});
df.app.activity(sendSmsChallengeActivityName, {
extraOutputs: [twilioOutput],
handler: function (phoneNumber, context) {
// Get a random challenge code
const challengeCode = Math.floor(Math.random() * 10000);
context.log(`Sending verification code ${challengeCode} to ${phoneNumber}.`);
context.extraOutputs.set(twilioOutput, {
body: `Your verification code is ${challengeCode.toPrecision(4)}`,
to: phoneNumber,
});
return challengeCode;
},
});