293 lines
7.8 KiB
JavaScript
293 lines
7.8 KiB
JavaScript
(function (root, factory) {
|
|
// 账号安全模块同时支持浏览器页面和 Node 单元测试。
|
|
if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory(root);
|
|
return;
|
|
}
|
|
|
|
root.SecurityPages = factory(root);
|
|
if (root.document) {
|
|
root.document.addEventListener('DOMContentLoaded', function () {
|
|
root.SecurityPages.init();
|
|
});
|
|
}
|
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
|
'use strict';
|
|
|
|
var documentRef = root.document;
|
|
var CAPTCHA_SCENES = {
|
|
password: 'WEB_H5_CHANGE_PASSWORD',
|
|
phone: 'WEB_H5_CHANGE_PHONE'
|
|
};
|
|
|
|
function getCaptchaScene(formType) {
|
|
return CAPTCHA_SCENES[formType] || '';
|
|
}
|
|
|
|
function hashPassword(value) {
|
|
// 接口文档要求密码提交 32 位 MD5,测试环境可传入 hashFn 替代。
|
|
if (root.hex_md5) return root.hex_md5(value);
|
|
if (root.md5) return root.md5(value);
|
|
return value;
|
|
}
|
|
|
|
function trimOrUndefined(value) {
|
|
var text = String(value === undefined || value === null ? '' : value).trim();
|
|
|
|
return text || undefined;
|
|
}
|
|
|
|
function buildPasswordChangeBody(values, hashFn) {
|
|
var source = values || {};
|
|
var hash = hashFn || hashPassword;
|
|
|
|
return {
|
|
oldPassword: hash(String(source.oldPassword || '').trim()),
|
|
newPassword: hash(String(source.newPassword || '').trim()),
|
|
validToken: trimOrUndefined(source.validToken)
|
|
};
|
|
}
|
|
|
|
function buildPhoneChangeBody(values) {
|
|
var source = values || {};
|
|
|
|
return {
|
|
newPhone: String(source.newPhone || '').trim(),
|
|
smsCode: String(source.smsCode || '').trim(),
|
|
validToken: trimOrUndefined(source.validToken)
|
|
};
|
|
}
|
|
|
|
function buildDeactivateBody(values, hashFn) {
|
|
var source = values || {};
|
|
var hash = hashFn || hashPassword;
|
|
|
|
return {
|
|
password: hash(String(source.password || '').trim()),
|
|
reason: trimOrUndefined(source.reason)
|
|
};
|
|
}
|
|
|
|
function isDangerConfirmed(value) {
|
|
return String(value || '').trim() === '确认注销';
|
|
}
|
|
|
|
function getApi() {
|
|
return root.GenealogyApi && root.GenealogyApi.defaultClient;
|
|
}
|
|
|
|
async function ensureCaptcha(form, formType, subject) {
|
|
// 改密和换绑必须先取得与当前业务场景绑定的验证码票据。
|
|
var captcha = root.CaptchaPages;
|
|
var sceneCode = getCaptchaScene(formType);
|
|
|
|
if (!sceneCode) return true;
|
|
if (!captcha || !captcha.ensureToken) {
|
|
showMessage('验证码组件未加载,请刷新后重试');
|
|
return false;
|
|
}
|
|
|
|
return Boolean(await captcha.ensureToken(form, {
|
|
sceneCode: sceneCode,
|
|
subject: subject || ''
|
|
}));
|
|
}
|
|
|
|
function query(selector, rootNode) {
|
|
return (rootNode || documentRef).querySelector(selector);
|
|
}
|
|
|
|
function queryAll(selector, rootNode) {
|
|
return Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector));
|
|
}
|
|
|
|
function showMessage(message) {
|
|
if (root.layui && root.layui.layer) {
|
|
root.layui.layer.msg(message);
|
|
return;
|
|
}
|
|
|
|
root.alert(message);
|
|
}
|
|
|
|
function getFormValues(form) {
|
|
var values = {};
|
|
|
|
queryAll('[name]', form).forEach(function (field) {
|
|
values[field.name] = field.value;
|
|
});
|
|
|
|
return values;
|
|
}
|
|
|
|
function setStatus(form, message) {
|
|
var status = query('[data-security-status]', form);
|
|
|
|
if (status) status.textContent = message;
|
|
}
|
|
|
|
async function submitPassword(form) {
|
|
var api = getApi();
|
|
var values = getFormValues(form);
|
|
|
|
if (!api) return;
|
|
if (!values.oldPassword || !values.newPassword) {
|
|
showMessage('请填写原密码和新密码');
|
|
return;
|
|
}
|
|
if (values.newPassword !== values.confirmPassword) {
|
|
showMessage('两次新密码不一致');
|
|
return;
|
|
}
|
|
|
|
if (!await ensureCaptcha(form, 'password')) return;
|
|
values = getFormValues(form);
|
|
|
|
try {
|
|
await api.changePassword(buildPasswordChangeBody(values));
|
|
setStatus(form, '密码已修改');
|
|
showMessage('密码已修改');
|
|
form.reset();
|
|
} catch (error) {
|
|
showMessage(error.message || '密码修改失败');
|
|
}
|
|
}
|
|
|
|
async function submitPhone(form) {
|
|
var api = getApi();
|
|
var body = buildPhoneChangeBody(getFormValues(form));
|
|
|
|
if (!api) return;
|
|
if (!body.newPhone || !body.smsCode) {
|
|
showMessage('请填写新手机号和验证码');
|
|
return;
|
|
}
|
|
|
|
if (!await ensureCaptcha(form, 'phone', body.newPhone)) return;
|
|
body = buildPhoneChangeBody(getFormValues(form));
|
|
|
|
try {
|
|
await api.changePhone(body);
|
|
setStatus(form, '手机号已换绑');
|
|
showMessage('手机号已换绑');
|
|
form.reset();
|
|
} catch (error) {
|
|
showMessage(error.message || '手机号换绑失败');
|
|
}
|
|
}
|
|
|
|
async function sendPhoneCode(button) {
|
|
// 换绑手机号使用独立短信场景,避免复用登录或找回密码票据。
|
|
var api = getApi();
|
|
var form = query('[data-security-form="phone"]');
|
|
var values;
|
|
|
|
if (!api || !form) return;
|
|
values = getFormValues(form);
|
|
if (!/^1[3-9]\d{9}$/.test(String(values.newPhone || '').trim())) {
|
|
showMessage('请输入正确的新手机号');
|
|
return;
|
|
}
|
|
if (!await ensureCaptcha(form, 'phone', values.newPhone)) return;
|
|
values = getFormValues(form);
|
|
button.disabled = true;
|
|
|
|
try {
|
|
await api.sendSmsCode({
|
|
phone: values.newPhone,
|
|
sceneCode: getCaptchaScene('phone'),
|
|
validToken: values.validToken || ''
|
|
});
|
|
showMessage('验证码已发送');
|
|
} catch (error) {
|
|
showMessage(error.message || '验证码发送失败');
|
|
} finally {
|
|
button.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function submitDeactivate(form) {
|
|
var api = getApi();
|
|
var values = getFormValues(form);
|
|
|
|
if (!api) return;
|
|
if (!values.password) {
|
|
showMessage('请填写当前密码');
|
|
return;
|
|
}
|
|
if (!isDangerConfirmed(values.confirmText)) {
|
|
showMessage('请完整输入“确认注销”');
|
|
return;
|
|
}
|
|
if (root.confirm && !root.confirm('账号注销后将退出登录,确认继续?')) return;
|
|
|
|
try {
|
|
await api.deactivateAccount(buildDeactivateBody(values));
|
|
showMessage('账号已提交注销');
|
|
root.location.href = 'index.html';
|
|
} catch (error) {
|
|
showMessage(error.message || '账号注销失败');
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
var api = getApi();
|
|
|
|
if (root.confirm && !root.confirm('确认退出当前账号?')) return;
|
|
|
|
try {
|
|
if (api) await api.logout();
|
|
} catch (error) {
|
|
// 退出请求失败时客户端仍会清理本地令牌,页面必须离开受保护区域。
|
|
} finally {
|
|
root.location.href = 'index.html';
|
|
}
|
|
}
|
|
|
|
function bindActions() {
|
|
if (!documentRef) return;
|
|
|
|
documentRef.addEventListener('submit', function (event) {
|
|
var form = event.target.closest('[data-security-form]');
|
|
var formType;
|
|
|
|
if (!form) return;
|
|
event.preventDefault();
|
|
formType = form.getAttribute('data-security-form');
|
|
|
|
if (formType === 'password') submitPassword(form);
|
|
if (formType === 'phone') submitPhone(form);
|
|
if (formType === 'deactivate') submitDeactivate(form);
|
|
});
|
|
|
|
documentRef.addEventListener('click', function (event) {
|
|
var codeButton = event.target.closest('[data-security-send-code]');
|
|
var button = event.target.closest('[data-security-logout]');
|
|
|
|
if (codeButton) {
|
|
event.preventDefault();
|
|
sendPhoneCode(codeButton);
|
|
return;
|
|
}
|
|
if (!button) return;
|
|
event.preventDefault();
|
|
logout();
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
if (!documentRef || !query('[data-security-page]')) return;
|
|
|
|
bindActions();
|
|
}
|
|
|
|
return {
|
|
buildPasswordChangeBody: buildPasswordChangeBody,
|
|
buildPhoneChangeBody: buildPhoneChangeBody,
|
|
buildDeactivateBody: buildDeactivateBody,
|
|
getCaptchaScene: getCaptchaScene,
|
|
isDangerConfirmed: isDangerConfirmed,
|
|
init: init
|
|
};
|
|
});
|