帮 Line Bot 加上身份验证(3)

昨天将产出的验证码写进了 Google Sheet,但我们还需要另一个功能:输入验证码找出所在Row,并且在同一列的其他 column 写入绑定的 userId 和 绑定时间。这样使用者在 Line Bot 输入验证码後,我们才能知道绑定这个验证码的使用者是谁。

在 Google Sheet 寻找特定栏位的资料

因为产出的验证码放在 verification_code 的第一行,所以我们可以取出这一行每个储存格的值,再查找目标验证码的index,从而可以得知该验证码所在列号。

新增一个 tagVerificationCode.gs 内容如下:

function tagVerificationCode() {
  var target = 'a55969eb';
  // 连接到 verification_code sheet
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  // 取得从 A1 开始到最後一列有资料的 AX 的所有值,X = getLastRow()
  var columnValues = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues();
  // 利用 findIndex 遍历 columnValues 查找 target 验证码,找到的话回传 index,否则回传 -1
  var searchResult = columnValues.findIndex((element)=>element[0]==target);
  Logger.log(searchResult);
}

将 target 的值改为想查找的验证码,然後执行看看结果:
search result 01

然後进一步将 tagVerificationCode.gs 修改如下:

function tagVerificationCode(target, userId) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var columnValues = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues();
  var searchResult = columnValues.findIndex((element)=>element[0]==target);
  if (searchResult !== -1) {
    searchResult++;
    var targetRange =  sheet.getRange(searchResult, 2, 1, 2);
    targetRange.setValues([[userId, new Date()]]);
  }
}

修改後记得储存,然後选取要执行的函式为 testTagVerificationCode
按下执行後查看结果:
search result 02

试算表结果:
search result 03

将查找的功能抽成共用 function

先前有提过,Google Sheet 虽然可以当成简易资料库使用,但其实是没有 Query & Insert 的概念的。而我们刚刚完成的 tagVerificationCode.gs 里用来查找某行资料有没有这个值,其实就有点类似 Query 的概念,所以很适合抽成共用 function 方便之後重复使用。

Read Mail 专案内新增 searchColumnValue.gs

function searchColumnValue(sheet, columnName, target) {
  Logger.log('start to searchColumnValue');
  // 先取得标题列的值
  var headerRowValue = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
  // 查看 columnName 在标题列的 index
  var columnIndex = headerRowValue[0].indexOf(columnName);
  if (columnIndex !== -1) {
    columnIndex++;
    // 抓出 columnName 那行所有的值
    var columnValues = sheet.getRange(2, columnIndex, sheet.getLastRow(), 1).getValues();
    // 回传查找结果
    var searchResult = columnValues.findIndex((element)=>element[0]==target);
    return searchResult;
  }
}

管理部署 or 重新部署

因为已经部署过 Read Mail了,有修改的话要管理部署 > 编辑 > 增加新版本
edit depoly

Reply Message 的资料库也要跟着切换版本,看看是否有正确读到最新版。有时候如果没有自动同步到最新开发人员版本,可以先切成别的版本再切回来就会正常。

将 verification_code sheet 加上标题列

加上标题列如下:
add header row

因为增加了标题列,所以 Reply Message 的 generateVerificationCode 跟 tagVerificationCode 也需要稍作修改,避免写入的范围有误差

修改 generateVerificationCode.gs 的 insertVerificationCode 如下:

function insertVerificationCode(uniqueVerificationCode) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var range = sheet.getRange(2, 1, uniqueVerificationCode.length, 1);
  range.setValues(uniqueVerificationCode);
}

修改 tagVerificationCode.gs 如下:

function tagVerificationCode(target, userId) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var searchResult = ReadMailAndInsertToGoogleSheet.searchColumnValue(sheet, 'code', target);
  if (searchResult !== -1) {
    var targetRange =  sheet.getRange((searchResult+2), 2, 1, 2);
    targetRange.setValues([[userId, new Date()]]);
  }
}

如此一来即可重复引用这个写好的查找功能~

修改 Reply Message 加上身份验证

接着让我们对 Reply Message 的流程稍作修改,加上简单的身份验证流程吧

流程构思

  1. Reply Message 收到 Message Event
  2. 查找 userId 是否已验证
    • 尚未验证:检验输入内容是否为我们产出的验证码,且尚未被绑定
      • 是:进行绑定作业,告知已完成身份认证
      • 否:回应请先进行身分认证绑定
    • 已验证:检验输入内容是否为获取验证码
      • 是:回应信件内容的验证码
      • 否:回应无效输入

新增 isUserIdVerified.gs

用来判断这个 userId 是否已经存在我们的绑定纪录里

function isUserIdVerified(userId) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var searchResult = ReadMailAndInsertToGoogleSheet.searchColumnValue(sheet, 'user_id', userId);
  return (searchResult !== -1);
}

修改 replyMessage.gs

将 replyMessage.gs 修改以符合我们的新流程:

const CHANNEL_ACCESS_TOKEN = 'YOUR_CHANNEL_ACCESS_TOKEN';

function doPost(e) {
  var requestContent = JSON.parse(e.postData.contents);
  var event = requestContent.events[0];
  // 必须是 Message Event
  if (event && (event.type === 'message')) {
    var replyToken = event.replyToken;
    var userId = event.source && event.source.userId;
    var userMessage = event.message.text;
    var replyMessage = [];

    if(isUserIdVerified(userId)) {
      replyMessage = userIsVerifiedFlow(userMessage, userId);
    } else {
      replyMessage = userIsNotVerifiedFlow(userMessage, userId);
    }
    
    doReplyMessage(replyMessage, replyToken);
  }
      
  return ContentService.createTextOutput('success');
}

function userIsVerifiedFlow(userMessage, userId){
  return (userMessage === '获取验证码') ? getValidationCodeMessage(userId) : getReplyMessage('无效的输入');
}

function userIsNotVerifiedFlow(userMessage, userId){
  var bindResult = tagVerificationCode(userMessage, userId);
  var replyMessage = bindResult ? '绑定成功!请点击选单或输入获取验证码。' : '请先进行身分认证绑定。'
  return getReplyMessage(replyMessage);
}

function getValidationCodeMessage(userId) {
  var validationCode = ReadMailAndInsertToGoogleSheet.app(userId);
  return [{
    'type': 'text',
    'text': validationCode
  }];
}

function getReplyMessage(message) {
  return [{
        'type': 'text',
        'text': message
  }];
}

function doReplyMessage(replyMessage, replyToken) {
  var payload = {
    replyToken: replyToken,
    messages: replyMessage
  };

  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
    },
    'method': 'post',
    'payload': JSON.stringify(payload)
  });
}

修改 tagVerificationCode.gs

tagVerificationCode 也要稍作修改,增加检查验证码是否已被绑定过(user_id 不为空),以符合我们的新流程:

function tagVerificationCode(target, userId) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var searchResult = ReadMailAndInsertToGoogleSheet.searchColumnValue(sheet, 'code', target);
  if (searchResult !== -1) {
    var targetRowIndex = searchResult+2;
    var userIdValue = sheet.getRange(targetRowIndex, 2, 1, 1).getValue();
    if (userIdValue.length === 0) {
      var targetRange =  sheet.getRange((searchResult+2), 2, 1, 2);
      targetRange.setValues([[userId, new Date()]]);
      return true;
    }
  }
  return false;
}

修改完後一样要管理部署 > 编辑 > 建立新版本 如下:
depoly result

接着把网页应用程序的网址复制,更新到验证码小帮手的 Messaging Api Webhook 网址
忘记怎麽设置的话请看 部署 Google App Script 专案(2) & Line Bot 简单回应讯息

设定好後就可以用验证码小帮手测试看看结果罗~
结果如下图:
Line bot result

Google Sheet 的结果如下:
Google Sheet Result 01
Google Sheet Result 02

以上~这样就完成了简单的身份验证流程。

当然还有很多可以完善的地方,例如虽然可以交由程序自动产生验证码跟绑定,但却要我们手动发送验证码给使用者,使用者也只能手动输入,实在是太不贴心~但这就是我们可以学习进步的地方!明天要做什麽还尚待决定~差不多也该进入 Liff 应用的篇幅了,那麽就待明天再揭晓主题吧~


<<:  【LeetCode】Binary Search

>>:  display : Inline、Block、Inline-Block

[Day1] - 开赛!

The lone wolf dies but the pack survives. ---- Ga...

新手学习JavaScript:day30 - Todolist(3)与完赛心得

嗨!大家好,昨天我们有提到,一开始两个区域有「目前没有新的任务」以及「尚未有任务完成」,这两个标题都...

.NET Core第22天_FormActionTagHelper的使用

FormActionTagHelper (窗体操作标记帮助程序) : 其非针对原生HTML任何TAG...

Day07:始祖巨人

在学习Java继承的部分时,就想到进击巨人的设定,九大巨人的能力只要被其他人吃掉,能力就会被传承过去...

「ASP.NET 具有潜在危险 request.form 的值」...有无危险实例可参

最近写一个ASP.NET WebForm的网页,允许使用者在TextBox输入各种文字,也包括Htm...