修改底层请求逻辑,封装请求方法
This commit is contained in:
@@ -591,41 +591,6 @@ textarea {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.auth-captcha-box {
|
||||
/* 认证页滑动验证挂载点,TAC 内部 DOM 由第三方库生成 */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.auth-captcha-box:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.auth-captcha-box #tianai-captcha-parent {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.auth-captcha-modal {
|
||||
/* 认证页滑动验证弹窗挂载点,TAC 内部 DOM 由第三方库生成 */
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
background: rgba(38, 48, 44, .32);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.auth-captcha-modal:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.auth-captcha-modal #tianai-captcha-parent {
|
||||
max-width: min(318px, calc(100vw - 32px));
|
||||
max-height: calc(100vh - 32px);
|
||||
}
|
||||
|
||||
/* 验证码输入和按钮的通用双列布局,窄屏在页面 CSS 中改为单列 */
|
||||
.code-row {
|
||||
display: grid;
|
||||
|
||||
@@ -1,449 +0,0 @@
|
||||
(function (root, factory) {
|
||||
// 同一份接口客户端同时支持浏览器页面和 Node 测试环境。
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory(root);
|
||||
return;
|
||||
}
|
||||
|
||||
root.GenealogyApi = factory(root);
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||
'use strict';
|
||||
|
||||
var DEFAULT_BASE_URL = 'http://test-genealogy-api.ddxcjp.cn';
|
||||
var DEFAULT_CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f';
|
||||
var DEFAULT_TENANT_ID = '000000';
|
||||
var DEFAULT_TOKEN_KEY = 'genealogy_auth_token';
|
||||
var DEFAULT_TOKEN_HEADER_NAME = 'Authorization';
|
||||
|
||||
function loadNodeModule(path) {
|
||||
if (typeof require !== 'function') return null;
|
||||
|
||||
try {
|
||||
return require(path);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigApi() {
|
||||
var configFactory;
|
||||
|
||||
if (root.GenealogyConfig) return root.GenealogyConfig;
|
||||
|
||||
configFactory = loadNodeModule('../../config.js');
|
||||
return configFactory ? configFactory(root) : null;
|
||||
}
|
||||
|
||||
function getStorageUtil() {
|
||||
return root.StorageUtil || loadNodeModule('../../utils/StorageUtil.js');
|
||||
}
|
||||
|
||||
function getRequestUtil() {
|
||||
return root.RequestUtil || loadNodeModule('../../utils/RequestUtil.js');
|
||||
}
|
||||
|
||||
function getStorage(store) {
|
||||
if (store) return store;
|
||||
|
||||
try {
|
||||
return root.localStorage;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getTokenFromResponse(data) {
|
||||
// 登录响应可能使用不同 token 字段,这里统一收敛为本地登录票据。
|
||||
if (!data) return '';
|
||||
return data.token || data.accessToken || data.tokenValue || '';
|
||||
}
|
||||
|
||||
function ApiError(message, options) {
|
||||
this.name = 'ApiError';
|
||||
this.message = message || '接口请求失败';
|
||||
this.status = options && options.status;
|
||||
this.code = options && options.code;
|
||||
this.data = options && options.data;
|
||||
}
|
||||
|
||||
ApiError.prototype = Object.create(Error.prototype);
|
||||
ApiError.prototype.constructor = ApiError;
|
||||
|
||||
function createUnavailableMethod(name) {
|
||||
return function () {
|
||||
var error = new Error('PC 接口文档未提供该业务接口:' + name);
|
||||
error.code = 'PC_API_NOT_AVAILABLE';
|
||||
throw error;
|
||||
};
|
||||
}
|
||||
|
||||
function createFormData(fileOrFormData) {
|
||||
var FormDataCtor = root.FormData;
|
||||
var formData;
|
||||
|
||||
if (FormDataCtor && fileOrFormData instanceof FormDataCtor) return fileOrFormData;
|
||||
if (!FormDataCtor) throw new ApiError('当前环境不支持文件上传');
|
||||
|
||||
formData = new FormDataCtor();
|
||||
formData.append('file', fileOrFormData);
|
||||
return formData;
|
||||
}
|
||||
|
||||
function createChunkFormData(body) {
|
||||
var FormDataCtor = root.FormData;
|
||||
var source = body || {};
|
||||
var formData;
|
||||
|
||||
if (FormDataCtor && body instanceof FormDataCtor) return body;
|
||||
if (!FormDataCtor) throw new ApiError('当前环境不支持文件上传');
|
||||
|
||||
formData = new FormDataCtor();
|
||||
formData.append('uploadId', source.uploadId);
|
||||
formData.append('chunkIndex', source.chunkIndex);
|
||||
formData.append('chunkMd5', source.chunkMd5);
|
||||
formData.append('file', source.file);
|
||||
return formData;
|
||||
}
|
||||
|
||||
function createClient(options) {
|
||||
var settings = options || {};
|
||||
var configApi = getConfigApi();
|
||||
var config = configApi && configApi.getConfig ? configApi.getConfig() : {};
|
||||
var storageUtil = getStorageUtil();
|
||||
var requestUtil = getRequestUtil();
|
||||
var store = getStorage(settings.tokenStore || settings.storage);
|
||||
var clientId = settings.clientId || config.clientId || DEFAULT_CLIENT_ID;
|
||||
var tenantId = settings.tenantId || config.tenantId || DEFAULT_TENANT_ID;
|
||||
var tokenKey = settings.tokenKey || config.tokenKey || DEFAULT_TOKEN_KEY;
|
||||
var tokenHeaderName = settings.tokenHeaderName || config.tokenHeaderName || DEFAULT_TOKEN_HEADER_NAME;
|
||||
var baseUrl = settings.baseUrl || config.apiBaseUrl || DEFAULT_BASE_URL;
|
||||
var requester;
|
||||
|
||||
if (!requestUtil || !requestUtil.createRequester) {
|
||||
throw new ApiError('缺少 RequestUtil.createRequester 公共请求工具');
|
||||
}
|
||||
|
||||
function getToken() {
|
||||
return storageUtil && storageUtil.read ? storageUtil.read(store, tokenKey) || '' : '';
|
||||
}
|
||||
|
||||
function setToken(token) {
|
||||
if (token && storageUtil && storageUtil.write) storageUtil.write(store, tokenKey, token);
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
if (storageUtil && storageUtil.remove) storageUtil.remove(store, tokenKey);
|
||||
}
|
||||
|
||||
requester = requestUtil.createRequester({
|
||||
baseUrl: baseUrl,
|
||||
clientId: clientId,
|
||||
tokenHeaderName: tokenHeaderName,
|
||||
getToken: getToken,
|
||||
fetchImpl: settings.fetchImpl || root.fetch
|
||||
});
|
||||
|
||||
function request(method, path, requestOptions) {
|
||||
return requester(method, path, requestOptions || {});
|
||||
}
|
||||
|
||||
function withTenant(body) {
|
||||
return Object.assign({
|
||||
tenantId: tenantId,
|
||||
clientId: clientId
|
||||
}, body || {});
|
||||
}
|
||||
|
||||
async function login(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/login', {
|
||||
auth: false,
|
||||
body: Object.assign({
|
||||
grantType: 'password'
|
||||
}, withTenant(body))
|
||||
});
|
||||
|
||||
setToken(getTokenFromResponse(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
async function register(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/register', {
|
||||
auth: false,
|
||||
body: Object.assign({
|
||||
grantType: 'password',
|
||||
registerSource: 'PC'
|
||||
}, withTenant(body))
|
||||
});
|
||||
|
||||
setToken(getTokenFromResponse(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
async function loginBySms(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/login/sms', {
|
||||
auth: false,
|
||||
body: Object.assign({
|
||||
grantType: 'sms'
|
||||
}, withTenant(body))
|
||||
});
|
||||
|
||||
setToken(getTokenFromResponse(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
function sendSmsCode(body) {
|
||||
return request('POST', '/genealogy/pc/auth/sms/code', {
|
||||
auth: false,
|
||||
body: Object.assign({
|
||||
grantType: 'sms'
|
||||
}, withTenant(body))
|
||||
});
|
||||
}
|
||||
|
||||
function captchaRequire(body) {
|
||||
var source = body || {};
|
||||
|
||||
return request('GET', '/captcha/require', {
|
||||
auth: false,
|
||||
query: {
|
||||
tenantId: source.tenantId || tenantId,
|
||||
clientId: source.clientId || clientId,
|
||||
sceneCode: source.sceneCode,
|
||||
subject: source.subject
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildApiUrl(path, query) {
|
||||
if (requestUtil.joinUrl && requestUtil.appendQuery) {
|
||||
return requestUtil.appendQuery(requestUtil.joinUrl(baseUrl, path), query);
|
||||
}
|
||||
|
||||
return baseUrl + path;
|
||||
}
|
||||
|
||||
var client = {
|
||||
clientId: clientId,
|
||||
tenantId: tenantId,
|
||||
tokenKey: tokenKey,
|
||||
getToken: getToken,
|
||||
setToken: setToken,
|
||||
clearToken: clearToken,
|
||||
request: request,
|
||||
buildApiUrl: buildApiUrl,
|
||||
get: function (path, query) {
|
||||
return request('GET', path, { query: query });
|
||||
},
|
||||
post: function (path, body, reqOptions) {
|
||||
return request('POST', path, Object.assign({}, reqOptions || {}, { body: body }));
|
||||
},
|
||||
put: function (path, body, reqOptions) {
|
||||
return request('PUT', path, Object.assign({}, reqOptions || {}, { body: body }));
|
||||
},
|
||||
delete: function (path, reqOptions) {
|
||||
return request('DELETE', path, reqOptions || {});
|
||||
},
|
||||
login: login,
|
||||
register: register,
|
||||
sendSmsCode: sendSmsCode,
|
||||
loginBySms: loginBySms,
|
||||
smsLogin: loginBySms,
|
||||
getProfile: function () {
|
||||
return request('GET', '/genealogy/pc/auth/profile');
|
||||
},
|
||||
currentProfile: function () {
|
||||
return request('GET', '/genealogy/pc/auth/profile');
|
||||
},
|
||||
updateProfile: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/profile', { body: body });
|
||||
},
|
||||
changePassword: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/password', { body: body });
|
||||
},
|
||||
resetPassword: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/password/reset', {
|
||||
auth: false,
|
||||
body: Object.assign({
|
||||
tenantId: tenantId
|
||||
}, body || {})
|
||||
});
|
||||
},
|
||||
changePhone: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/phone', { body: body });
|
||||
},
|
||||
deactivateAccount: async function (body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/account/deactivate', { body: body });
|
||||
|
||||
clearToken();
|
||||
return data;
|
||||
},
|
||||
logout: async function () {
|
||||
var data = await request('DELETE', '/genealogy/pc/auth/logout');
|
||||
|
||||
clearToken();
|
||||
return data;
|
||||
},
|
||||
uploadFile: function (fileOrFormData) {
|
||||
return request('POST', '/genealogy/pc/files/upload', {
|
||||
body: createFormData(fileOrFormData)
|
||||
});
|
||||
},
|
||||
initResumableUpload: function (body) {
|
||||
return request('POST', '/genealogy/pc/files/resumable/init', { body: body });
|
||||
},
|
||||
uploadChunk: function (body) {
|
||||
return request('POST', '/genealogy/pc/files/resumable/chunk', {
|
||||
body: createChunkFormData(body)
|
||||
});
|
||||
},
|
||||
uploadResumableChunk: function (body) {
|
||||
return this.uploadChunk(body);
|
||||
},
|
||||
completeResumableUpload: function (body) {
|
||||
return request('POST', '/genealogy/pc/files/resumable/complete', { body: body });
|
||||
},
|
||||
bindFileReference: function (body) {
|
||||
return request('POST', '/genealogy/pc/files/reference', { body: body });
|
||||
},
|
||||
releaseFileReference: function (query) {
|
||||
return request('DELETE', '/genealogy/pc/files/reference', { query: query });
|
||||
},
|
||||
getRegionChildren: function (parentCode) {
|
||||
return request('GET', '/genealogy/region/children', {
|
||||
query: {
|
||||
parentCode: parentCode
|
||||
}
|
||||
});
|
||||
},
|
||||
regionChildren: function (parentCode) {
|
||||
return this.getRegionChildren(parentCode);
|
||||
},
|
||||
getRegionPath: function (regionCode) {
|
||||
return request('GET', '/genealogy/region/path/' + encodeURIComponent(regionCode));
|
||||
},
|
||||
regionPath: function (regionCode) {
|
||||
return this.getRegionPath(regionCode);
|
||||
},
|
||||
searchRegions: function (query) {
|
||||
return request('GET', '/genealogy/region/search', { query: query });
|
||||
},
|
||||
regionSearch: function (query) {
|
||||
return this.searchRegions(query);
|
||||
},
|
||||
getRegion: function (regionCode) {
|
||||
return request('GET', '/genealogy/region/' + encodeURIComponent(regionCode));
|
||||
},
|
||||
regionDetail: function (regionCode) {
|
||||
return this.getRegion(regionCode);
|
||||
},
|
||||
captchaRequire: captchaRequire,
|
||||
captchaRequirement: captchaRequire,
|
||||
captchaChallenge: function (body) {
|
||||
return request('POST', '/captcha/challenge', { auth: false, body: body });
|
||||
},
|
||||
captchaVerify: function (body) {
|
||||
return request('POST', '/captcha/verify', { auth: false, body: body });
|
||||
},
|
||||
legacyCaptcha: function () {
|
||||
return request('GET', '/auth/code', { auth: false });
|
||||
}
|
||||
};
|
||||
|
||||
// PC YAML 未提供这些业务 paths,保留方法名只用于阻止旧移动端路径继续发请求。
|
||||
[
|
||||
'listGenealogies',
|
||||
'createGenealogy',
|
||||
'publicGenealogies',
|
||||
'myGenealogies',
|
||||
'genealogyDetail',
|
||||
'genealogyOverview',
|
||||
'applyJoinGenealogy',
|
||||
'myJoinApplies',
|
||||
'pendingJoinApplies',
|
||||
'auditJoinApply',
|
||||
'cancelJoinApply',
|
||||
'genealogyMembers',
|
||||
'genealogyMemberOptions',
|
||||
'updateGenealogyMember',
|
||||
'removeGenealogyMember',
|
||||
'leaveGenealogy',
|
||||
'transferGenealogyOwner',
|
||||
'articleCategories',
|
||||
'articles',
|
||||
'articleDetail',
|
||||
'createArticle',
|
||||
'updateArticle',
|
||||
'feeds',
|
||||
'feedsPage',
|
||||
'feedDetail',
|
||||
'createFeed',
|
||||
'updateFeed',
|
||||
'likeFeed',
|
||||
'unlikeFeed',
|
||||
'feedComments',
|
||||
'feedCommentsPage',
|
||||
'createFeedComment',
|
||||
'deleteFeedComment',
|
||||
'albums',
|
||||
'createAlbum',
|
||||
'updateAlbum',
|
||||
'albumPhotos',
|
||||
'createAlbumPhoto',
|
||||
'ceremonies',
|
||||
'ceremonyDetail',
|
||||
'createCeremony',
|
||||
'updateCeremony',
|
||||
'ceremonyGifts',
|
||||
'createCeremonyGift',
|
||||
'meritRecords',
|
||||
'createMeritRecord',
|
||||
'growthRecords',
|
||||
'growthRecordDetail',
|
||||
'createGrowthRecord',
|
||||
'updateGrowthRecord',
|
||||
'memos',
|
||||
'memoDetail',
|
||||
'createMemo',
|
||||
'updateMemo',
|
||||
'vipPackages',
|
||||
'vipOrders',
|
||||
'createVipOrder',
|
||||
'generationPoems',
|
||||
'createGenerationPoem',
|
||||
'updateGenerationPoem',
|
||||
'previewGenerationPoems',
|
||||
'saveGenerationPoemsBatch',
|
||||
'lineageTree',
|
||||
'lineagePersons',
|
||||
'lineagePersonsPage',
|
||||
'lineagePersonOptions',
|
||||
'lineagePersonDetail',
|
||||
'createLineagePerson',
|
||||
'updateLineagePerson',
|
||||
'deleteLineagePerson',
|
||||
'addLineageRelation',
|
||||
'notifications',
|
||||
'markNotificationRead',
|
||||
'markAllNotificationsRead',
|
||||
'helpArticles',
|
||||
'helpArticleDetail',
|
||||
'promotions',
|
||||
'feedbackList',
|
||||
'submitFeedback'
|
||||
].forEach(function (name) {
|
||||
client[name] = createUnavailableMethod(name);
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
return {
|
||||
DEFAULT_BASE_URL: DEFAULT_BASE_URL,
|
||||
DEFAULT_CLIENT_ID: DEFAULT_CLIENT_ID,
|
||||
DEFAULT_TENANT_ID: DEFAULT_TENANT_ID,
|
||||
TOKEN_KEY: DEFAULT_TOKEN_KEY,
|
||||
ApiError: ApiError,
|
||||
createClient: createClient,
|
||||
defaultClient: createClient()
|
||||
};
|
||||
});
|
||||
+62
-14
@@ -10,6 +10,8 @@
|
||||
'use strict';
|
||||
|
||||
var DEFAULT_SCENE_CODE = 'WEB_H5_LOGIN';
|
||||
var CAPTCHA_LAYER_AREA = ['318px', '318px'];
|
||||
var captchaLayerSeed = 0;
|
||||
|
||||
function getApi() {
|
||||
return root.GenealogyApi && root.GenealogyApi.defaultClient;
|
||||
@@ -111,11 +113,44 @@
|
||||
return query('input[name="validToken"]', form);
|
||||
}
|
||||
|
||||
function getCaptchaBox(form) {
|
||||
// 优先使用页面级弹窗挂载点,避免 TAC 验证码挤占表单布局。
|
||||
return query('.auth-captcha-modal[data-captcha-box]')
|
||||
|| query('[data-captcha-box]', form)
|
||||
|| query('[data-captcha-box]');
|
||||
function createCaptchaLayer() {
|
||||
// Layui 只负责提供默认弹层容器,验证码外观完全由 TAC 自带文件决定。
|
||||
if (!root.layui || !root.layui.layer || !root.layui.layer.open || !root.document) {
|
||||
throw new Error('缺少 layui.layer 验证码弹层依赖');
|
||||
}
|
||||
|
||||
var layerId = 'captcha-layer-' + (++captchaLayerSeed);
|
||||
var selector = '[data-captcha-layer-box="' + layerId + '"]';
|
||||
var layerIndex = root.layui.layer.open({
|
||||
type: 1,
|
||||
title: false,
|
||||
closeBtn: 0,
|
||||
// 尺寸来自 TAC 原生 #tianai-captcha-parent,提前交给 Layui 计算居中位置。
|
||||
area: CAPTCHA_LAYER_AREA,
|
||||
offset: 'auto',
|
||||
content: '<div data-captcha-layer-box="' + layerId + '"></div>'
|
||||
});
|
||||
var box = query(selector);
|
||||
|
||||
if (!box) {
|
||||
root.layui.layer.close(layerIndex);
|
||||
throw new Error('缺少 Layui 验证码实际挂载点');
|
||||
}
|
||||
|
||||
return {
|
||||
box: box,
|
||||
layerIndex: layerIndex
|
||||
};
|
||||
}
|
||||
|
||||
function closeCaptchaLayer(captchaLayer) {
|
||||
// 组件结束后清空第三方 DOM 并关闭本次创建的 Layui 层。
|
||||
if (!captchaLayer) return;
|
||||
|
||||
if (captchaLayer.box) captchaLayer.box.innerHTML = '';
|
||||
if (root.layui && root.layui.layer && root.layui.layer.close) {
|
||||
root.layui.layer.close(captchaLayer.layerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function writeValidToken(form, token) {
|
||||
@@ -132,7 +167,8 @@
|
||||
|
||||
function createTac(form, options, api, resolve, reject) {
|
||||
var settings = options || {};
|
||||
var box = getCaptchaBox(form);
|
||||
var captchaLayer;
|
||||
var box;
|
||||
var context = buildChallengeBody(settings, api);
|
||||
var lastChallenge = {};
|
||||
var captcha;
|
||||
@@ -143,8 +179,11 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!box) {
|
||||
reject(new Error('缺少滑动验证容器'));
|
||||
try {
|
||||
captchaLayer = createCaptchaLayer();
|
||||
box = captchaLayer.box;
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -159,12 +198,15 @@
|
||||
var token = extractValidToken(response);
|
||||
|
||||
if (!token) {
|
||||
if (captchaInstance && captchaInstance.destroyWindow) captchaInstance.destroyWindow();
|
||||
closeCaptchaLayer(captchaLayer);
|
||||
reject(new Error('滑动验证未返回有效票据'));
|
||||
return;
|
||||
}
|
||||
|
||||
writeValidToken(form, token);
|
||||
if (captchaInstance && captchaInstance.destroyWindow) captchaInstance.destroyWindow();
|
||||
closeCaptchaLayer(captchaLayer);
|
||||
resolve(token);
|
||||
},
|
||||
validFail: function (response, captchaControl, captchaInstance) {
|
||||
@@ -173,6 +215,7 @@
|
||||
},
|
||||
btnCloseFun: function (event, captchaInstance) {
|
||||
if (captchaInstance) captchaInstance.destroyWindow();
|
||||
closeCaptchaLayer(captchaLayer);
|
||||
reject(new Error('已取消滑动验证'));
|
||||
}
|
||||
});
|
||||
@@ -213,11 +256,15 @@
|
||||
}
|
||||
});
|
||||
|
||||
captcha = new root.TAC(config, {
|
||||
bgUrl: 'public/tac/images/dun.jpeg',
|
||||
logoUrl: null
|
||||
});
|
||||
captcha.init();
|
||||
try {
|
||||
captcha = new root.TAC(config);
|
||||
captcha.init();
|
||||
} catch (error) {
|
||||
closeCaptchaLayer(captchaLayer);
|
||||
reject(error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return captcha;
|
||||
}
|
||||
|
||||
@@ -252,7 +299,8 @@
|
||||
buildChallengeBody: buildChallengeBody,
|
||||
adaptChallengeResponse: adaptChallengeResponse,
|
||||
buildVerifyBody: buildVerifyBody,
|
||||
getCaptchaBox: getCaptchaBox,
|
||||
createCaptchaLayer: createCaptchaLayer,
|
||||
closeCaptchaLayer: closeCaptchaLayer,
|
||||
extractValidToken: extractValidToken,
|
||||
normalizeVerifyResponse: normalizeVerifyResponse,
|
||||
shouldRequireCaptcha: shouldRequireCaptcha,
|
||||
|
||||
Reference in New Issue
Block a user