2747 lines
92 KiB
JavaScript
2747 lines
92 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.setUpTokenAndUser = setUpTokenAndUser;
|
||
exports.loadTokenInfo = loadTokenInfo;
|
||
exports.loginByMobile = loginByMobile;
|
||
exports.verifyPassword = verifyPassword;
|
||
exports.loginByAccount = loginByAccount;
|
||
exports.loginByEmail = loginByEmail;
|
||
exports.bindByMobile = bindByMobile;
|
||
exports.bindByEmail = bindByEmail;
|
||
exports.setUserAvatarFromWechat = setUserAvatarFromWechat;
|
||
exports.refreshWechatPublicUserInfo = refreshWechatPublicUserInfo;
|
||
exports.loginByWechat = loginByWechat;
|
||
exports.loginWechatNative = loginWechatNative;
|
||
exports.loginWechat = loginWechat;
|
||
exports.loginWechatMp = loginWechatMp;
|
||
exports.syncUserInfoWechatMp = syncUserInfoWechatMp;
|
||
exports.sendCaptchaByMobile = sendCaptchaByMobile;
|
||
exports.sendCaptchaByEmail = sendCaptchaByEmail;
|
||
exports.switchTo = switchTo;
|
||
exports.getWechatMpUserPhoneNumber = getWechatMpUserPhoneNumber;
|
||
exports.logout = logout;
|
||
exports.wakeupParasite = wakeupParasite;
|
||
exports.refreshToken = refreshToken;
|
||
exports.loginWebByMpToken = loginWebByMpToken;
|
||
const tslib_1 = require("tslib");
|
||
const uuid_1 = require("oak-domain/lib/utils/uuid");
|
||
const WechatSDK_1 = tslib_1.__importDefault(require("oak-external-sdk/lib/WechatSDK"));
|
||
const assert_1 = require("oak-domain/lib/utils/assert");
|
||
const types_1 = require("oak-domain/lib/types");
|
||
const index_backend_1 = require("../utils/cos/index.backend");
|
||
const Exception_1 = require("../types/Exception");
|
||
const password_1 = require("../utils/password");
|
||
const Projection_1 = require("../types/Projection");
|
||
const sms_1 = require("../utils/sms");
|
||
const user_1 = require("./user");
|
||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||
const email_1 = require("../utils/email");
|
||
const validator_1 = require("oak-domain/lib/utils/validator");
|
||
const passport_1 = require("../utils/passport");
|
||
async function makeDistinguishException(userId, context, message) {
|
||
const [user] = await context.select('user', {
|
||
data: {
|
||
id: 1,
|
||
password: 1,
|
||
passwordSha1: 1,
|
||
idState: 1,
|
||
wechatUser$user: {
|
||
$entity: 'wechatUser',
|
||
data: {
|
||
id: 1,
|
||
},
|
||
},
|
||
email$user: {
|
||
$entity: 'email',
|
||
data: {
|
||
id: 1,
|
||
email: 1,
|
||
},
|
||
},
|
||
mobile$user: {
|
||
$entity: 'mobile',
|
||
data: {
|
||
id: 1,
|
||
mobile: 1,
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
id: userId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
(0, assert_1.assert)(user);
|
||
const { password, passwordSha1, idState, wechatUser$user, email$user, mobile$user } = user;
|
||
return new Exception_1.OakDistinguishUserException(userId, !!(password || passwordSha1), idState === 'verified', !!wechatUser$user?.length, !!email$user?.length, !!mobile$user?.length, message);
|
||
}
|
||
async function tryMakeChangeLoginWay(userId, context) {
|
||
const [user] = await context.select('user', {
|
||
data: {
|
||
id: 1,
|
||
idState: 1,
|
||
wechatUser$user: {
|
||
$entity: 'wechatUser',
|
||
data: {
|
||
id: 1,
|
||
},
|
||
},
|
||
email$user: {
|
||
$entity: 'email',
|
||
data: {
|
||
id: 1,
|
||
email: 1,
|
||
},
|
||
},
|
||
mobile$user: {
|
||
$entity: 'mobile',
|
||
data: {
|
||
id: 1,
|
||
mobile: 1,
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
id: userId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
(0, assert_1.assert)(user);
|
||
const { idState, wechatUser$user, email$user, mobile$user } = user;
|
||
if (idState === 'verified' ||
|
||
(wechatUser$user && wechatUser$user.length > 0) ||
|
||
(email$user && email$user.length > 0)) {
|
||
return new Exception_1.OakChangeLoginWayException(userId, idState === 'verified', !!(wechatUser$user && wechatUser$user.length > 0), !!(email$user && email$user.length > 0), !!(mobile$user && mobile$user.length > 0));
|
||
}
|
||
}
|
||
async function dealWithUserState(user, context, tokenData) {
|
||
switch (user.userState) {
|
||
case 'disabled': {
|
||
throw new Exception_1.OakUserDisabledException();
|
||
}
|
||
case 'shadow': {
|
||
return {
|
||
userId: user.id,
|
||
user: {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'activate',
|
||
data: {},
|
||
},
|
||
};
|
||
}
|
||
case 'merged': {
|
||
(0, assert_1.assert)(user?.refId);
|
||
const [user2] = await context.select('user', {
|
||
data: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
wechatUser$user: {
|
||
$entity: 'wechatUser',
|
||
data: {
|
||
id: 1,
|
||
},
|
||
},
|
||
userSystem$user: {
|
||
$entity: 'userSystem',
|
||
data: {
|
||
id: 1,
|
||
systemId: 1,
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
id: user.refId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
return await dealWithUserState(user2, context, tokenData);
|
||
}
|
||
default: {
|
||
(0, assert_1.assert)(user.userState === 'normal');
|
||
return {
|
||
userId: user.id,
|
||
};
|
||
}
|
||
}
|
||
}
|
||
function autoMergeUser(context) {
|
||
const { system } = context.getApplication();
|
||
return !!system.config.App.mergeUserDirectly;
|
||
}
|
||
/**
|
||
* 根据user的不同情况,完成登录动作
|
||
* @param env
|
||
* @param context
|
||
* @param user
|
||
* @return tokenValue
|
||
*/
|
||
async function setUpTokenAndUser(env, context, entity, // 支持更多的登录渠道使用此函数创建token
|
||
entityId, // 如果是现有对象传id,如果没有对象传createData
|
||
createData, user) {
|
||
const currentToken = context.getToken(true);
|
||
const schema = context.getSchema();
|
||
(0, assert_1.assert)(schema.hasOwnProperty(entity), `${entity}必须是有效的对象名 `);
|
||
(0, assert_1.assert)(schema.token.attributes.entity.ref.includes(entity), `${entity}必须是token的有效关联对象`);
|
||
(0, assert_1.assert)(schema[entity].attributes.hasOwnProperty('userId') &&
|
||
schema[entity].attributes.userId.ref === 'user', `${entity}必须有指向user的userId属性`);
|
||
if (currentToken) {
|
||
(0, assert_1.assert)(currentToken.id);
|
||
(0, assert_1.assert)(currentToken.userId);
|
||
if (user) {
|
||
// 有用户,和当前用户进行合并
|
||
const { userState } = user;
|
||
switch (userState) {
|
||
case 'normal': {
|
||
if (currentToken.userId === user.id) {
|
||
return currentToken.value;
|
||
}
|
||
const autoMerge = autoMergeUser(context);
|
||
if (autoMerge) {
|
||
await (0, user_1.mergeUser)({ from: user.id, to: currentToken.userId, mergeMobile: true, mergeWechatUser: true, mergeEmail: true }, context, true);
|
||
return currentToken.value;
|
||
}
|
||
throw await makeDistinguishException(user.id, context);
|
||
}
|
||
case 'shadow': {
|
||
(0, assert_1.assert)(currentToken.userId !== user.id);
|
||
const autoMerge = autoMergeUser(context);
|
||
if (autoMerge) {
|
||
await (0, user_1.mergeUser)({ from: user.id, to: currentToken.userId, mergeMobile: true, mergeWechatUser: true, mergeEmail: true }, context, true);
|
||
return currentToken.value;
|
||
}
|
||
throw await makeDistinguishException(user.id, context);
|
||
}
|
||
case 'disabled': {
|
||
throw new Exception_1.OakUserDisabledException();
|
||
}
|
||
case 'merged': {
|
||
(0, assert_1.assert)(user.refId);
|
||
if (user.refId === currentToken.userId) {
|
||
return currentToken.value;
|
||
}
|
||
const autoMerge = autoMergeUser(context);
|
||
if (autoMerge) {
|
||
// 说明一个用户被其他用户merge了,现在还是暂时先merge,后面再说
|
||
console.warn(`用户${user.id}已经是merged状态「${user.refId}」,再次被merged到「${currentToken.userId}]」`);
|
||
await (0, user_1.mergeUser)({ from: user.id, to: currentToken.userId, mergeMobile: true, mergeWechatUser: true, mergeEmail: true }, context, true);
|
||
return currentToken.value;
|
||
}
|
||
throw await makeDistinguishException(user.id, context);
|
||
}
|
||
default: {
|
||
(0, assert_1.assert)(false, `不能理解的user状态「${userState}」`);
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
// 没用户,指向当前用户
|
||
(0, assert_1.assert)(createData && !entityId);
|
||
if (createData) {
|
||
await context.operate(entity, {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: Object.assign(createData, {
|
||
userId: currentToken.userId,
|
||
}),
|
||
}, { dontCollect: entity !== 'mobile' });
|
||
}
|
||
else {
|
||
(0, assert_1.assert)(entityId);
|
||
await context.operate(entity, {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
userId: currentToken.userId,
|
||
},
|
||
filter: {
|
||
id: entityId,
|
||
},
|
||
}, { dontCollect: entity !== 'mobile' });
|
||
}
|
||
return currentToken.value;
|
||
}
|
||
}
|
||
else {
|
||
/* if (entityId) {
|
||
// 已经有相应对象,判定一下能否重用上一次的token
|
||
// 不再重用了
|
||
const application = context.getApplication();
|
||
const [originToken] = await context.select(
|
||
'token',
|
||
{
|
||
data: {
|
||
id: 1,
|
||
value: 1,
|
||
},
|
||
filter: {
|
||
applicationId: application!.id,
|
||
ableState: 'enabled',
|
||
entity,
|
||
entityId: entityId,
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
if (originToken) {
|
||
return originToken.value!;
|
||
}
|
||
} */
|
||
const tokenData = {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
env,
|
||
refreshedAt: Date.now(),
|
||
value: await (0, uuid_1.generateNewIdAsync)(),
|
||
};
|
||
if (user) {
|
||
// 根据此用户状态进行处理
|
||
const { userState } = user;
|
||
let userId = user.id;
|
||
switch (userState) {
|
||
case 'normal': {
|
||
break;
|
||
}
|
||
case 'merged': {
|
||
userId = user.refId;
|
||
break;
|
||
}
|
||
case 'disabled': {
|
||
throw new Exception_1.OakUserDisabledException();
|
||
}
|
||
case 'shadow': {
|
||
await context.operate('user', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'activate',
|
||
data: {},
|
||
filter: {
|
||
id: userId,
|
||
},
|
||
}, { dontCollect: true });
|
||
break;
|
||
}
|
||
default: {
|
||
(0, assert_1.assert)(false, `不能理解的user状态「${userState}」`);
|
||
}
|
||
}
|
||
tokenData.userId = userId;
|
||
tokenData.applicationId = context.getApplicationId();
|
||
tokenData.playerId = userId;
|
||
if (entityId) {
|
||
tokenData.entity = entity;
|
||
tokenData.entityId = entityId;
|
||
}
|
||
else {
|
||
(0, assert_1.assert)(createData);
|
||
Object.assign(tokenData, {
|
||
[entity]: {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: Object.assign(createData, {
|
||
userId,
|
||
}),
|
||
},
|
||
});
|
||
}
|
||
await context.operate('token', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: tokenData,
|
||
}, { dontCollect: true });
|
||
return tokenData.value;
|
||
}
|
||
else {
|
||
// 创建新用户
|
||
// 要先create token,再create entity。不然可能权限会被挡住
|
||
const userData = {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
userState: 'normal',
|
||
};
|
||
await context.operate('user', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: userData,
|
||
}, {});
|
||
(0, assert_1.assert)(entityId || createData.id, 'entityId和createData必须存在一项');
|
||
tokenData.userId = userData.id;
|
||
tokenData.playerId = userData.id;
|
||
tokenData.entity = entity;
|
||
tokenData.entityId = entityId || createData.id;
|
||
tokenData.applicationId = context.getApplicationId();
|
||
await context.operate('token', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: tokenData,
|
||
}, { dontCollect: true });
|
||
await context.setTokenValue(tokenData.value);
|
||
if (createData) {
|
||
await context.operate(entity, {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: Object.assign(createData, {
|
||
userId: userData.id,
|
||
}),
|
||
}, { dontCollect: true });
|
||
}
|
||
else {
|
||
(0, assert_1.assert)(entityId);
|
||
await context.operate(entity, {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
userId: userData.id,
|
||
},
|
||
filter: {
|
||
id: entityId,
|
||
},
|
||
}, { dontCollect: true });
|
||
}
|
||
return tokenData.value;
|
||
}
|
||
}
|
||
}
|
||
async function setupMobile(mobile, env, context) {
|
||
const result2 = await context.select('mobile', {
|
||
data: {
|
||
id: 1,
|
||
mobile: 1,
|
||
userId: 1,
|
||
ableState: 1,
|
||
user: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
ref: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
},
|
||
wechatUser$user: {
|
||
$entity: 'wechatUser',
|
||
data: {
|
||
id: 1,
|
||
},
|
||
},
|
||
userSystem$user: {
|
||
$entity: 'userSystem',
|
||
data: {
|
||
id: 1,
|
||
systemId: 1,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
mobile,
|
||
ableState: 'enabled',
|
||
},
|
||
}, { dontCollect: true });
|
||
if (result2.length > 0) {
|
||
// 此手机号已经存在
|
||
(0, assert_1.assert)(result2.length === 1);
|
||
const [mobileRow] = result2;
|
||
const { user } = mobileRow;
|
||
const { userState, ref } = user;
|
||
if (userState === 'merged') {
|
||
return await setUpTokenAndUser(env, context, 'mobile', mobileRow.id, undefined, ref);
|
||
}
|
||
return await setUpTokenAndUser(env, context, 'mobile', mobileRow.id, undefined, user);
|
||
}
|
||
else {
|
||
//此手机号不存在
|
||
return await setUpTokenAndUser(env, context, 'mobile', undefined, {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
mobile,
|
||
});
|
||
}
|
||
}
|
||
async function loadTokenInfo(tokenValue, context) {
|
||
return await context.select('token', {
|
||
data: (0, lodash_1.cloneDeep)(Projection_1.tokenProjection),
|
||
filter: {
|
||
value: tokenValue,
|
||
},
|
||
}, {});
|
||
}
|
||
async function loginByMobile(params, context) {
|
||
const { mobile, captcha, env, disableRegister } = params;
|
||
const loginLogic = async (isRoot) => {
|
||
const systemId = context.getSystemId();
|
||
if (isRoot) {
|
||
return await setupMobile(mobile, env, context);
|
||
}
|
||
const result = await context.select('captcha', {
|
||
data: {
|
||
id: 1,
|
||
expired: 1,
|
||
},
|
||
filter: {
|
||
origin: 'mobile',
|
||
content: mobile,
|
||
code: captcha,
|
||
},
|
||
sorter: [
|
||
{
|
||
$attr: {
|
||
$$createAt$$: 1,
|
||
},
|
||
$direction: 'desc',
|
||
},
|
||
],
|
||
indexFrom: 0,
|
||
count: 1,
|
||
}, { dontCollect: true });
|
||
if (result.length > 0) {
|
||
const [captchaRow] = result;
|
||
if (captchaRow.expired) {
|
||
throw new types_1.OakUserException('验证码已经过期');
|
||
}
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captchaRow.id
|
||
}
|
||
}, {});
|
||
// 到这里说明验证码已经通过
|
||
return await setupMobile(mobile, env, context);
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('验证码无效');
|
||
}
|
||
};
|
||
const isRoot = context.isRoot();
|
||
const closeRootMode = context.openRootMode();
|
||
const application = context.getApplication();
|
||
const [applicationPassport] = await context.select('applicationPassport', {
|
||
data: {
|
||
id: 1,
|
||
passportId: 1,
|
||
passport: {
|
||
id: 1,
|
||
config: 1,
|
||
type: 1,
|
||
},
|
||
applicationId: 1,
|
||
},
|
||
filter: {
|
||
applicationId: application?.id,
|
||
passport: {
|
||
type: 'sms'
|
||
},
|
||
}
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
(0, assert_1.assert)(applicationPassport?.passport);
|
||
if (disableRegister) {
|
||
const [existMobile] = await context.select('mobile', {
|
||
data: {
|
||
id: 1,
|
||
mobile: 1,
|
||
},
|
||
filter: {
|
||
mobile: mobile,
|
||
ableState: 'enabled',
|
||
},
|
||
}, { dontCollect: true });
|
||
if (!existMobile) {
|
||
closeRootMode();
|
||
throw new types_1.OakUserException('账号不存在');
|
||
}
|
||
}
|
||
const tokenValue = await loginLogic(isRoot);
|
||
await loadTokenInfo(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
async function verifyPassword(params, context) {
|
||
const { password } = params;
|
||
const systemId = context.getSystemId();
|
||
const [pwdPassport] = await context.select('passport', {
|
||
data: {
|
||
id: 1,
|
||
systemId: 1,
|
||
config: 1,
|
||
type: 1,
|
||
enabled: 1,
|
||
},
|
||
filter: {
|
||
systemId,
|
||
enabled: true,
|
||
type: 'password',
|
||
}
|
||
}, { forUpdate: true });
|
||
// assert(pwdPassport);
|
||
const pwdMode = pwdPassport?.config?.mode ?? 'all';
|
||
let pwdFilter = {};
|
||
if (pwdMode === 'all') {
|
||
pwdFilter = {
|
||
$or: [
|
||
{
|
||
password,
|
||
},
|
||
{
|
||
passwordSha1: (0, password_1.encryptPasswordSha1)(password),
|
||
},
|
||
]
|
||
};
|
||
}
|
||
else if (pwdMode === 'plain') {
|
||
pwdFilter = {
|
||
password,
|
||
};
|
||
}
|
||
else if (pwdMode === 'sha1') {
|
||
pwdFilter = {
|
||
passwordSha1: password,
|
||
};
|
||
}
|
||
const userId = context.getCurrentUserId();
|
||
const [user] = await context.select('user', {
|
||
data: {},
|
||
filter: {
|
||
id: userId,
|
||
...pwdFilter,
|
||
}
|
||
}, {});
|
||
if (!user) {
|
||
throw new types_1.OakUserException('error::user.passwordUnmatch');
|
||
}
|
||
await context.operate('user', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
verifyPasswordAt: Date.now(),
|
||
},
|
||
filter: {
|
||
id: userId,
|
||
}
|
||
}, {});
|
||
}
|
||
async function loginByAccount(params, context) {
|
||
const { account, password, env } = params;
|
||
let needUpdatePassword = false;
|
||
const loginLogic = async () => {
|
||
const systemId = context.getSystemId();
|
||
const applicationId = context.getApplicationId();
|
||
(0, assert_1.assert)(password);
|
||
(0, assert_1.assert)(account);
|
||
const accountType = (0, validator_1.isEmail)(account) ? 'email' : ((0, validator_1.isMobile)(account) ? 'mobile' : 'loginName');
|
||
if (accountType === 'email') {
|
||
const { config, emailConfig } = await (0, passport_1.getAndCheckPassportByEmail)(context, account);
|
||
const existEmail = await context.select('email', {
|
||
data: {
|
||
id: 1,
|
||
email: 1,
|
||
},
|
||
filter: {
|
||
email: account,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
if (!(existEmail && existEmail.length > 0)) {
|
||
throw new types_1.OakUserException('error::user.emailUnexists');
|
||
}
|
||
const result = await context.select('user', {
|
||
data: {
|
||
id: 1,
|
||
password: 1,
|
||
passwordSha1: 1,
|
||
email$user: {
|
||
$entity: 'email',
|
||
data: {
|
||
id: 1,
|
||
email: 1,
|
||
ableState: 1,
|
||
}
|
||
},
|
||
},
|
||
filter: {
|
||
$and: [
|
||
{
|
||
email$user: {
|
||
email: account,
|
||
}
|
||
},
|
||
{
|
||
...pwdFilter,
|
||
}
|
||
],
|
||
},
|
||
}, { dontCollect: true });
|
||
switch (result.length) {
|
||
case 0: {
|
||
throw new types_1.OakUserException('error::user.passwordUnmath');
|
||
}
|
||
case 1: {
|
||
const applicationPassports = await context.select('applicationPassport', {
|
||
data: {
|
||
id: 1,
|
||
applicationId: 1,
|
||
passportId: 1,
|
||
passport: {
|
||
id: 1,
|
||
type: 1,
|
||
systemId: 1,
|
||
}
|
||
},
|
||
filter: {
|
||
passport: {
|
||
systemId,
|
||
},
|
||
applicationId,
|
||
}
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
const allowEmail = !!applicationPassports.find((ele) => ele.passport?.type === 'email');
|
||
const [userRow] = result;
|
||
const { email$user, id: userId, } = userRow;
|
||
needUpdatePassword = !(userRow.password || userRow.passwordSha1);
|
||
if (allowEmail) {
|
||
const email = email$user?.find(ele => ele.email.toLowerCase() === account.toLowerCase());
|
||
if (email) {
|
||
const ableState = email.ableState;
|
||
if (ableState === 'disabled') {
|
||
// 虽然密码和邮箱匹配,但邮箱已经禁用了,在可能的情况下提醒用户使用其它方法登录
|
||
const exception = await tryMakeChangeLoginWay(userId, context);
|
||
if (exception) {
|
||
throw exception;
|
||
}
|
||
}
|
||
return await setupEmail(account, env, context);
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('error::user.emailUnexists');
|
||
}
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('error::user.loginWayDisabled');
|
||
}
|
||
}
|
||
default: {
|
||
throw new types_1.OakUserException('error::user.loginWayDisabled');
|
||
}
|
||
}
|
||
}
|
||
else if (accountType === 'mobile') {
|
||
const existMobile = await context.select('mobile', {
|
||
data: {
|
||
id: 1,
|
||
mobile: 1,
|
||
},
|
||
filter: {
|
||
mobile: account,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
if (!(existMobile && existMobile.length > 0)) {
|
||
throw new types_1.OakUserException('手机号未注册');
|
||
}
|
||
const result = await context.select('user', {
|
||
data: {
|
||
id: 1,
|
||
password: 1,
|
||
passwordSha1: 1,
|
||
mobile$user: {
|
||
$entity: 'mobile',
|
||
data: {
|
||
id: 1,
|
||
mobile: 1,
|
||
ableState: 1,
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
$and: [
|
||
{
|
||
mobile$user: {
|
||
mobile: account,
|
||
}
|
||
},
|
||
{
|
||
...pwdFilter,
|
||
}
|
||
],
|
||
},
|
||
}, { dontCollect: true });
|
||
switch (result.length) {
|
||
case 0: {
|
||
throw new types_1.OakUserException('手机号与密码不匹配');
|
||
}
|
||
case 1: {
|
||
const applicationPassports = await context.select('applicationPassport', {
|
||
data: {
|
||
id: 1,
|
||
applicationId: 1,
|
||
passportId: 1,
|
||
passport: {
|
||
id: 1,
|
||
type: 1,
|
||
systemId: 1,
|
||
}
|
||
},
|
||
filter: {
|
||
passport: {
|
||
systemId,
|
||
},
|
||
applicationId,
|
||
}
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
const [userRow] = result;
|
||
const { mobile$user, id: userId, } = userRow;
|
||
needUpdatePassword = !(userRow.password || userRow.passwordSha1);
|
||
const allowSms = !!applicationPassports.find((ele) => ele.passport?.type === 'sms');
|
||
if (allowSms) {
|
||
const mobile = mobile$user?.find(ele => ele.mobile === account);
|
||
if (mobile) {
|
||
const ableState = mobile.ableState;
|
||
if (ableState === 'disabled') {
|
||
// 虽然密码和手机号匹配,但手机号已经禁用了,在可能的情况下提醒用户使用其它方法登录
|
||
const exception = await tryMakeChangeLoginWay(userId, context);
|
||
if (exception) {
|
||
throw exception;
|
||
}
|
||
}
|
||
return await setupMobile(account, env, context);
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('手机号未注册');
|
||
}
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('暂不支持手机号登录');
|
||
}
|
||
}
|
||
default: {
|
||
throw new types_1.OakUserException('不支持的登录方式');
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
const existLoginName = await context.select('loginName', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
},
|
||
filter: {
|
||
name: account,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
if (!(existLoginName && existLoginName.length > 0)) {
|
||
throw new types_1.OakUserException('账号未注册');
|
||
}
|
||
const result = await context.select('user', {
|
||
data: {
|
||
id: 1,
|
||
password: 1,
|
||
passwordSha1: 1,
|
||
loginName$user: {
|
||
$entity: 'loginName',
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
ableState: 1,
|
||
}
|
||
}
|
||
},
|
||
filter: {
|
||
$and: [
|
||
{
|
||
loginName$user: {
|
||
name: account,
|
||
}
|
||
},
|
||
{
|
||
...pwdFilter,
|
||
}
|
||
],
|
||
},
|
||
}, { dontCollect: true });
|
||
switch (result.length) {
|
||
case 0: {
|
||
throw new types_1.OakUserException('账号与密码不匹配');
|
||
}
|
||
case 1: {
|
||
const [userRow] = result;
|
||
needUpdatePassword = !(userRow.password || userRow.passwordSha1);
|
||
const { loginName$user, id: userId, } = userRow;
|
||
const loginName = loginName$user?.find((ele) => ele.name.toLowerCase() === account.toLowerCase());
|
||
if (loginName) {
|
||
const ableState = loginName.ableState;
|
||
if (ableState === 'disabled') {
|
||
// 虽然密码和账号匹配,但账号已经禁用了,在可能的情况下提醒用户使用其它方法登录
|
||
const exception = await tryMakeChangeLoginWay(userId, context);
|
||
if (exception) {
|
||
throw exception;
|
||
}
|
||
}
|
||
return await setupLoginName(account, env, context);
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('账号不存在');
|
||
}
|
||
}
|
||
default: {
|
||
throw new types_1.OakUserException('不支持的登录方式');
|
||
}
|
||
}
|
||
}
|
||
};
|
||
const closeRootMode = context.openRootMode();
|
||
const application = context.getApplication();
|
||
const [applicationPassport] = await context.select('applicationPassport', {
|
||
data: {
|
||
id: 1,
|
||
passportId: 1,
|
||
passport: {
|
||
id: 1,
|
||
config: 1,
|
||
type: 1,
|
||
},
|
||
applicationId: 1,
|
||
},
|
||
filter: {
|
||
applicationId: application?.id,
|
||
passport: {
|
||
type: 'password',
|
||
},
|
||
}
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
// assert(applicationPassport?.passport);
|
||
const pwdMode = applicationPassport?.passport?.config?.mode ?? 'all';
|
||
let pwdFilter = {}, updateData = {};
|
||
if (pwdMode === 'all') {
|
||
pwdFilter = {
|
||
$or: [
|
||
{
|
||
password,
|
||
},
|
||
{
|
||
passwordSha1: (0, password_1.encryptPasswordSha1)(password),
|
||
},
|
||
],
|
||
};
|
||
updateData = {
|
||
password,
|
||
passwordSha1: (0, password_1.encryptPasswordSha1)(password),
|
||
};
|
||
}
|
||
else if (pwdMode === 'plain') {
|
||
pwdFilter = {
|
||
password,
|
||
};
|
||
updateData = {
|
||
password,
|
||
};
|
||
}
|
||
else if (pwdMode === 'sha1') {
|
||
pwdFilter = {
|
||
passwordSha1: password,
|
||
};
|
||
updateData = {
|
||
passwordSha1: password,
|
||
};
|
||
}
|
||
const tokenValue = await loginLogic();
|
||
const [tokenInfo] = await loadTokenInfo(tokenValue, context);
|
||
const { userId } = tokenInfo;
|
||
await context.operate('user', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: needUpdatePassword ? {
|
||
verifyPasswordAt: Date.now(),
|
||
...updateData,
|
||
} : {
|
||
verifyPasswordAt: Date.now(),
|
||
},
|
||
filter: {
|
||
id: userId,
|
||
}
|
||
}, {});
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
async function loginByEmail(params, context) {
|
||
const { email, captcha, env, disableRegister } = params;
|
||
const loginLogic = async () => {
|
||
const systemId = context.getSystemId();
|
||
const result = await context.select('captcha', {
|
||
data: {
|
||
id: 1,
|
||
expired: 1,
|
||
},
|
||
filter: {
|
||
origin: 'email',
|
||
content: email,
|
||
code: captcha,
|
||
},
|
||
sorter: [
|
||
{
|
||
$attr: {
|
||
$$createAt$$: 1,
|
||
},
|
||
$direction: 'desc',
|
||
},
|
||
],
|
||
indexFrom: 0,
|
||
count: 1,
|
||
}, { dontCollect: true });
|
||
if (result.length > 0) {
|
||
const [captchaRow] = result;
|
||
if (captchaRow.expired) {
|
||
throw new types_1.OakUserException('验证码已经过期');
|
||
}
|
||
// 到这里说明验证码已经通过,可以让验证码失效
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captchaRow.id
|
||
}
|
||
}, {});
|
||
return await setupEmail(email, env, context);
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('验证码无效');
|
||
}
|
||
};
|
||
const closeRootMode = context.openRootMode();
|
||
const { config, emailConfig } = await (0, passport_1.getAndCheckPassportByEmail)(context, email);
|
||
if (disableRegister) {
|
||
const [existEmail] = await context.select('email', {
|
||
data: {
|
||
id: 1,
|
||
email: 1,
|
||
},
|
||
filter: {
|
||
email: email,
|
||
ableState: 'enabled',
|
||
},
|
||
}, { dontCollect: true });
|
||
if (!existEmail) {
|
||
closeRootMode();
|
||
throw new types_1.OakUserException('账号不存在');
|
||
}
|
||
}
|
||
const tokenValue = await loginLogic();
|
||
await loadTokenInfo(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
async function bindByMobile(params, context) {
|
||
const { mobile, captcha, env, } = params;
|
||
const userId = context.getCurrentUserId();
|
||
const bindLogic = async () => {
|
||
const systemId = context.getSystemId();
|
||
const result = await context.select('captcha', {
|
||
data: {
|
||
id: 1,
|
||
expired: 1,
|
||
},
|
||
filter: {
|
||
origin: 'mobile',
|
||
content: mobile,
|
||
code: captcha,
|
||
},
|
||
sorter: [
|
||
{
|
||
$attr: {
|
||
$$createAt$$: 1,
|
||
},
|
||
$direction: 'desc',
|
||
},
|
||
],
|
||
indexFrom: 0,
|
||
count: 1,
|
||
}, { dontCollect: true });
|
||
if (result.length > 0) {
|
||
const [captchaRow] = result;
|
||
if (captchaRow.expired) {
|
||
throw new types_1.OakUserException('验证码已经过期');
|
||
}
|
||
// 到这里说明验证码已经通过
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captchaRow.id
|
||
}
|
||
}, {});
|
||
// 检查当前user是否已绑定mobile
|
||
const [boundMobile] = await context.select('mobile', {
|
||
data: {
|
||
id: 1,
|
||
mobile: 1,
|
||
},
|
||
filter: {
|
||
ableState: 'enabled',
|
||
userId: userId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
forUpdate: true,
|
||
});
|
||
if (boundMobile) {
|
||
//用户已绑定的mobile与当前输入的mobile一致
|
||
if (boundMobile.mobile === mobile) {
|
||
throw new types_1.OakUserException('已绑定该手机号,无需重复绑定');
|
||
}
|
||
//更新mobile
|
||
await context.operate('mobile', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
mobile,
|
||
},
|
||
filter: {
|
||
id: boundMobile.id,
|
||
},
|
||
}, {});
|
||
}
|
||
else {
|
||
//创建mobile
|
||
await context.operate('mobile', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
mobile,
|
||
userId,
|
||
},
|
||
}, {});
|
||
}
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('验证码无效');
|
||
}
|
||
};
|
||
const closeRootMode = context.openRootMode();
|
||
const [otherUserMobile] = await context.select('mobile', {
|
||
data: {
|
||
id: 1,
|
||
mobile: 1,
|
||
},
|
||
filter: {
|
||
mobile: mobile,
|
||
ableState: 'enabled',
|
||
userId: {
|
||
$ne: userId,
|
||
}
|
||
},
|
||
}, { dontCollect: true });
|
||
if (otherUserMobile) {
|
||
closeRootMode();
|
||
throw new types_1.OakUserException('该手机号已绑定其他用户,请检查');
|
||
}
|
||
await bindLogic();
|
||
closeRootMode();
|
||
}
|
||
async function bindByEmail(params, context) {
|
||
const { email, captcha, env, } = params;
|
||
const userId = context.getCurrentUserId();
|
||
const bindLogic = async () => {
|
||
const systemId = context.getSystemId();
|
||
const result = await context.select('captcha', {
|
||
data: {
|
||
id: 1,
|
||
expired: 1,
|
||
},
|
||
filter: {
|
||
origin: 'email',
|
||
content: email,
|
||
code: captcha,
|
||
},
|
||
sorter: [
|
||
{
|
||
$attr: {
|
||
$$createAt$$: 1,
|
||
},
|
||
$direction: 'desc',
|
||
},
|
||
],
|
||
indexFrom: 0,
|
||
count: 1,
|
||
}, { dontCollect: true });
|
||
if (result.length > 0) {
|
||
const [captchaRow] = result;
|
||
if (captchaRow.expired) {
|
||
throw new types_1.OakUserException('验证码已经过期');
|
||
}
|
||
// 到这里说明验证码已经通过,可以让验证码失效
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captchaRow.id
|
||
}
|
||
}, {});
|
||
//检查当前user是否已绑定email
|
||
const [boundEmail] = await context.select('email', {
|
||
data: {
|
||
id: 1,
|
||
email: 1,
|
||
},
|
||
filter: {
|
||
ableState: 'enabled',
|
||
userId: userId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
forUpdate: true,
|
||
});
|
||
if (boundEmail) {
|
||
//用户已绑定的email与当前输入的email一致
|
||
if (boundEmail.email === email) {
|
||
throw new types_1.OakUserException('已绑定该邮箱,无需重复绑定');
|
||
}
|
||
//更新email
|
||
await context.operate('email', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
email,
|
||
},
|
||
filter: {
|
||
id: boundEmail.id,
|
||
},
|
||
}, {});
|
||
}
|
||
else {
|
||
//创建email
|
||
await context.operate('email', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
email,
|
||
userId,
|
||
},
|
||
}, {});
|
||
}
|
||
}
|
||
else {
|
||
throw new types_1.OakUserException('验证码无效');
|
||
}
|
||
};
|
||
const closeRootMode = context.openRootMode();
|
||
const { config, emailConfig } = await (0, passport_1.getAndCheckPassportByEmail)(context, email);
|
||
const [otherUserEmail] = await context.select('email', {
|
||
data: {
|
||
id: 1,
|
||
email: 1,
|
||
},
|
||
filter: {
|
||
email: email,
|
||
ableState: 'enabled',
|
||
userId: {
|
||
$ne: userId,
|
||
}
|
||
},
|
||
}, { dontCollect: true });
|
||
if (otherUserEmail) {
|
||
closeRootMode();
|
||
throw new types_1.OakUserException('该邮箱已绑定其他用户,请检查');
|
||
}
|
||
await bindLogic();
|
||
closeRootMode();
|
||
}
|
||
async function setupLoginName(name, env, context) {
|
||
const result2 = await context.select('loginName', {
|
||
data: {
|
||
id: 1,
|
||
name: 1,
|
||
userId: 1,
|
||
ableState: 1,
|
||
user: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
ref: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
},
|
||
wechatUser$user: {
|
||
$entity: 'wechatUser',
|
||
data: {
|
||
id: 1,
|
||
},
|
||
},
|
||
userSystem$user: {
|
||
$entity: 'userSystem',
|
||
data: {
|
||
id: 1,
|
||
systemId: 1,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
name,
|
||
ableState: 'enabled',
|
||
},
|
||
}, { dontCollect: true });
|
||
(0, assert_1.assert)(result2.length === 1);
|
||
const [loginNameRow] = result2;
|
||
const { user } = loginNameRow;
|
||
const { userState, ref } = user;
|
||
if (userState === 'merged') {
|
||
return await setUpTokenAndUser(env, context, 'loginName', loginNameRow.id, undefined, ref);
|
||
}
|
||
return await setUpTokenAndUser(env, context, 'loginName', loginNameRow.id, undefined, user);
|
||
}
|
||
async function setupEmail(email, env, context) {
|
||
const result2 = await context.select('email', {
|
||
data: {
|
||
id: 1,
|
||
email: 1,
|
||
userId: 1,
|
||
ableState: 1,
|
||
user: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
ref: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
},
|
||
wechatUser$user: {
|
||
$entity: 'wechatUser',
|
||
data: {
|
||
id: 1,
|
||
},
|
||
},
|
||
userSystem$user: {
|
||
$entity: 'userSystem',
|
||
data: {
|
||
id: 1,
|
||
systemId: 1,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
email,
|
||
ableState: 'enabled',
|
||
},
|
||
}, { dontCollect: true });
|
||
if (result2.length > 0) {
|
||
// 此邮箱已经存在
|
||
(0, assert_1.assert)(result2.length === 1);
|
||
const [emailRow] = result2;
|
||
const { user } = emailRow;
|
||
const { userState, ref } = user;
|
||
if (userState === 'merged') {
|
||
return await setUpTokenAndUser(env, context, 'email', emailRow.id, undefined, ref);
|
||
}
|
||
return await setUpTokenAndUser(env, context, 'email', emailRow.id, undefined, user);
|
||
}
|
||
else {
|
||
//此邮箱不存在
|
||
return await setUpTokenAndUser(env, context, 'email', undefined, {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
email,
|
||
});
|
||
}
|
||
}
|
||
async function setUserInfoFromWechat(user, userInfo, context) {
|
||
const application = context.getApplication();
|
||
const applicationId = context.getApplicationId();
|
||
const config = application?.system?.config || application?.system?.platform?.config;
|
||
const { nickname, gender, avatar } = userInfo;
|
||
const { nickname: originalNickname, gender: originalGender, extraFile$entity, } = user;
|
||
const updateData = {};
|
||
if (nickname && nickname !== originalNickname) {
|
||
Object.assign(updateData, {
|
||
nickname,
|
||
});
|
||
}
|
||
if (gender && gender !== originalGender) {
|
||
Object.assign(updateData, {
|
||
gender,
|
||
});
|
||
}
|
||
if (avatar &&
|
||
(extraFile$entity?.length === 0 ||
|
||
await (0, index_backend_1.composeFileUrlBackend)(application, extraFile$entity[0], context) !== avatar)) {
|
||
// 需要更新新的avatar extra file
|
||
const extraFileOperations = [
|
||
{
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: Object.assign({
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
tag1: 'avatar',
|
||
entity: 'user',
|
||
entityId: user.id,
|
||
objectId: await (0, uuid_1.generateNewIdAsync)(),
|
||
origin: 'unknown',
|
||
extra1: avatar,
|
||
type: 'image',
|
||
filename: '',
|
||
bucket: '',
|
||
applicationId: applicationId,
|
||
}),
|
||
},
|
||
];
|
||
if (extraFile$entity.length > 0) {
|
||
extraFileOperations.push({
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'remove',
|
||
data: {},
|
||
filter: {
|
||
id: extraFile$entity[0].id,
|
||
},
|
||
});
|
||
}
|
||
Object.assign(updateData, {
|
||
extraFile$entity: extraFileOperations,
|
||
});
|
||
}
|
||
if (Object.keys(updateData).length > 0) {
|
||
await context.operate('user', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: updateData,
|
||
filter: {
|
||
id: user.id,
|
||
},
|
||
}, {});
|
||
}
|
||
}
|
||
//将获取的微信头像作为用户头像
|
||
async function setUserAvatarFromWechat(params, context) {
|
||
const { avatar } = params;
|
||
const application = context.getApplication();
|
||
const { type, config } = application;
|
||
(0, assert_1.assert)(type === 'wechatMp' || config.type === 'wechatMp');
|
||
const applicationId = context.getApplicationId();
|
||
const userId = context.getCurrentUserId();
|
||
const extraFiles = await context.select('extraFile', {
|
||
data: {
|
||
id: 1,
|
||
tag1: 1,
|
||
origin: 1,
|
||
bucket: 1,
|
||
objectId: 1,
|
||
filename: 1,
|
||
extra1: 1,
|
||
entity: 1,
|
||
entityId: 1,
|
||
},
|
||
filter: {
|
||
entity: 'user',
|
||
entityId: userId,
|
||
tag1: 'avatar',
|
||
}
|
||
}, { forUpdate: true });
|
||
const updateData = {};
|
||
if (avatar &&
|
||
(extraFiles?.length === 0 ||
|
||
await (0, index_backend_1.composeFileUrlBackend)(application, extraFiles[0], context) !== avatar)) {
|
||
// 需要更新新的avatar extra file
|
||
const extraFileOperations = [
|
||
{
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: Object.assign({
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
tag1: 'avatar',
|
||
entity: 'user',
|
||
entityId: userId,
|
||
objectId: await (0, uuid_1.generateNewIdAsync)(),
|
||
origin: 'unknown',
|
||
extra1: avatar,
|
||
type: 'image',
|
||
filename: '',
|
||
bucket: '',
|
||
applicationId: applicationId,
|
||
}),
|
||
},
|
||
];
|
||
if (extraFiles.length > 0) {
|
||
extraFileOperations.push({
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'remove',
|
||
data: {},
|
||
filter: {
|
||
id: extraFiles[0].id,
|
||
},
|
||
});
|
||
}
|
||
Object.assign(updateData, {
|
||
extraFile$entity: extraFileOperations,
|
||
});
|
||
}
|
||
if (Object.keys(updateData).length > 0) {
|
||
await context.operate('user', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: updateData,
|
||
filter: {
|
||
id: userId,
|
||
},
|
||
}, {});
|
||
}
|
||
}
|
||
async function tryRefreshWechatPublicUserInfo(wechatUserId, context) {
|
||
const [wechatUser] = await context.select('wechatUser', {
|
||
data: {
|
||
id: 1,
|
||
accessToken: 1,
|
||
refreshToken: 1,
|
||
atExpiredAt: 1,
|
||
rtExpiredAt: 1,
|
||
scope: 1,
|
||
openId: 1,
|
||
user: {
|
||
id: 1,
|
||
nickname: 1,
|
||
gender: 1,
|
||
extraFile$entity: {
|
||
$entity: 'extraFile',
|
||
data: {
|
||
id: 1,
|
||
tag1: 1,
|
||
origin: 1,
|
||
bucket: 1,
|
||
objectId: 1,
|
||
filename: 1,
|
||
extra1: 1,
|
||
entity: 1,
|
||
entityId: 1,
|
||
},
|
||
filter: {
|
||
tag1: 'avatar',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
id: wechatUserId,
|
||
},
|
||
}, { dontCollect: true });
|
||
const application = context.getApplication();
|
||
const { type, config } = application;
|
||
(0, assert_1.assert)(type !== 'wechatMp' && type !== 'native');
|
||
if (type === 'web') {
|
||
return;
|
||
}
|
||
let appId, appSecret;
|
||
const config2 = config;
|
||
appId = config2.appId;
|
||
appSecret = config2.appSecret;
|
||
const wechatInstance = WechatSDK_1.default.getInstance(appId, type, appSecret);
|
||
let { accessToken, refreshToken, atExpiredAt, rtExpiredAt, scope, openId, user, } = wechatUser;
|
||
const now = Date.now();
|
||
(0, assert_1.assert)(scope.toLowerCase().includes('userinfo'));
|
||
if (rtExpiredAt < now) {
|
||
// refreshToken过期,直接返回未登录异常,使用户去重新登录
|
||
throw new types_1.OakUnloggedInException();
|
||
}
|
||
if (atExpiredAt < now) {
|
||
// 刷新accessToken
|
||
const { accessToken: at2, atExpiredAt: ate2, scope: s2, } = await wechatInstance.refreshUserAccessToken(refreshToken);
|
||
await context.operate('wechatUser', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
accessToken: at2,
|
||
atExpiredAt: ate2,
|
||
scope: s2,
|
||
},
|
||
filter: {
|
||
id: wechatUserId,
|
||
}
|
||
}, { dontCollect: true });
|
||
accessToken = at2;
|
||
}
|
||
const { nickname, gender, avatar } = await wechatInstance.getUserInfo(accessToken, openId);
|
||
await context.operate('wechatUser', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
nickname,
|
||
avatar,
|
||
},
|
||
filter: {
|
||
id: wechatUserId,
|
||
}
|
||
}, { dontCollect: true });
|
||
await setUserInfoFromWechat(user, { nickname, gender: gender, avatar }, context);
|
||
}
|
||
async function refreshWechatPublicUserInfo({}, context) {
|
||
const tokenValue = context.getTokenValue();
|
||
const [token] = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
entity: 1,
|
||
entityId: 1,
|
||
},
|
||
filter: {
|
||
id: tokenValue,
|
||
},
|
||
}, { dontCollect: true });
|
||
(0, assert_1.assert)(token.entity === 'wechatUser');
|
||
(0, assert_1.assert)(token.entityId);
|
||
return await tryRefreshWechatPublicUserInfo(token.entityId, context);
|
||
}
|
||
// 用户在微信端授权登录后,在web端触发该方法
|
||
async function loginByWechat(params, context) {
|
||
const { wechatLoginId, env } = params;
|
||
const closeRootMode = context.openRootMode();
|
||
const [wechatLoginData] = await context.select('wechatLogin', {
|
||
data: {
|
||
id: 1,
|
||
userId: 1,
|
||
user: {
|
||
id: 1,
|
||
name: 1,
|
||
nickname: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
isRoot: 1,
|
||
},
|
||
type: 1,
|
||
},
|
||
filter: {
|
||
id: wechatLoginId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
const tokenValue = await setUpTokenAndUser(env, context, 'wechatLogin', wechatLoginId, undefined, wechatLoginData.user);
|
||
await loadTokenInfo(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
||
const application = context.getApplication();
|
||
const { type, config, systemId } = application;
|
||
let appId, appSecret;
|
||
if (type === 'wechatPublic') {
|
||
const config2 = config;
|
||
appId = config2.appId;
|
||
appSecret = config2.appSecret;
|
||
}
|
||
else if (type === 'wechatMp') {
|
||
const config2 = config;
|
||
appId = config2.appId;
|
||
appSecret = config2.appSecret;
|
||
}
|
||
else if (type === 'native') {
|
||
const config2 = config;
|
||
(0, assert_1.assert)(config2.wechatNative);
|
||
appId = config2.wechatNative.appId;
|
||
appSecret = config2.wechatNative.appSecret;
|
||
}
|
||
else {
|
||
(0, assert_1.assert)(type === 'web');
|
||
const config2 = config;
|
||
(0, assert_1.assert)(config2.wechat);
|
||
appId = config2.wechat.appId;
|
||
appSecret = config2.wechat.appSecret;
|
||
}
|
||
const wechatInstance = WechatSDK_1.default.getInstance(appId, type, appSecret);
|
||
const { isSnapshotUser, openId, unionId, ...wechatUserData } = await wechatInstance.code2Session(code);
|
||
if (isSnapshotUser) {
|
||
throw new types_1.OakUserException('请使用完整服务后再进行登录操作');
|
||
}
|
||
const OriginMap = {
|
||
web: 'web',
|
||
wechatPublic: 'public',
|
||
wechatMp: 'mp',
|
||
native: 'native',
|
||
};
|
||
const createWechatUserAndReturnTokenId = async (user, wechatLoginId) => {
|
||
const wechatUserCreateData = {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
unionId,
|
||
origin: OriginMap[type],
|
||
openId,
|
||
applicationId: application.id,
|
||
...wechatUserData,
|
||
};
|
||
let tokenValue;
|
||
if (wechatLoginId) {
|
||
tokenValue = await setUpTokenAndUser(env, context, 'wechatLogin', wechatLoginId, undefined, user);
|
||
}
|
||
else {
|
||
tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', undefined, wechatUserCreateData, user);
|
||
}
|
||
return { tokenValue, wechatUserId: wechatUserCreateData.id };
|
||
};
|
||
// 扫码者
|
||
const [wechatUser] = await context.select('wechatUser', {
|
||
data: {
|
||
id: 1,
|
||
userId: 1,
|
||
unionId: 1,
|
||
user: {
|
||
id: 1,
|
||
name: 1,
|
||
nickname: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
isRoot: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
applicationId: application.id,
|
||
openId,
|
||
origin: OriginMap[type],
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
if (wechatLoginId) {
|
||
const updateWechatLogin = async (updateData) => {
|
||
await context.operate('wechatLogin', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: updateData,
|
||
filter: {
|
||
id: wechatLoginId,
|
||
},
|
||
}, { dontCollect: true });
|
||
};
|
||
// 扫码产生的实体wechaLogin
|
||
const [wechatLoginData] = await context.select('wechatLogin', {
|
||
data: {
|
||
id: 1,
|
||
userId: 1,
|
||
type: 1,
|
||
user: {
|
||
id: 1,
|
||
name: 1,
|
||
nickname: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
isRoot: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
id: wechatLoginId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
(0, assert_1.assert)(wechatLoginData, 'wechatLogin data not found');
|
||
// 用户已登录,通过扫描二维码绑定
|
||
if (wechatLoginData.type === 'bind') {
|
||
// 首先通过wechaLogin.userId查询是否存在wechatUser 判断是否绑定
|
||
// 登录者
|
||
const [wechatUserLogin] = await context.select('wechatUser', {
|
||
data: {
|
||
id: 1,
|
||
userId: 1,
|
||
user: {
|
||
id: 1,
|
||
name: 1,
|
||
nickname: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
isRoot: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
userId: wechatLoginData.userId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
// 已绑定
|
||
if (wechatUserLogin) {
|
||
throw new types_1.OakUserException('当前登录者已绑定微信');
|
||
}
|
||
// 未绑定的情况,就要先看扫码者是否绑定了公众号
|
||
// 扫码者已绑定, 将扫码者的userId替换成登录者的userId
|
||
if (wechatUser) {
|
||
await context.operate('wechatUser', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
userId: wechatLoginData.userId,
|
||
...wechatUserData
|
||
},
|
||
filter: {
|
||
id: wechatUser.id,
|
||
},
|
||
}, { dontCollect: true });
|
||
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', wechatUser.id, undefined, wechatLoginData.user);
|
||
await updateWechatLogin({ successed: true });
|
||
return tokenValue;
|
||
}
|
||
else {
|
||
const { tokenValue } = await createWechatUserAndReturnTokenId(wechatLoginData.user);
|
||
await updateWechatLogin({ successed: true });
|
||
return tokenValue;
|
||
}
|
||
}
|
||
// 用户未登录情况下
|
||
else if (wechatLoginData.type === 'login') {
|
||
// wechatUser存在直接登录
|
||
if (wechatUser) {
|
||
// 微信公众号关注后,会创建一个没有userId的wechatUser
|
||
let user2 = wechatUser.user;
|
||
if (!user2 && unionId) {
|
||
// 如果有unionId,查找同一个system下有相同的unionId的user
|
||
const [user] = await context.select('user', {
|
||
data: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
},
|
||
filter: {
|
||
wechatUser$user: {
|
||
application: {
|
||
systemId: application.systemId,
|
||
},
|
||
unionId,
|
||
},
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
user2 = user;
|
||
}
|
||
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', wechatUser.id, undefined, user2);
|
||
const wechatUserUpdateData = wechatUserData;
|
||
if (unionId !== wechatUser.unionId) {
|
||
Object.assign(wechatUserUpdateData, {
|
||
unionId,
|
||
});
|
||
}
|
||
if (user2 && !wechatUser.userId) {
|
||
Object.assign(wechatUserUpdateData, {
|
||
userId: user2.id,
|
||
});
|
||
}
|
||
await context.operate('wechatUser', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: wechatUserUpdateData,
|
||
filter: {
|
||
id: wechatUser.id,
|
||
},
|
||
}, { dontCollect: true });
|
||
await updateWechatLogin({
|
||
userId: wechatUser.userId,
|
||
successed: true,
|
||
});
|
||
return tokenValue;
|
||
}
|
||
else {
|
||
// 创建user和wechatUser(绑定并登录)
|
||
const userId = await (0, uuid_1.generateNewIdAsync)();
|
||
const userData = {
|
||
id: userId,
|
||
userState: 'normal',
|
||
};
|
||
await context.operate('user', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: userData,
|
||
}, {});
|
||
const { tokenValue, wechatUserId } = await createWechatUserAndReturnTokenId(userData, wechatLoginId);
|
||
await updateWechatLogin({ userId, wechatUserId, successed: true });
|
||
return tokenValue;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
if (wechatUser) {
|
||
// 微信公众号关注后,会创建一个没有userId的wechatUser
|
||
let user2 = wechatUser.user;
|
||
if (!user2 && unionId) {
|
||
// 如果有unionId,查找同一个system下有相同的unionId的user
|
||
const [user] = await context.select('user', {
|
||
data: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
},
|
||
filter: {
|
||
wechatUser$user: {
|
||
application: {
|
||
systemId: application.systemId,
|
||
},
|
||
unionId,
|
||
}
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
user2 = user;
|
||
}
|
||
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', wechatUser.id, undefined, user2);
|
||
const wechatUserUpdateData = wechatUserData;
|
||
if (unionId !== wechatUser.unionId) {
|
||
Object.assign(wechatUserUpdateData, {
|
||
unionId,
|
||
});
|
||
}
|
||
if (user2 && !wechatUser.userId) {
|
||
Object.assign(wechatUserUpdateData, {
|
||
userId: user2.id,
|
||
});
|
||
}
|
||
await context.operate('wechatUser', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: wechatUserUpdateData,
|
||
filter: {
|
||
id: wechatUser.id,
|
||
},
|
||
}, { dontCollect: true });
|
||
return tokenValue;
|
||
}
|
||
else if (unionId) {
|
||
// 如果有unionId,查找同一个system下有没有相同的unionId
|
||
const [wechatUser3] = await context.select('wechatUser', {
|
||
data: {
|
||
id: 1,
|
||
userId: 1,
|
||
unionId: 1,
|
||
user: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
application: {
|
||
systemId: application.systemId,
|
||
},
|
||
unionId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
if (wechatUser3) {
|
||
const wechatUserCreateData = {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
unionId,
|
||
origin: OriginMap[type],
|
||
openId,
|
||
applicationId: application.id,
|
||
...wechatUserData,
|
||
};
|
||
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', undefined, wechatUserCreateData, wechatUser3.user);
|
||
if (!wechatUser3.userId) {
|
||
// 这里顺便帮其它wechatUser数据也补上相应的userId
|
||
await context.operate('wechatUser', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
userId: wechatUserCreateData.userId, // 在setUpTokenAndUser内赋上值
|
||
},
|
||
filter: {
|
||
id: wechatUser3.id,
|
||
},
|
||
}, { dontCollect: true });
|
||
}
|
||
return tokenValue;
|
||
}
|
||
}
|
||
}
|
||
// 到这里都是要同时创建wechatUser和user对象了
|
||
return (await createWechatUserAndReturnTokenId()).tokenValue;
|
||
}
|
||
/**
|
||
* 微信App授权登录
|
||
* @param param0
|
||
* @param context
|
||
* @returns
|
||
*/
|
||
async function loginWechatNative({ code, env, }, context) {
|
||
const closeRootMode = context.openRootMode();
|
||
const tokenValue = await loginFromWechatEnv(code, env, context);
|
||
await loadTokenInfo(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
/**
|
||
* 公众号授权登录
|
||
* @param param0
|
||
* @param context
|
||
*/
|
||
async function loginWechat({ code, env, wechatLoginId, }, context) {
|
||
const closeRootMode = context.openRootMode();
|
||
const tokenValue = await loginFromWechatEnv(code, env, context, wechatLoginId);
|
||
const [tokenInfo] = await loadTokenInfo(tokenValue, context);
|
||
(0, assert_1.assert)(tokenInfo.entity === 'wechatUser' || tokenInfo.entity === 'wechatLogin');
|
||
await context.setTokenValue(tokenValue);
|
||
if (tokenInfo.entity === 'wechatUser') {
|
||
await tryRefreshWechatPublicUserInfo(tokenInfo.entityId, context);
|
||
}
|
||
else if (tokenInfo.entity === 'wechatLogin') {
|
||
const [wechatLogin] = await context.select('wechatLogin', {
|
||
data: {
|
||
id: 1,
|
||
wechatUserId: 1,
|
||
},
|
||
filter: {
|
||
id: tokenInfo.entityId,
|
||
},
|
||
}, {});
|
||
(0, assert_1.assert)(wechatLogin?.wechatUserId);
|
||
await tryRefreshWechatPublicUserInfo(wechatLogin.wechatUserId, context);
|
||
}
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
/**
|
||
* 小程序授权登录
|
||
* @param param0
|
||
* @param context
|
||
* @returns
|
||
*/
|
||
async function loginWechatMp({ code, env, }, context) {
|
||
const closeRootMode = context.openRootMode();
|
||
const tokenValue = await loginFromWechatEnv(code, env, context);
|
||
await loadTokenInfo(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
/**
|
||
* 同步从wx.getUserProfile拿到的用户信息
|
||
* @param param0
|
||
* @param context
|
||
*/
|
||
async function syncUserInfoWechatMp({ nickname, avatarUrl, encryptedData, iv, signature, }, context) {
|
||
const { userId } = context.getToken();
|
||
const application = context.getApplication();
|
||
const config = application?.system?.config || application?.system?.platform?.config;
|
||
const [{ sessionKey, user }] = await context.select('wechatUser', {
|
||
data: {
|
||
id: 1,
|
||
sessionKey: 1,
|
||
nickname: 1,
|
||
avatar: 1,
|
||
user: {
|
||
id: 1,
|
||
nickname: 1,
|
||
gender: 1,
|
||
extraFile$entity: {
|
||
$entity: 'extraFile',
|
||
data: {
|
||
id: 1,
|
||
tag1: 1,
|
||
origin: 1,
|
||
bucket: 1,
|
||
objectId: 1,
|
||
filename: 1,
|
||
extra1: 1,
|
||
entity: 1,
|
||
entityId: 1,
|
||
},
|
||
filter: {
|
||
tag1: 'avatar',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
userId: userId,
|
||
applicationId: application.id,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
const { type, config: config2 } = application;
|
||
(0, assert_1.assert)(type === 'wechatMp' || config2.type === 'wechatMp');
|
||
const { appId, appSecret } = config2;
|
||
const wechatInstance = WechatSDK_1.default.getInstance(appId, 'wechatMp', appSecret);
|
||
const result = wechatInstance.decryptData(sessionKey, encryptedData, iv, signature);
|
||
// 实测发现解密出来的和userInfo完全一致……
|
||
await setUserInfoFromWechat(user, { nickname, avatar: avatarUrl }, context);
|
||
}
|
||
async function sendCaptchaByMobile({ mobile, env, type: captchaType, }, context) {
|
||
const { type } = env;
|
||
let visitorId = mobile;
|
||
if (type === 'web' || type === 'native') {
|
||
visitorId = env.visitorId;
|
||
}
|
||
const application = context.getApplication();
|
||
const [applicationPassport] = await context.select('applicationPassport', {
|
||
data: {
|
||
id: 1,
|
||
passportId: 1,
|
||
passport: {
|
||
id: 1,
|
||
config: 1,
|
||
type: 1,
|
||
},
|
||
applicationId: 1,
|
||
},
|
||
filter: {
|
||
applicationId: application?.id,
|
||
passport: {
|
||
type: 'sms'
|
||
},
|
||
}
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
if (!applicationPassport?.passport) {
|
||
throw new Exception_1.OakIncompleteConfig(application.id);
|
||
}
|
||
const config = applicationPassport.passport.config;
|
||
const mockSend = config.mockSend;
|
||
const codeTemplateName = config.templateName;
|
||
const origin = config.defaultOrigin;
|
||
const duration = config.codeDuration || 1;
|
||
const digit = config.digit || 4;
|
||
const now = Date.now();
|
||
const closeRootMode = context.openRootMode();
|
||
if (!mockSend) {
|
||
const [count1, count2] = await Promise.all([
|
||
context.count('captcha', {
|
||
filter: {
|
||
origin: 'mobile',
|
||
visitorId,
|
||
$$createAt$$: {
|
||
$gt: now - 3600 * 1000,
|
||
},
|
||
type: captchaType,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
}),
|
||
context.count('captcha', {
|
||
filter: {
|
||
origin: 'mobile',
|
||
content: mobile,
|
||
$$createAt$$: {
|
||
$gt: now - 3600 * 1000,
|
||
},
|
||
type: captchaType,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
}),
|
||
]);
|
||
if (count1 > 5 || count2 > 5) {
|
||
closeRootMode();
|
||
throw new types_1.OakUserException('您已发送很多次短信,请休息会再发吧');
|
||
}
|
||
}
|
||
const [captcha] = await context.select('captcha', {
|
||
data: {
|
||
id: 1,
|
||
code: 1,
|
||
$$createAt$$: 1,
|
||
},
|
||
filter: {
|
||
origin: 'mobile',
|
||
content: mobile,
|
||
$$createAt$$: {
|
||
$gt: now - duration * 60 * 1000,
|
||
},
|
||
expired: false,
|
||
type: captchaType,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
const getCode = async () => {
|
||
let code;
|
||
if (mockSend) {
|
||
code = mobile.substring(11 - digit);
|
||
}
|
||
else {
|
||
// code = Math.floor(Math.random() * Math.pow(10, digit)).toString();
|
||
// while (code.length < digit) {
|
||
// code += '0';
|
||
// }
|
||
code = Array.from({ length: digit }, () => Math.floor(Math.random() * 10)).join('');
|
||
}
|
||
const id = await (0, uuid_1.generateNewIdAsync)();
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
origin: 'mobile',
|
||
content: mobile,
|
||
code,
|
||
visitorId,
|
||
env,
|
||
expired: false,
|
||
expiresAt: now + duration * 60 * 1000,
|
||
type: captchaType,
|
||
applicationId: application?.id,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
return {
|
||
code,
|
||
captchaId: id
|
||
};
|
||
};
|
||
if (captcha) {
|
||
const captchaDuration = process.env.NODE_ENV === 'development' ? 10 * 1000 : 60 * 1000;
|
||
if (now - captcha.$$createAt$$ < captchaDuration) {
|
||
closeRootMode();
|
||
throw new types_1.OakUserException('您的操作太迅捷啦,请稍候再点吧');
|
||
}
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captcha.id
|
||
}
|
||
}, {});
|
||
}
|
||
const { code, captchaId } = await getCode();
|
||
if (mockSend) {
|
||
closeRootMode();
|
||
return `验证码[${code}]已创建`;
|
||
}
|
||
else {
|
||
(0, assert_1.assert)(origin, '必须设置短信渠道');
|
||
//发送短信
|
||
const result = await (0, sms_1.sendSms)({
|
||
origin: origin,
|
||
templateName: codeTemplateName,
|
||
mobile,
|
||
templateParam: { code, duration: duration.toString() },
|
||
}, context);
|
||
closeRootMode();
|
||
if (result.success) {
|
||
return '验证码已发送';
|
||
}
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captchaId
|
||
}
|
||
}, {});
|
||
console.error('短信发送失败,原因:\n', result?.res);
|
||
return '验证码发送失败';
|
||
}
|
||
}
|
||
async function sendCaptchaByEmail({ email, env, type: captchaType, }, context) {
|
||
const { type } = env;
|
||
let visitorId = email;
|
||
if (type === 'web' || type === 'native') {
|
||
visitorId = env.visitorId;
|
||
}
|
||
const { config, emailConfig } = await (0, passport_1.getAndCheckPassportByEmail)(context, email);
|
||
const duration = config.codeDuration || 5;
|
||
const digit = config.digit || 4;
|
||
const mockSend = config.mockSend;
|
||
let emailOptions = {
|
||
// host: emailConfig.host,
|
||
// port: emailConfig.port,
|
||
// secure: emailConfig.secure,
|
||
// account: emailConfig.account,
|
||
// password: emailConfig.password,
|
||
from: emailConfig.name ? `"${emailConfig.name}" <${emailConfig.account}>` : emailConfig.account,
|
||
subject: config.subject,
|
||
to: email,
|
||
text: config.text,
|
||
html: config.html,
|
||
};
|
||
const now = Date.now();
|
||
const closeRootMode = context.openRootMode();
|
||
if (!mockSend) {
|
||
const [count1, count2] = await Promise.all([
|
||
context.count('captcha', {
|
||
filter: {
|
||
origin: 'email',
|
||
visitorId,
|
||
$$createAt$$: {
|
||
$gt: now - 3600 * 1000,
|
||
},
|
||
type: captchaType,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
}),
|
||
context.count('captcha', {
|
||
filter: {
|
||
origin: 'email',
|
||
content: email,
|
||
$$createAt$$: {
|
||
$gt: now - 3600 * 1000,
|
||
},
|
||
type: captchaType,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
}),
|
||
]);
|
||
if (count1 > 5 || count2 > 5) {
|
||
closeRootMode();
|
||
throw new types_1.OakUserException('您已发送很多次邮件,请休息会再发吧');
|
||
}
|
||
}
|
||
const [captcha] = await context.select('captcha', {
|
||
data: {
|
||
id: 1,
|
||
code: 1,
|
||
$$createAt$$: 1,
|
||
},
|
||
filter: {
|
||
origin: 'email',
|
||
content: email,
|
||
$$createAt$$: {
|
||
$gt: now - duration * 60 * 1000,
|
||
},
|
||
expired: false,
|
||
type: captchaType,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
const getCode = async () => {
|
||
let code;
|
||
code = Array.from({ length: digit }, () => Math.floor(Math.random() * 10)).join('');
|
||
const id = await (0, uuid_1.generateNewIdAsync)();
|
||
const applicationId = context.getApplication()?.id;
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: {
|
||
id,
|
||
origin: 'email',
|
||
content: email,
|
||
code,
|
||
visitorId,
|
||
env,
|
||
expired: false,
|
||
expiresAt: now + duration * 60 * 1000,
|
||
type: captchaType,
|
||
applicationId,
|
||
},
|
||
}, {
|
||
dontCollect: true,
|
||
});
|
||
return {
|
||
code,
|
||
captchaId: id
|
||
};
|
||
};
|
||
if (captcha) {
|
||
const captchaDuration = process.env.NODE_ENV === 'development' ? 10 * 1000 : 60 * 1000;
|
||
if (now - captcha.$$createAt$$ < captchaDuration) {
|
||
closeRootMode();
|
||
throw new types_1.OakUserException('您的操作太迅捷啦,请稍候再点吧');
|
||
}
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captcha.id
|
||
}
|
||
}, {});
|
||
}
|
||
const { code, captchaId } = await getCode();
|
||
if (mockSend) {
|
||
closeRootMode();
|
||
return `验证码[${code}]已创建`;
|
||
}
|
||
else {
|
||
(0, assert_1.assert)(config.account, '必须设置邮箱');
|
||
//发送邮件
|
||
const subject = config.subject?.replace('${code}', code);
|
||
const text = config.text?.replace('${duration}', duration.toString()).replace('${code}', code);
|
||
const html = config.html?.replace('${duration}', duration.toString()).replace('${code}', code);
|
||
emailOptions.subject = subject;
|
||
emailOptions.text = text;
|
||
emailOptions.html = html;
|
||
const result = await (0, email_1.sendEmail)(emailOptions, context);
|
||
closeRootMode();
|
||
if (result.success) {
|
||
return '验证码已发送';
|
||
}
|
||
await context.operate('captcha', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captchaId
|
||
}
|
||
}, {});
|
||
console.error('邮件发送失败,原因:\n', result?.error);
|
||
return '验证码发送失败';
|
||
}
|
||
}
|
||
async function switchTo({ userId }, context) {
|
||
const reallyRoot = context.isReallyRoot();
|
||
if (!reallyRoot) {
|
||
throw new types_1.OakOperationUnpermittedException('user', { id: 'switchTo', action: 'switch', data: {}, filter: { id: userId } }, context.getCurrentUserId());
|
||
}
|
||
const currentUserId = context.getCurrentUserId();
|
||
if (currentUserId === userId) {
|
||
throw new types_1.OakPreConditionUnsetException('您已经是当前用户');
|
||
}
|
||
const token = context.getToken();
|
||
await context.operate('token', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
userId,
|
||
},
|
||
filter: {
|
||
id: token.id,
|
||
},
|
||
}, {});
|
||
}
|
||
async function getWechatMpUserPhoneNumber({ code, env }, context) {
|
||
const application = context.getApplication();
|
||
const { type, config, systemId } = application;
|
||
(0, assert_1.assert)(type === 'wechatMp' && config.type === 'wechatMp');
|
||
const config2 = config;
|
||
const { appId, appSecret } = config2;
|
||
const wechatInstance = WechatSDK_1.default.getInstance(appId, 'wechatMp', appSecret);
|
||
const result = await wechatInstance.getUserPhoneNumber(code);
|
||
const closeRootMode = context.openRootMode();
|
||
//获取 绑定的手机号码
|
||
const phoneNumber = result?.phoneNumber;
|
||
const reuslt = await setupMobile(phoneNumber, env, context);
|
||
closeRootMode();
|
||
return reuslt;
|
||
}
|
||
async function logout(params, context) {
|
||
const { tokenValue } = params;
|
||
if (tokenValue) {
|
||
const closeRootMode = context.openRootMode();
|
||
try {
|
||
await context.operate('token', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'disable',
|
||
data: {},
|
||
filter: {
|
||
value: tokenValue,
|
||
ableState: 'enabled',
|
||
},
|
||
}, { dontCollect: true });
|
||
}
|
||
catch (err) {
|
||
closeRootMode();
|
||
throw err;
|
||
}
|
||
closeRootMode();
|
||
}
|
||
}
|
||
/**
|
||
* 创建一个当前parasite上的token
|
||
* @param params
|
||
* @param context
|
||
* @returns
|
||
*/
|
||
async function wakeupParasite(params, context) {
|
||
const { id, env } = params;
|
||
const [parasite] = await context.select('parasite', {
|
||
data: {
|
||
id: 1,
|
||
expired: 1,
|
||
multiple: 1,
|
||
userId: 1,
|
||
tokenLifeLength: 1,
|
||
user: {
|
||
id: 1,
|
||
userState: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
id,
|
||
},
|
||
}, { dontCollect: true });
|
||
if (parasite.expired) {
|
||
const e = new types_1.OakRowInconsistencyException('数据已经过期');
|
||
e.addData('parasite', [parasite], context.getSchema());
|
||
throw e;
|
||
}
|
||
if (parasite.user?.userState !== 'shadow') {
|
||
throw new types_1.OakUserException('此用户已经登录过系统,不允许借用身份');
|
||
}
|
||
const closeRootMode = context.openRootMode();
|
||
if (!parasite.multiple) {
|
||
await context.operate('parasite', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'wakeup',
|
||
data: {
|
||
expired: true,
|
||
},
|
||
filter: {
|
||
id,
|
||
},
|
||
}, { dontCollect: true });
|
||
}
|
||
const tokenValue = await (0, uuid_1.generateNewIdAsync)();
|
||
await context.operate('token', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'create',
|
||
data: {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
entity: 'parasite',
|
||
entityId: id,
|
||
userId: parasite.userId,
|
||
playerId: parasite.userId,
|
||
disablesAt: Date.now() + parasite.tokenLifeLength,
|
||
env,
|
||
refreshedAt: Date.now(),
|
||
value: tokenValue,
|
||
applicationId: context.getApplicationId(),
|
||
},
|
||
}, { dontCollect: true });
|
||
await loadTokenInfo(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
/**
|
||
* todo 检查登录环境一致性,同一个token不能跨越不同设备
|
||
* @param env1
|
||
* @param env2
|
||
* @returns
|
||
*/
|
||
function checkTokenEnvConsistency(env1, env2) {
|
||
if (env1.type !== env2.type) {
|
||
return false;
|
||
}
|
||
switch (env1.type) {
|
||
case 'web': {
|
||
return env1.visitorId === env2.visitorId;
|
||
}
|
||
case 'wechatMp': {
|
||
return true;
|
||
}
|
||
case 'native': {
|
||
return env1.visitorId === env2.visitorId;
|
||
}
|
||
default: {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
async function refreshToken(params, context) {
|
||
const { env, tokenValue, applicationId: appId } = params;
|
||
const closeRootMode = context.openRootMode();
|
||
let [token] = await context.select('token', {
|
||
data: Object.assign({
|
||
env: 1,
|
||
...Projection_1.tokenProjection,
|
||
}),
|
||
filter: {
|
||
$or: [
|
||
{
|
||
value: tokenValue,
|
||
},
|
||
{
|
||
oldValue: tokenValue,
|
||
// refreshedAt: {
|
||
// $gte: Date.now() - 300 * 1000,
|
||
// },
|
||
},
|
||
],
|
||
},
|
||
}, {});
|
||
if (!token) {
|
||
throw new types_1.OakUnloggedInException("Token令牌已失效,请重新登录");
|
||
}
|
||
const now = Date.now();
|
||
if (!checkTokenEnvConsistency(env, token.env)) {
|
||
console.log('####### refreshToken 环境改变 start #######\n');
|
||
console.log(env);
|
||
console.log('---------------------\n');
|
||
console.log(token.env);
|
||
console.log('####### refreshToken 环境改变 end #######\n');
|
||
await context.operate('token', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'disable',
|
||
data: {},
|
||
filter: {
|
||
id: token.id,
|
||
}
|
||
}, { dontCollect: true });
|
||
closeRootMode();
|
||
return '';
|
||
}
|
||
if (process.env.OAK_PLATFORM === 'server') {
|
||
// 只有server模式去刷新token
|
||
// 'development' | 'production' | 'staging'
|
||
const intervals = {
|
||
development: 7200 * 1000, // 2小时
|
||
staging: 600 * 1000, // 十分钟
|
||
production: 600 * 1000, // 十分钟
|
||
};
|
||
let applicationId = token.applicationId;
|
||
// TODO 修复token上缺失applicationId, 原因是创建user时,创建的token上未附上applicationId,后面数据处理好了再移除代码 by wkj
|
||
if (!applicationId) {
|
||
(0, assert_1.assert)(appId, '从上下文中获取applicationId为空');
|
||
const [application] = await context.select('application', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: appId,
|
||
},
|
||
}, {
|
||
dontCollect: true
|
||
});
|
||
(0, assert_1.assert)(application, 'application找不到');
|
||
await context.operate('token', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
applicationId: application.id,
|
||
},
|
||
filter: {
|
||
id: token.id,
|
||
}
|
||
}, { dontCollect: true });
|
||
applicationId = application.id;
|
||
}
|
||
//assert(applicationId, 'token上applicationId必须存在,请检查token数据');
|
||
const [application] = await context.select('application', {
|
||
data: {
|
||
id: 1,
|
||
systemId: 1,
|
||
system: {
|
||
config: 1,
|
||
},
|
||
},
|
||
filter: {
|
||
id: applicationId,
|
||
},
|
||
}, { dontCollect: true });
|
||
const system = application?.system;
|
||
const tokenRefreshTime = system?.config?.App?.tokenRefreshTime; // 系统设置刷新间隔 毫秒
|
||
const interval = tokenRefreshTime || intervals[process.env.NODE_ENV];
|
||
if (tokenRefreshTime !== 0 && now - token.refreshedAt > interval) {
|
||
const newValue = await (0, uuid_1.generateNewIdAsync)();
|
||
console.log('####### refreshToken token update start #######\n');
|
||
console.log('刷新前tokenId:', token?.id);
|
||
console.log('刷新前tokenValue:', tokenValue);
|
||
console.log('---------------------\n');
|
||
await context.operate('token', {
|
||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||
action: 'update',
|
||
data: {
|
||
value: newValue,
|
||
refreshedAt: now,
|
||
oldValue: tokenValue,
|
||
},
|
||
filter: {
|
||
id: token.id,
|
||
},
|
||
}, {});
|
||
console.log('刷新后tokenValue:', newValue);
|
||
console.log('####### refreshToken token update end #######\n');
|
||
closeRootMode();
|
||
return newValue;
|
||
}
|
||
}
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
/**
|
||
* 使用微信小程序中的token登录web
|
||
* @param tokenValue
|
||
* @param env
|
||
* @param context
|
||
* @returns
|
||
*/
|
||
async function setupWechatMp(mpToken, env, context) {
|
||
const [token] = await context.select('token', {
|
||
data: {
|
||
id: 1,
|
||
entity: 1,
|
||
entityId: 1,
|
||
ableState: 1,
|
||
userId: 1,
|
||
user: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
ref: {
|
||
id: 1,
|
||
userState: 1,
|
||
refId: 1,
|
||
},
|
||
wechatUser$user: {
|
||
$entity: 'wechatUser',
|
||
data: {
|
||
id: 1,
|
||
},
|
||
},
|
||
userSystem$user: {
|
||
$entity: 'userSystem',
|
||
data: {
|
||
id: 1,
|
||
systemId: 1,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
filter: {
|
||
$or: [{
|
||
value: mpToken,
|
||
}, {
|
||
oldValue: mpToken,
|
||
}]
|
||
// ableState: 'enabled',
|
||
},
|
||
}, { dontCollect: true });
|
||
(0, assert_1.assert)(token);
|
||
const { user } = token;
|
||
return await setUpTokenAndUser(env, context, token.entity, token.entityId, undefined, user);
|
||
}
|
||
/**
|
||
* 小程序web-view处理token
|
||
* @param mpToken
|
||
* @param env
|
||
* @param context
|
||
* @returns
|
||
*/
|
||
async function loginWebByMpToken(params, context) {
|
||
const { mpToken, env } = params;
|
||
const closeRootMode = context.openRootMode();
|
||
const tokenValue = await setupWechatMp(mpToken, env, context);
|
||
await loadTokenInfo(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|