oak-general-business/lib/aspects/token.js

2623 lines
90 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.refreshToken = exports.wakeupParasite = exports.logout = exports.getWechatMpUserPhoneNumber = exports.switchTo = exports.sendCaptchaByEmail = exports.sendCaptchaByMobile = exports.syncUserInfoWechatMp = exports.loginWechatMp = exports.loginWechat = exports.loginWechatNative = exports.loginByWechat = exports.refreshWechatPublicUserInfo = exports.bindByEmail = exports.bindByMobile = exports.loginByEmail = exports.loginByAccount = exports.verifyPassword = exports.loginByMobile = void 0;
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");
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 () => {
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('验证码已经过期');
}
// 到这里说明验证码已经通过
return await setupMobile(mobile, env, context);
}
else {
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: '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();
await loadTokenInfo(tokenValue, context);
closeRootMode();
return tokenValue;
}
exports.loginByMobile = loginByMobile;
async function verifyPassword(params, context) {
const { password } = params;
const userId = context.getCurrentUserId();
const [user] = await context.select('user', {
data: {},
filter: {
id: userId,
$or: [
{
password,
},
{
passwordSha1: (0, password_1.encryptPasswordSha1)(password),
},
],
}
}, {});
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,
}
}, {});
}
exports.verifyPassword = verifyPassword;
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 application = context.getApplication();
// const { system } = application!;
// 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: 'email'
// },
// }
// },
// {
// dontCollect: true,
// }
// );
// assert(applicationPassport?.passport);
// const config = applicationPassport.passport.config as EmailConfig;
// const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
// assert(emailConfig);
// const emailSuffixes = config.emailSuffixes;
// // 检查邮箱后缀是否满足配置
// if (emailSuffixes?.length! > 0) {
// let isValid = false;
// for (const suffix of emailSuffixes!) {
// if (account.endsWith(suffix)) {
// isValid = true;
// break;
// }
// }
// if (!isValid) {
// throw new OakUserException('error::user.emailSuffixIsInvalid');
// }
// }
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,
email$user: {
$entity: 'email',
data: {
id: 1,
email: 1,
ableState: 1,
}
},
},
filter: {
$and: [
{
email$user: {
email: account,
}
},
{
$or: [
{
password,
},
{
passwordSha1: (0, password_1.encryptPasswordSha1)(password),
},
],
}
],
},
}, { 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;
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,
mobile$user: {
$entity: 'mobile',
data: {
id: 1,
mobile: 1,
ableState: 1,
},
},
},
filter: {
$and: [
{
mobile$user: {
mobile: account,
}
},
{
$or: [
{
password,
},
{
passwordSha1: (0, password_1.encryptPasswordSha1)(password),
},
],
}
],
},
}, { 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;
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,
loginName$user: {
$entity: 'loginName',
data: {
id: 1,
name: 1,
ableState: 1,
}
}
},
filter: {
$and: [
{
loginName$user: {
name: account,
}
},
{
$or: [
{
password,
},
{
passwordSha1: (0, password_1.encryptPasswordSha1)(password),
},
],
}
],
},
}, { dontCollect: true });
switch (result.length) {
case 0: {
throw new types_1.OakUserException('账号与密码不匹配');
}
case 1: {
const [userRow] = result;
needUpdatePassword = !userRow.password;
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,
});
(0, assert_1.assert)(applicationPassport?.passport);
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: password ? {
password,
verifyPasswordAt: Date.now(),
} : {
verifyPasswordAt: Date.now(),
},
filter: {
id: userId,
}
}, {});
closeRootMode();
return tokenValue;
}
exports.loginByAccount = loginByAccount;
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 application = context.getApplication();
const { system } = application;
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: 'email'
},
}
}, {
dontCollect: true,
});
(0, assert_1.assert)(applicationPassport?.passport);
const config = applicationPassport.passport.config;
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
(0, assert_1.assert)(emailConfig);
const emailSuffixes = config.emailSuffixes;
// 检查邮箱后缀是否满足配置
if (emailSuffixes?.length > 0) {
let isValid = false;
for (const suffix of emailSuffixes) {
if (email.endsWith(suffix)) {
isValid = true;
break;
}
}
if (!isValid) {
throw new types_1.OakUserException('邮箱后缀不符合要求');
}
}
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;
}
exports.loginByEmail = loginByEmail;
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('验证码已经过期');
}
// 到这里说明验证码已经通过
// 检查当前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();
}
exports.bindByMobile = bindByMobile;
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 application = context.getApplication();
const { system } = application;
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: 'email'
},
}
}, {
dontCollect: true,
});
(0, assert_1.assert)(applicationPassport?.passport);
const config = applicationPassport.passport.config;
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
(0, assert_1.assert)(emailConfig);
const emailSuffixes = config.emailSuffixes;
// 检查邮箱后缀是否满足配置
if (emailSuffixes?.length > 0) {
let isValid = false;
for (const suffix of emailSuffixes) {
if (email.endsWith(suffix)) {
isValid = true;
break;
}
}
if (!isValid) {
throw new types_1.OakUserException('邮箱后缀不符合要求');
}
}
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();
}
exports.bindByEmail = bindByEmail;
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 ||
(0, index_backend_1.composeFileUrl)(application, extraFile$entity[0]) !== 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 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,
},
}, { dontCollect: true });
accessToken = at2;
}
const { nickname, gender, avatar } = await wechatInstance.getUserInfo(accessToken, openId);
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);
}
exports.refreshWechatPublicUserInfo = refreshWechatPublicUserInfo;
// 用户在微信端授权登录后在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;
}
exports.loginByWechat = loginByWechat;
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,
});
// 用户已登录,通过扫描二维码绑定
if (wechatLoginData && 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,
});
// 已绑定
(0, assert_1.assert)(!wechatUserLogin, '登录者已经绑定微信公众号');
// 未绑定的情况,就要先看扫码者是否绑定了公众号
// 扫码者已绑定, 将扫码者的userId替换成登录者的userId
if (wechatUser) {
await context.operate('wechatUser', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'update',
data: {
userId: wechatLoginData.userId,
},
filter: {
id: wechatUser.id,
},
}, { dontCollect: true });
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', wechatUser.id, undefined, wechatUserLogin
.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;
}
exports.loginWechatNative = loginWechatNative;
/**
* 公众号授权登录
* @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;
}
exports.loginWechat = loginWechat;
/**
* 小程序授权登录
* @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;
}
exports.loginWechatMp = loginWechatMp;
/**
* 同步从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);
}
exports.syncUserInfoWechatMp = syncUserInfoWechatMp;
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,
});
(0, assert_1.assert)(applicationPassport?.passport);
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: {
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';
}
}
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,
},
}, {
dontCollect: true,
});
return code;
};
let code = captcha?.code;
if (captcha) {
const captchaDuration = process.env.NODE_ENV === 'development' ? 10 * 1000 : 60 * 1000;
if (now - captcha.$$createAt$$ < captchaDuration) {
closeRootMode();
throw new types_1.OakUserException('您的操作太迅捷啦,请稍候再点吧');
}
}
else {
code = 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 '验证码已发送';
}
console.error('短信发送失败,原因:\n', result?.res);
return '验证码发送失败';
}
}
exports.sendCaptchaByMobile = sendCaptchaByMobile;
async function sendCaptchaByEmail({ email, env, type: captchaType, }, context) {
const { type } = env;
let visitorId = email;
if (type === 'web' || type === 'native') {
visitorId = env.visitorId;
}
const application = context.getApplication();
const { system } = application;
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: 'email'
},
}
}, {
dontCollect: true,
});
(0, assert_1.assert)(applicationPassport?.passport);
const config = applicationPassport.passport.config;
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
(0, assert_1.assert)(emailConfig);
const duration = config.codeDuration || 5;
const digit = config.digit || 4;
const mockSend = config.mockSend;
const emailSuffixes = config.emailSuffixes;
// 检查邮箱后缀是否满足配置
if (emailSuffixes?.length > 0) {
let isValid = false;
for (const suffix of emailSuffixes) {
if (email.endsWith(suffix)) {
isValid = true;
break;
}
}
if (!isValid) {
throw new types_1.OakUserException('邮箱后缀不符合要求');
}
}
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: {
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 = Math.floor(Math.random() * Math.random() * Math.pow(10, digit)).toString();
while (code.length < digit) {
code += '0';
}
const id = await (0, uuid_1.generateNewIdAsync)();
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,
},
}, {
dontCollect: true,
});
return code;
};
let code = captcha?.code;
if (captcha) {
const captchaDuration = process.env.NODE_ENV === 'development' ? 10 * 1000 : 60 * 1000;
if (now - captcha.$$createAt$$ < captchaDuration) {
closeRootMode();
throw new types_1.OakUserException('您的操作太迅捷啦,请稍候再点吧');
}
}
else {
code = 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 '验证码已发送';
}
console.error('邮件发送失败,原因:\n', result?.error);
return '验证码发送失败';
}
}
exports.sendCaptchaByEmail = sendCaptchaByEmail;
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 } });
}
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,
},
}, {});
}
exports.switchTo = switchTo;
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;
}
exports.getWechatMpUserPhoneNumber = getWechatMpUserPhoneNumber;
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();
}
}
exports.logout = logout;
/**
* 创建一个当前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;
}
exports.wakeupParasite = wakeupParasite;
/**
* 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,
// },
},
],
},
}, {});
(0, assert_1.assert)(token, `tokenValue: ${tokenValue}`);
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;
}
exports.refreshToken = refreshToken;