Apps Script用Sheet生成動態網頁(9): 會員登入頁面

前一篇中我們製作了一個會員註冊系統,使用者可以透過網頁註冊成會員,隨後用Apps Script發信請使用者確認,只要使用者有點擊確認信,就能用這篇將介紹的會員登入頁面。

話不多說,我們直接上code...

1. Apps Script的部份(後端/伺服端)

這是登入會用到的函數loginUser(),這裡跟上一篇一樣需要使用到JWT,執行的流程依序有3個步驟:
  1. 檢查輸入,表單的username和password必須吻合正規表示式
  2. 從sheet抓取使用者資料,跟使用者輸入的內容做確認
  3. 產生登入成功的token回傳給使用者,使用的token是JWT。
注意:實務上,表單的密碼應該要雜湊或加密一下才傳給後端。
function loginUser(form) {
const name = form.username;
if (typeof name !== 'string' || !/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z]+$/.test(name))
throw new Error("帳號格式錯誤:應是E-mail");
const password = form.password;
if (typeof password !== 'string' || !/^[A-Za-z][A-Za-z0-9]{7,16}$/.test(password))
throw new Error("密碼格式錯誤:首字需為英文,其他為大小寫英數字,長度8到16之間");
let sheet = getUserSheet();
let row_idx = findIndexInColumn(name, _COLUMN_IDX_OF_NAME, sheet);
if (row_idx < 0)
throw new Error("帳號或密碼錯誤");
let usr_data = sheet.getRange(1 + row_idx, 1, 1, sheet.getLastColumn()).getValues()[0];
if (!usr_data[_COLUMN_IDX_OF_CONFIRMED])
throw new Error("你已經註冊了,但是還沒點擊確認信,請查看你的信箱!");
if (password !== usr_data[_COLUMN_IDX_OF_PWD])
throw new Error("帳號或密碼錯誤");
const accessToken = createJwt({
privateKey: ScriptApp.getScriptId(), // 請改成你喜歡的
expiresInHours: 1,
data: {
iss: name,
user: { name, loginAt: new Date().getTime() },
host: ScriptApp.getService().getUrl(),
},
});
return { status: 201, message: '已成功登入', token: accessToken };
}

2. HTML部份(前端/使用者端)

登入頁面這裡使用前一篇的表單再稍做修改,會在表單傳送時onSubmit()呼叫後端的loginUser()。如果失敗,就顯示失敗訊息;若是登入成功,則做以下
  1. 表單內容清空
    form.reset();
  2. 將表單元素移除
    form.parentNode.removeChild(form);
    這個語法的意思就找表單的父節點,將表單父節點中移除。在網頁上也就刪掉。
  3. 產生使用者可點擊的連結以回到首頁
    由於AppsScript網頁的連結實際上跟網址上的位置是不一樣的,所以我們無法使用 <a href="/">首頁</a>或是<a href="/exec">首頁</a>的方式讓AppsScript回到首頁。
    所以我們透過getServerUrl()取回首頁的網址,再將登入token賦予它。

<!DOCTYPE html>
<html lang="zh-Hant-TW">

<head>
<base target="_top">
<meta charset="UTF-8">
</head>

<body>
<h1>登入</h1>
<form onsubmit="onSubmit(event)">
<label>帳號<input type="text" name="username" autocomplete="username" required /></label>
<label>密碼<input type="password" name="password" autocomplete="current-password" required /></label>
<input type="submit" value="登入" />
</form>
<div id="msg"></div>
<br />
<script>
function showText(txt) {
var target = document.getElementById('msg');
if (target) target.textContent = typeof txt !== "string" ? JSON.stringify(txt) : txt;
}
function onSubmit(event) {
event.preventDefault();
showText('登入中,請稍候...');
try {
google.script.run
.withSuccessHandler(onCompleted)
.withFailureHandler(onFailure)
.withUserObject(event.target)
.loginUser(event.target);
} catch (error) {
onFailure(error);
}
return false;
}
function onCompleted(response, form) {
form.reset();
form.parentNode.removeChild(form);
google.script.run
.withSuccessHandler(function (url, rsp) {
var target = document.getElementById('msg');
target.innerHTML =
`${rsp.message},<a href="${url}?token=${rsp.token}">點此</a>回首頁`;
})
.withUserObject(response)
.getServerUrl();
}
function onFailure(error) {
console.error(error);
showText('登入失敗,錯誤訊息:' + error.message);
}
</script>
</body>

</html>







留言