Apps Script用Sheet生成動態網頁(9): 會員登入頁面
在前一篇中我們製作了一個會員註冊系統,使用者可以透過網頁註冊成會員,隨後用Apps Script發信請使用者確認,只要使用者有點擊確認信,就能用這篇將介紹的會員登入頁面。
話不多說,我們直接上code...
1. Apps Script的部份(後端/伺服端)
這是登入會用到的函數loginUser(),這裡跟上一篇一樣需要使用到JWT,執行的流程依序有3個步驟:
- 檢查輸入,表單的username和password必須吻合正規表示式
- 從sheet抓取使用者資料,跟使用者輸入的內容做確認
- 產生登入成功的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()。如果失敗,就顯示失敗訊息;若是登入成功,則做以下
- 表單內容清空
form.reset(); - 將表單元素移除
form.parentNode.removeChild(form);
這個語法的意思就找表單的父節點,將表單父節點中移除。在網頁上也就刪掉。 - 產生使用者可點擊的連結以回到首頁
由於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>
留言