3377 lines
104 KiB
TypeScript
3377 lines
104 KiB
TypeScript
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||
import { EntityDict } from '../oak-app-domain';
|
||
import WechatSDK, {
|
||
WechatMpInstance,
|
||
WechatPublicInstance,
|
||
} from 'oak-external-sdk/lib/WechatSDK';
|
||
import { assert } from 'oak-domain/lib/utils/assert';
|
||
import {
|
||
WechatMpConfig,
|
||
WechatPublicConfig,
|
||
WebConfig,
|
||
NativeConfig,
|
||
} from '../oak-app-domain/Application/Schema';
|
||
import {
|
||
NativeEnv,
|
||
WebEnv,
|
||
WechatMpEnv,
|
||
} from 'oak-domain/lib/types/Environment';
|
||
import { CreateOperationData as CreateWechatUser } from '../oak-app-domain/WechatUser/Schema';
|
||
import { UpdateOperationData as UpdateWechatLoginData } from '../oak-app-domain/WechatLogin/Schema';
|
||
import { Operation as ExtraFileOperation } from '../oak-app-domain/ExtraFile/Schema';
|
||
import {
|
||
OakPreConditionUnsetException,
|
||
OakRowInconsistencyException,
|
||
OakUnloggedInException,
|
||
OakUserException,
|
||
OakOperationUnpermittedException,
|
||
} from 'oak-domain/lib/types';
|
||
import { composeFileUrl } from '../utils/cos/index.backend';
|
||
import {
|
||
OakChangeLoginWayException,
|
||
OakDistinguishUserException,
|
||
OakUserDisabledException,
|
||
} from '../types/Exception';
|
||
import { encryptPasswordSha1 } from '../utils/password';
|
||
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||
import { tokenProjection } from '../types/Projection';
|
||
import { sendSms } from '../utils/sms';
|
||
import { mergeUser } from './user';
|
||
import { cloneDeep, pick } from 'oak-domain/lib/utils/lodash';
|
||
import { BRC } from '../types/RuntimeCxt';
|
||
import { SmsConfig } from '../entities/Passport';
|
||
import { sendEmail } from '../utils/email';
|
||
import { EmailConfig } from '../oak-app-domain/Passport/Schema';
|
||
import { isEmail, isMobile } from 'oak-domain/lib/utils/validator';
|
||
import { EmailOptions } from '../types/Email';
|
||
import { getAndCheckPassportByEmail } from '../utils/passport';
|
||
|
||
async function makeDistinguishException<ED extends EntityDict>(userId: string, context: BRC<ED>, message?: string) {
|
||
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,
|
||
}
|
||
);
|
||
assert(user);
|
||
const { password, passwordSha1, idState, wechatUser$user, email$user, mobile$user } = user;
|
||
|
||
return new OakDistinguishUserException(
|
||
userId,
|
||
!!(password || passwordSha1),
|
||
idState === 'verified',
|
||
!!wechatUser$user?.length,
|
||
!!email$user?.length,
|
||
!!mobile$user?.length,
|
||
message
|
||
);
|
||
}
|
||
|
||
async function tryMakeChangeLoginWay<ED extends EntityDict>(userId: string, context: BRC<ED>) {
|
||
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,
|
||
}
|
||
);
|
||
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 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: Partial<EntityDict['user']['Schema']>,
|
||
context: BackendRuntimeContext<EntityDict>,
|
||
tokenData: EntityDict['token']['CreateOperationData']
|
||
): Promise<Partial<EntityDict['token']['CreateOperationData']>> {
|
||
switch (user.userState) {
|
||
case 'disabled': {
|
||
throw new OakUserDisabledException();
|
||
}
|
||
case 'shadow': {
|
||
return {
|
||
userId: user.id,
|
||
user: {
|
||
id: await generateNewIdAsync(),
|
||
action: 'activate',
|
||
data: {},
|
||
},
|
||
};
|
||
}
|
||
case 'merged': {
|
||
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: {
|
||
assert(user.userState === 'normal');
|
||
return {
|
||
userId: user.id,
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
function autoMergeUser<ED extends EntityDict>(context: BRC<ED>) {
|
||
const { system } = context.getApplication()!;
|
||
return !!system!.config!.App.mergeUserDirectly;
|
||
}
|
||
|
||
/**
|
||
* 根据user的不同情况,完成登录动作
|
||
* @param env
|
||
* @param context
|
||
* @param user
|
||
* @return tokenValue
|
||
*/
|
||
async function setUpTokenAndUser<ED extends EntityDict>(
|
||
env: WebEnv | WechatMpEnv | NativeEnv,
|
||
context: BRC<ED>,
|
||
entity: string, // 支持更多的登录渠道使用此函数创建token
|
||
entityId?: string, // 如果是现有对象传id,如果没有对象传createData
|
||
createData?: any,
|
||
user?: Partial<ED['user']['Schema']>
|
||
): Promise<string> {
|
||
const currentToken = context.getToken(true);
|
||
const schema = context.getSchema();
|
||
assert(schema.hasOwnProperty(entity), `${entity}必须是有效的对象名 `);
|
||
assert(
|
||
schema.token.attributes.entity.ref!.includes(entity),
|
||
`${entity}必须是token的有效关联对象`
|
||
);
|
||
assert(
|
||
schema[entity as keyof ED].attributes.hasOwnProperty('userId') &&
|
||
(schema[entity as keyof ED].attributes as any).userId!.ref === 'user',
|
||
`${entity}必须有指向user的userId属性`
|
||
);
|
||
if (currentToken) {
|
||
assert(currentToken.id);
|
||
assert(currentToken.userId);
|
||
if (user) {
|
||
// 有用户,和当前用户进行合并
|
||
const { userState } = user;
|
||
switch (userState) {
|
||
case 'normal': {
|
||
if (currentToken.userId === user.id) {
|
||
return currentToken.value!;
|
||
}
|
||
const autoMerge = autoMergeUser<ED>(context);
|
||
if (autoMerge) {
|
||
await mergeUser<ED>(
|
||
{ from: user.id!, to: currentToken.userId!, mergeMobile: true, mergeWechatUser: true, mergeEmail: true },
|
||
context,
|
||
true
|
||
);
|
||
return currentToken.value!;
|
||
}
|
||
throw await makeDistinguishException<ED>(user.id!, context);
|
||
}
|
||
case 'shadow': {
|
||
assert(currentToken.userId !== user.id);
|
||
const autoMerge = autoMergeUser<ED>(context);
|
||
if (autoMerge) {
|
||
await mergeUser<ED>(
|
||
{ from: user.id!, to: currentToken.userId!, mergeMobile: true, mergeWechatUser: true, mergeEmail: true },
|
||
context,
|
||
true
|
||
);
|
||
return currentToken.value!;
|
||
}
|
||
throw await makeDistinguishException<ED>(user.id!, context);
|
||
}
|
||
case 'disabled': {
|
||
throw new OakUserDisabledException();
|
||
}
|
||
case 'merged': {
|
||
assert(user.refId);
|
||
if (user.refId === currentToken.userId) {
|
||
return currentToken.value!;
|
||
}
|
||
const autoMerge = autoMergeUser<ED>(context);
|
||
if (autoMerge) {
|
||
// 说明一个用户被其他用户merge了,现在还是暂时先merge,后面再说
|
||
console.warn(
|
||
`用户${user.id}已经是merged状态「${user.refId}」,再次被merged到「${currentToken.userId}]」`
|
||
);
|
||
await mergeUser<ED>(
|
||
{ from: user.id!, to: currentToken.userId!, mergeMobile: true, mergeWechatUser: true, mergeEmail: true },
|
||
context,
|
||
true
|
||
);
|
||
return currentToken.value!;
|
||
}
|
||
throw await makeDistinguishException<ED>(user.id!, context);
|
||
}
|
||
default: {
|
||
assert(false, `不能理解的user状态「${userState}」`);
|
||
}
|
||
}
|
||
} else {
|
||
// 没用户,指向当前用户
|
||
assert(createData && !entityId);
|
||
if (createData) {
|
||
await context.operate(
|
||
entity as keyof ED,
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: Object.assign(createData, {
|
||
userId: currentToken.userId,
|
||
}),
|
||
} as any,
|
||
{ dontCollect: entity !== 'mobile' }
|
||
);
|
||
} else {
|
||
assert(entityId);
|
||
await context.operate(
|
||
entity as keyof ED,
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
userId: currentToken.userId,
|
||
},
|
||
filter: {
|
||
id: entityId,
|
||
},
|
||
} as any,
|
||
{ 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: EntityDict['token']['CreateOperationData'] = {
|
||
id: await generateNewIdAsync(),
|
||
env,
|
||
refreshedAt: Date.now(),
|
||
value: await generateNewIdAsync(),
|
||
};
|
||
if (user) {
|
||
// 根据此用户状态进行处理
|
||
const { userState } = user;
|
||
let userId: string = user.id!;
|
||
switch (userState) {
|
||
case 'normal': {
|
||
break;
|
||
}
|
||
case 'merged': {
|
||
userId = user.refId!;
|
||
break;
|
||
}
|
||
case 'disabled': {
|
||
throw new OakUserDisabledException();
|
||
}
|
||
case 'shadow': {
|
||
await context.operate(
|
||
'user',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'activate',
|
||
data: {},
|
||
filter: {
|
||
id: userId,
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
break;
|
||
}
|
||
default: {
|
||
assert(false, `不能理解的user状态「${userState}」`);
|
||
}
|
||
}
|
||
|
||
tokenData.userId = userId;
|
||
tokenData.applicationId = context.getApplicationId();
|
||
tokenData.playerId = userId;
|
||
if (entityId) {
|
||
tokenData.entity = entity;
|
||
tokenData.entityId = entityId;
|
||
} else {
|
||
assert(createData);
|
||
Object.assign(tokenData, {
|
||
[entity]: {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: Object.assign(createData, {
|
||
userId,
|
||
}),
|
||
},
|
||
});
|
||
}
|
||
|
||
await context.operate(
|
||
'token',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: tokenData,
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
return tokenData.value!;
|
||
} else {
|
||
// 创建新用户
|
||
// 要先create token,再create entity。不然可能权限会被挡住
|
||
const userData: EntityDict['user']['CreateOperationData'] = {
|
||
id: await generateNewIdAsync(),
|
||
userState: 'normal',
|
||
};
|
||
await context.operate(
|
||
'user',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: userData,
|
||
},
|
||
{}
|
||
);
|
||
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 generateNewIdAsync(),
|
||
action: 'create',
|
||
data: tokenData,
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
await context.setTokenValue(tokenData.value!);
|
||
|
||
if (createData) {
|
||
await context.operate(
|
||
entity as keyof ED,
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: Object.assign(createData, {
|
||
userId: userData.id,
|
||
}),
|
||
} as any,
|
||
{ dontCollect: true }
|
||
);
|
||
} else {
|
||
assert(entityId);
|
||
await context.operate(
|
||
entity as keyof ED,
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
userId: userData.id,
|
||
},
|
||
filter: {
|
||
id: entityId,
|
||
},
|
||
} as any,
|
||
{ dontCollect: true }
|
||
);
|
||
}
|
||
|
||
return tokenData.value!;
|
||
}
|
||
}
|
||
}
|
||
|
||
async function setupMobile<ED extends EntityDict>(mobile: string, env: WebEnv | WechatMpEnv | NativeEnv, context: BRC<ED>) {
|
||
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) {
|
||
// 此手机号已经存在
|
||
assert(result2.length === 1);
|
||
const [mobileRow] = result2;
|
||
const { user } = mobileRow;
|
||
const { userState, ref } = user!;
|
||
if (userState === 'merged') {
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'mobile',
|
||
mobileRow.id,
|
||
undefined,
|
||
ref as Partial<ED['user']['Schema']>
|
||
);
|
||
}
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'mobile',
|
||
mobileRow.id,
|
||
undefined,
|
||
user as Partial<ED['user']['Schema']>
|
||
);
|
||
} else {
|
||
//此手机号不存在
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'mobile',
|
||
undefined,
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
mobile,
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
async function loadTokenInfo<ED extends EntityDict>(tokenValue: string, context: BRC<ED>) {
|
||
return await context.select(
|
||
'token',
|
||
{
|
||
data: cloneDeep(tokenProjection),
|
||
filter: {
|
||
value: tokenValue,
|
||
},
|
||
},
|
||
{}
|
||
);
|
||
}
|
||
|
||
export async function loginByMobile<ED extends EntityDict>(
|
||
params: {
|
||
mobile: string;
|
||
captcha: string;
|
||
disableRegister?: boolean;
|
||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
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 OakUserException('验证码已经过期');
|
||
}
|
||
await context.operate(
|
||
'captcha',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captchaRow.id!
|
||
}
|
||
},
|
||
{}
|
||
);
|
||
|
||
// 到这里说明验证码已经通过
|
||
return await setupMobile<ED>(mobile, env, context);
|
||
} else {
|
||
throw new 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,
|
||
}
|
||
);
|
||
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 OakUserException('账号不存在');
|
||
}
|
||
}
|
||
const tokenValue = await loginLogic();
|
||
await loadTokenInfo<ED>(tokenValue, context);
|
||
closeRootMode();
|
||
|
||
return tokenValue;
|
||
}
|
||
|
||
export async function verifyPassword<ED extends EntityDict>(
|
||
params: {
|
||
password: string;
|
||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||
},
|
||
context: BRC<ED>
|
||
) {
|
||
const { password } = params;
|
||
const userId = context.getCurrentUserId();
|
||
|
||
const [user] = await context.select('user', {
|
||
data: {},
|
||
filter: {
|
||
id: userId,
|
||
$or: [
|
||
{
|
||
password,
|
||
},
|
||
{
|
||
passwordSha1: encryptPasswordSha1(password),
|
||
},
|
||
],
|
||
}
|
||
}, {});
|
||
|
||
if (!user) {
|
||
throw new OakUserException('error::user.passwordUnmatch');
|
||
}
|
||
|
||
await context.operate('user', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
verifyPasswordAt: Date.now(),
|
||
},
|
||
filter: {
|
||
id: userId!,
|
||
}
|
||
}, {});
|
||
}
|
||
|
||
export async function loginByAccount<ED extends EntityDict>(
|
||
params: {
|
||
account: string;
|
||
password: string;
|
||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
const { account, password, env } = params;
|
||
|
||
let needUpdatePassword = false;
|
||
const loginLogic = async () => {
|
||
const systemId = context.getSystemId();
|
||
const applicationId = context.getApplicationId();
|
||
assert(password);
|
||
assert(account);
|
||
const accountType = isEmail(account) ? 'email' : (isMobile(account) ? 'mobile' : 'loginName');
|
||
if (accountType === 'email') {
|
||
|
||
const { config, emailConfig } = await 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 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,
|
||
}
|
||
},
|
||
{
|
||
$or: [
|
||
{
|
||
password,
|
||
},
|
||
{
|
||
passwordSha1: encryptPasswordSha1(password),
|
||
},
|
||
],
|
||
}
|
||
],
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
switch (result.length) {
|
||
case 0: {
|
||
throw new 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<ED>(
|
||
userId as string,
|
||
context
|
||
);
|
||
if (exception) {
|
||
throw exception;
|
||
}
|
||
}
|
||
return await setupEmail<ED>(account, env, context);
|
||
} else {
|
||
throw new OakUserException('error::user.emailUnexists');
|
||
}
|
||
} else {
|
||
throw new OakUserException('error::user.loginWayDisabled');
|
||
}
|
||
}
|
||
default: {
|
||
throw new 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 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,
|
||
}
|
||
},
|
||
{
|
||
$or: [
|
||
{
|
||
password,
|
||
},
|
||
{
|
||
passwordSha1: encryptPasswordSha1(password),
|
||
},
|
||
],
|
||
}
|
||
],
|
||
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
switch (result.length) {
|
||
case 0: {
|
||
throw new 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<ED>(
|
||
userId as string,
|
||
context
|
||
);
|
||
if (exception) {
|
||
throw exception;
|
||
}
|
||
}
|
||
return await setupMobile<ED>(account, env, context);
|
||
} else {
|
||
throw new OakUserException('手机号未注册');
|
||
}
|
||
} else {
|
||
throw new OakUserException('暂不支持手机号登录');
|
||
}
|
||
}
|
||
default: {
|
||
throw new 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 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,
|
||
}
|
||
},
|
||
{
|
||
$or: [
|
||
{
|
||
password,
|
||
},
|
||
{
|
||
passwordSha1: encryptPasswordSha1(password),
|
||
},
|
||
],
|
||
}
|
||
],
|
||
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
switch (result.length) {
|
||
case 0: {
|
||
throw new 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<ED>(
|
||
userId as string,
|
||
context
|
||
);
|
||
if (exception) {
|
||
throw exception;
|
||
}
|
||
}
|
||
return await setupLoginName<ED>(account, env, context);
|
||
} else {
|
||
throw new OakUserException('账号不存在');
|
||
}
|
||
}
|
||
default: {
|
||
throw new 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 tokenValue = await loginLogic();
|
||
const [tokenInfo] = await loadTokenInfo<ED>(tokenValue, context);
|
||
const { userId } = tokenInfo;
|
||
|
||
await context.operate('user', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: needUpdatePassword ? {
|
||
password,
|
||
passwordSha1: encryptPasswordSha1(password),
|
||
verifyPasswordAt: Date.now(),
|
||
} : {
|
||
verifyPasswordAt: Date.now(),
|
||
},
|
||
filter: {
|
||
id: userId!,
|
||
}
|
||
}, {});
|
||
closeRootMode();
|
||
|
||
return tokenValue;
|
||
}
|
||
|
||
export async function loginByEmail<ED extends EntityDict>(
|
||
params: {
|
||
email: string;
|
||
captcha: string;
|
||
disableRegister?: boolean;
|
||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
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 OakUserException('验证码已经过期');
|
||
}
|
||
|
||
// 到这里说明验证码已经通过,可以让验证码失效
|
||
await context.operate(
|
||
'captcha',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captchaRow.id!
|
||
}
|
||
},
|
||
{}
|
||
);
|
||
return await setupEmail<ED>(email, env, context);
|
||
} else {
|
||
throw new OakUserException('验证码无效');
|
||
}
|
||
};
|
||
const closeRootMode = context.openRootMode();
|
||
|
||
const { config, emailConfig } = await 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 OakUserException('账号不存在');
|
||
}
|
||
}
|
||
const tokenValue = await loginLogic();
|
||
await loadTokenInfo<ED>(tokenValue, context);
|
||
closeRootMode();
|
||
|
||
return tokenValue;
|
||
}
|
||
|
||
export async function bindByMobile<ED extends EntityDict>(
|
||
params: {
|
||
mobile: string;
|
||
captcha: string;
|
||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||
},
|
||
context: BRC<ED>
|
||
) {
|
||
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 OakUserException('验证码已经过期');
|
||
}
|
||
// 到这里说明验证码已经通过
|
||
await context.operate(
|
||
'captcha',
|
||
{
|
||
id: await 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 OakUserException('已绑定该手机号,无需重复绑定');
|
||
}
|
||
//更新mobile
|
||
await context.operate(
|
||
'mobile',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
mobile,
|
||
},
|
||
filter: {
|
||
id: boundMobile.id!,
|
||
},
|
||
},
|
||
{}
|
||
);
|
||
} else {
|
||
//创建mobile
|
||
await context.operate(
|
||
'mobile',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await generateNewIdAsync(),
|
||
mobile,
|
||
userId,
|
||
},
|
||
},
|
||
{}
|
||
);
|
||
}
|
||
} else {
|
||
throw new 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 OakUserException('该手机号已绑定其他用户,请检查');
|
||
}
|
||
await bindLogic();
|
||
closeRootMode();
|
||
}
|
||
|
||
export async function bindByEmail<ED extends EntityDict>(
|
||
params: {
|
||
email: string;
|
||
captcha: string;
|
||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||
},
|
||
context: BRC<ED>
|
||
) {
|
||
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 OakUserException('验证码已经过期');
|
||
}
|
||
|
||
// 到这里说明验证码已经通过,可以让验证码失效
|
||
await context.operate(
|
||
'captcha',
|
||
{
|
||
id: await 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 OakUserException('已绑定该邮箱,无需重复绑定');
|
||
}
|
||
//更新email
|
||
await context.operate(
|
||
'email',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
email,
|
||
},
|
||
filter: {
|
||
id: boundEmail.id!,
|
||
},
|
||
},
|
||
{}
|
||
);
|
||
} else {
|
||
//创建email
|
||
await context.operate(
|
||
'email',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await generateNewIdAsync(),
|
||
email,
|
||
userId,
|
||
},
|
||
},
|
||
{}
|
||
);
|
||
}
|
||
} else {
|
||
throw new OakUserException('验证码无效');
|
||
}
|
||
};
|
||
|
||
const closeRootMode = context.openRootMode();
|
||
|
||
const { config, emailConfig } = await 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 OakUserException('该邮箱已绑定其他用户,请检查');
|
||
}
|
||
await bindLogic();
|
||
closeRootMode();
|
||
}
|
||
|
||
async function setupLoginName<ED extends EntityDict>(name: string, env: WebEnv | WechatMpEnv | NativeEnv, context: BRC<ED>) {
|
||
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 }
|
||
);
|
||
assert(result2.length === 1);
|
||
const [loginNameRow] = result2;
|
||
const { user } = loginNameRow;
|
||
const { userState, ref } = user!;
|
||
if (userState === 'merged') {
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'loginName',
|
||
loginNameRow.id,
|
||
undefined,
|
||
ref as Partial<ED['user']['Schema']>
|
||
);
|
||
}
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'loginName',
|
||
loginNameRow.id,
|
||
undefined,
|
||
user as Partial<ED['user']['Schema']>
|
||
);
|
||
}
|
||
|
||
async function setupEmail<ED extends EntityDict>(email: string, env: WebEnv | WechatMpEnv | NativeEnv, context: BRC<ED>) {
|
||
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) {
|
||
// 此邮箱已经存在
|
||
assert(result2.length === 1);
|
||
const [emailRow] = result2;
|
||
const { user } = emailRow;
|
||
const { userState, ref } = user!;
|
||
if (userState === 'merged') {
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'email',
|
||
emailRow.id,
|
||
undefined,
|
||
ref as Partial<ED['user']['Schema']>
|
||
);
|
||
}
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'email',
|
||
emailRow.id,
|
||
undefined,
|
||
user as Partial<ED['user']['Schema']>
|
||
);
|
||
} else {
|
||
//此邮箱不存在
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'email',
|
||
undefined,
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
email,
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
async function setUserInfoFromWechat<ED extends EntityDict>(
|
||
user: Partial<ED['user']['Schema']>,
|
||
userInfo: {
|
||
nickname?: string;
|
||
avatar?: string;
|
||
gender?: 'male' | 'female';
|
||
},
|
||
context: BRC<ED>
|
||
) {
|
||
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 ||
|
||
composeFileUrl<ED>(application as EntityDict['application']['Schema'], extraFile$entity![0] as EntityDict['extraFile']['Schema']) !== avatar)
|
||
) {
|
||
// 需要更新新的avatar extra file
|
||
const extraFileOperations: ExtraFileOperation['data'][] = [
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: Object.assign({
|
||
id: await generateNewIdAsync(),
|
||
tag1: 'avatar',
|
||
entity: 'user',
|
||
entityId: user.id,
|
||
objectId: await generateNewIdAsync(),
|
||
origin: 'unknown',
|
||
extra1: avatar,
|
||
type: 'image',
|
||
filename: '',
|
||
bucket: '',
|
||
applicationId: applicationId!,
|
||
}),
|
||
},
|
||
];
|
||
if (extraFile$entity!.length > 0) {
|
||
extraFileOperations.push({
|
||
id: await 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 generateNewIdAsync(),
|
||
action: 'update',
|
||
data: updateData,
|
||
filter: {
|
||
id: user.id!,
|
||
},
|
||
},
|
||
{}
|
||
);
|
||
}
|
||
}
|
||
|
||
async function tryRefreshWechatPublicUserInfo<ED extends EntityDict>(wechatUserId: string, context: BRC<ED>) {
|
||
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!;
|
||
|
||
assert(type !== 'wechatMp' && type !== 'native');
|
||
if (type === 'web') {
|
||
return;
|
||
}
|
||
let appId: string, appSecret: string;
|
||
const config2 = config as WechatPublicConfig;
|
||
appId = config2.appId;
|
||
appSecret = config2.appSecret;
|
||
|
||
const wechatInstance = WechatSDK.getInstance(
|
||
appId!,
|
||
type!,
|
||
appSecret!
|
||
) as WechatPublicInstance;
|
||
|
||
let {
|
||
accessToken,
|
||
refreshToken,
|
||
atExpiredAt,
|
||
rtExpiredAt,
|
||
scope,
|
||
openId,
|
||
user,
|
||
} = wechatUser;
|
||
const now = Date.now();
|
||
assert(scope!.toLowerCase().includes('userinfo'));
|
||
if ((rtExpiredAt as number) < now) {
|
||
// refreshToken过期,直接返回未登录异常,使用户去重新登录
|
||
throw new OakUnloggedInException();
|
||
}
|
||
if ((atExpiredAt as number) < now) {
|
||
// 刷新accessToken
|
||
const {
|
||
accessToken: at2,
|
||
atExpiredAt: ate2,
|
||
scope: s2,
|
||
} = await wechatInstance.refreshUserAccessToken(refreshToken!);
|
||
await context.operate(
|
||
'wechatUser',
|
||
{
|
||
id: await 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<ED>(
|
||
user!,
|
||
{ nickname, gender: gender as 'male', avatar },
|
||
context
|
||
);
|
||
}
|
||
|
||
export async function refreshWechatPublicUserInfo<ED extends EntityDict>({ }, context: BRC<ED>) {
|
||
const tokenValue = context.getTokenValue();
|
||
const [token] = await context.select(
|
||
'token',
|
||
{
|
||
data: {
|
||
id: 1,
|
||
entity: 1,
|
||
entityId: 1,
|
||
},
|
||
filter: {
|
||
id: tokenValue,
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
assert(token.entity === 'wechatUser');
|
||
assert(token.entityId);
|
||
return await tryRefreshWechatPublicUserInfo<ED>(
|
||
token.entityId,
|
||
context
|
||
);
|
||
}
|
||
|
||
// 用户在微信端授权登录后,在web端触发该方法
|
||
export async function loginByWechat<ED extends EntityDict>(
|
||
params: {
|
||
wechatLoginId: string;
|
||
env: WebEnv | WechatMpEnv;
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
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<ED>(
|
||
env,
|
||
context,
|
||
'wechatLogin',
|
||
wechatLoginId,
|
||
undefined,
|
||
wechatLoginData.user!
|
||
);
|
||
await loadTokenInfo<ED>(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
|
||
async function loginFromWechatEnv<ED extends EntityDict>(
|
||
code: string,
|
||
env: WebEnv | WechatMpEnv | NativeEnv,
|
||
context: BRC<ED>,
|
||
wechatLoginId?: string
|
||
): Promise<string> {
|
||
const application = context.getApplication();
|
||
const { type, config, systemId } = application!;
|
||
|
||
let appId: string, appSecret: string;
|
||
if (type === 'wechatPublic') {
|
||
const config2 = config as WechatPublicConfig;
|
||
appId = config2.appId;
|
||
appSecret = config2.appSecret;
|
||
} else if (type === 'wechatMp') {
|
||
const config2 = config as WechatMpConfig;
|
||
appId = config2.appId;
|
||
appSecret = config2.appSecret;
|
||
} else if (type === 'native') {
|
||
const config2 = config as NativeConfig;
|
||
assert(config2.wechatNative);
|
||
appId = config2.wechatNative.appId;
|
||
appSecret = config2.wechatNative.appSecret;
|
||
} else {
|
||
assert(type === 'web');
|
||
const config2 = config as WebConfig;
|
||
assert(config2.wechat);
|
||
appId = config2.wechat.appId;
|
||
appSecret = config2.wechat.appSecret;
|
||
}
|
||
const wechatInstance = WechatSDK.getInstance(
|
||
appId!,
|
||
type!,
|
||
appSecret!
|
||
) as WechatPublicInstance;
|
||
|
||
const { isSnapshotUser, openId, unionId, ...wechatUserData } =
|
||
await wechatInstance.code2Session(code);
|
||
if (isSnapshotUser) {
|
||
throw new OakUserException('请使用完整服务后再进行登录操作');
|
||
}
|
||
|
||
const OriginMap: Record<string, 'mp' | 'public' | 'web' | 'native'> = {
|
||
web: 'web',
|
||
wechatPublic: 'public',
|
||
wechatMp: 'mp',
|
||
native: 'native',
|
||
};
|
||
|
||
const createWechatUserAndReturnTokenId = async (
|
||
user?: EntityDict['user']['Schema'],
|
||
wechatLoginId?: string,
|
||
) => {
|
||
const wechatUserCreateData: CreateWechatUser = {
|
||
id: await generateNewIdAsync(),
|
||
unionId,
|
||
origin: OriginMap[type],
|
||
openId,
|
||
applicationId: application!.id!,
|
||
...wechatUserData,
|
||
};
|
||
|
||
let tokenValue;
|
||
if (wechatLoginId) {
|
||
tokenValue = await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'wechatLogin',
|
||
wechatLoginId,
|
||
undefined,
|
||
user
|
||
);
|
||
} else {
|
||
tokenValue = await setUpTokenAndUser<ED>(
|
||
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: UpdateWechatLoginData) => {
|
||
await context.operate(
|
||
'wechatLogin',
|
||
{
|
||
id: await 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,
|
||
}
|
||
);
|
||
// 已绑定
|
||
assert(!wechatUserLogin, '登录者已经绑定微信公众号');
|
||
// 未绑定的情况,就要先看扫码者是否绑定了公众号
|
||
// 扫码者已绑定, 将扫码者的userId替换成登录者的userId
|
||
if (wechatUser) {
|
||
await context.operate(
|
||
'wechatUser',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
userId: wechatLoginData.userId!,
|
||
},
|
||
filter: {
|
||
id: wechatUser.id,
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
const tokenValue = await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'wechatUser',
|
||
wechatUser.id,
|
||
undefined,
|
||
(wechatUserLogin as EntityDict['wechatUser']['Schema'])
|
||
.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 as EntityDict['user']['Schema'];
|
||
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 as EntityDict['user']['Schema'];
|
||
}
|
||
const tokenValue = await setUpTokenAndUser<ED>(
|
||
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 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 generateNewIdAsync();
|
||
const userData = {
|
||
id: userId,
|
||
userState: 'normal',
|
||
};
|
||
await context.operate(
|
||
'user',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: userData,
|
||
},
|
||
{}
|
||
);
|
||
const { tokenValue, wechatUserId } = await createWechatUserAndReturnTokenId(
|
||
userData as EntityDict['user']['Schema'],
|
||
wechatLoginId,
|
||
);
|
||
await updateWechatLogin({ userId, wechatUserId, successed: true });
|
||
return tokenValue;
|
||
}
|
||
}
|
||
} else {
|
||
if (wechatUser) {
|
||
// 微信公众号关注后,会创建一个没有userId的wechatUser
|
||
let user2 = wechatUser.user as EntityDict['user']['Schema'];
|
||
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 as EntityDict['user']['Schema'];
|
||
}
|
||
const tokenValue = await setUpTokenAndUser<ED>(
|
||
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 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: CreateWechatUser = {
|
||
id: await generateNewIdAsync(),
|
||
unionId,
|
||
origin: OriginMap[type],
|
||
openId,
|
||
applicationId: application!.id!,
|
||
...wechatUserData,
|
||
};
|
||
const tokenValue = await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
'wechatUser',
|
||
undefined,
|
||
wechatUserCreateData,
|
||
wechatUser3.user!
|
||
);
|
||
|
||
if (!wechatUser3.userId) {
|
||
// 这里顺便帮其它wechatUser数据也补上相应的userId
|
||
await context.operate(
|
||
'wechatUser',
|
||
{
|
||
id: await 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
|
||
*/
|
||
export async function loginWechatNative<ED extends EntityDict>(
|
||
{
|
||
code,
|
||
env,
|
||
}: {
|
||
code: string;
|
||
env: NativeEnv;
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
const closeRootMode = context.openRootMode();
|
||
const tokenValue = await loginFromWechatEnv<ED>(code, env, context);
|
||
await loadTokenInfo<ED>(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
|
||
/**
|
||
* 公众号授权登录
|
||
* @param param0
|
||
* @param context
|
||
*/
|
||
export async function loginWechat<ED extends EntityDict>(
|
||
{
|
||
code,
|
||
env,
|
||
wechatLoginId,
|
||
}: {
|
||
code: string;
|
||
env: WebEnv;
|
||
wechatLoginId?: string;
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
const closeRootMode = context.openRootMode();
|
||
const tokenValue = await loginFromWechatEnv<ED>(
|
||
code,
|
||
env,
|
||
context,
|
||
wechatLoginId
|
||
);
|
||
const [tokenInfo] = await loadTokenInfo<ED>(tokenValue, context);
|
||
assert(tokenInfo.entity === 'wechatUser' || tokenInfo.entity === 'wechatLogin');
|
||
await context.setTokenValue(tokenValue);
|
||
if (tokenInfo.entity === 'wechatUser') {
|
||
await tryRefreshWechatPublicUserInfo<ED>(tokenInfo.entityId!, context);
|
||
} else if (tokenInfo.entity === 'wechatLogin') {
|
||
const [wechatLogin] = await context.select('wechatLogin',
|
||
{
|
||
data: {
|
||
id: 1,
|
||
wechatUserId: 1,
|
||
},
|
||
filter: {
|
||
id: tokenInfo.entityId,
|
||
},
|
||
},
|
||
{}
|
||
)
|
||
assert(wechatLogin?.wechatUserId);
|
||
await tryRefreshWechatPublicUserInfo<ED>(wechatLogin.wechatUserId, context);
|
||
}
|
||
closeRootMode();
|
||
|
||
return tokenValue;
|
||
}
|
||
|
||
/**
|
||
* 小程序授权登录
|
||
* @param param0
|
||
* @param context
|
||
* @returns
|
||
*/
|
||
export async function loginWechatMp<ED extends EntityDict>(
|
||
{
|
||
code,
|
||
env,
|
||
}: {
|
||
code: string;
|
||
env: WechatMpEnv;
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
const closeRootMode = context.openRootMode();
|
||
const tokenValue = await loginFromWechatEnv<ED>(code, env, context);
|
||
await loadTokenInfo<ED>(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
|
||
/**
|
||
* 同步从wx.getUserProfile拿到的用户信息
|
||
* @param param0
|
||
* @param context
|
||
*/
|
||
export async function syncUserInfoWechatMp<ED extends EntityDict>(
|
||
{
|
||
nickname,
|
||
avatarUrl,
|
||
encryptedData,
|
||
iv,
|
||
signature,
|
||
}: {
|
||
nickname: string;
|
||
avatarUrl: string;
|
||
encryptedData: string;
|
||
iv: string;
|
||
signature: string;
|
||
},
|
||
context: BRC<ED>
|
||
) {
|
||
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 as Partial<
|
||
EntityDict['application']['Schema']
|
||
>;
|
||
|
||
assert(type === 'wechatMp' || config2!.type === 'wechatMp');
|
||
const { appId, appSecret } = config2 as WechatMpConfig;
|
||
const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
|
||
const result = wechatInstance.decryptData(
|
||
sessionKey as string,
|
||
encryptedData,
|
||
iv,
|
||
signature
|
||
);
|
||
// 实测发现解密出来的和userInfo完全一致……
|
||
await setUserInfoFromWechat<ED>(
|
||
user!,
|
||
{ nickname, avatar: avatarUrl },
|
||
context
|
||
);
|
||
}
|
||
|
||
export async function sendCaptchaByMobile<ED extends EntityDict>(
|
||
{
|
||
mobile,
|
||
env,
|
||
type: captchaType,
|
||
}: {
|
||
mobile: string;
|
||
env: WechatMpEnv | WebEnv | NativeEnv;
|
||
type: 'login' | 'changePassword' | 'confirm';
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
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,
|
||
}
|
||
);
|
||
assert(applicationPassport?.passport);
|
||
const config = applicationPassport.passport.config as SmsConfig;
|
||
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 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: string;
|
||
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 generateNewIdAsync();
|
||
await context.operate(
|
||
'captcha',
|
||
{
|
||
id: await 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;
|
||
}
|
||
let code = captcha?.code!;
|
||
if (captcha) {
|
||
const captchaDuration = process.env.NODE_ENV === 'development' ? 10 * 1000 : 60 * 1000;
|
||
if (now - (captcha.$$createAt$$! as number) < captchaDuration) {
|
||
closeRootMode();
|
||
throw new OakUserException('您的操作太迅捷啦,请稍候再点吧');
|
||
}
|
||
await context.operate(
|
||
'captcha',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captcha.id!
|
||
}
|
||
},
|
||
{}
|
||
);
|
||
code = await getCode();
|
||
} else {
|
||
code = await getCode();
|
||
}
|
||
if (mockSend) {
|
||
closeRootMode();
|
||
return `验证码[${code}]已创建`;
|
||
} else {
|
||
assert(origin, '必须设置短信渠道');
|
||
//发送短信
|
||
const result = await sendSms<ED>(
|
||
{
|
||
origin: origin,
|
||
templateName: codeTemplateName,
|
||
mobile,
|
||
templateParam: { code, duration: duration.toString() },
|
||
},
|
||
context
|
||
);
|
||
closeRootMode();
|
||
if (result.success) {
|
||
return '验证码已发送';
|
||
}
|
||
console.error('短信发送失败,原因:\n', result?.res);
|
||
return '验证码发送失败';
|
||
}
|
||
}
|
||
|
||
export async function sendCaptchaByEmail<ED extends EntityDict>(
|
||
{
|
||
email,
|
||
env,
|
||
type: captchaType,
|
||
}: {
|
||
email: string;
|
||
env: WechatMpEnv | WebEnv | NativeEnv;
|
||
type: 'login' | 'changePassword' | 'confirm';
|
||
},
|
||
context: BRC<ED>
|
||
): Promise<string> {
|
||
const { type } = env;
|
||
|
||
let visitorId = email;
|
||
if (type === 'web' || type === 'native') {
|
||
visitorId = env.visitorId;
|
||
}
|
||
|
||
const { config, emailConfig } = await getAndCheckPassportByEmail(context, email);
|
||
const duration = config.codeDuration || 5;
|
||
const digit = config.digit || 4;
|
||
const mockSend = config.mockSend;
|
||
|
||
let emailOptions: 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 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: string;
|
||
// code = Math.floor(Math.random() * 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 generateNewIdAsync();
|
||
const applicationId = context.getApplication()?.id!;
|
||
await context.operate(
|
||
'captcha',
|
||
{
|
||
id: await 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;
|
||
}
|
||
|
||
let code = captcha?.code!;
|
||
if (captcha) {
|
||
const captchaDuration = process.env.NODE_ENV === 'development' ? 10 * 1000 : 60 * 1000;
|
||
if (now - (captcha.$$createAt$$! as number) < captchaDuration) {
|
||
closeRootMode();
|
||
throw new OakUserException('您的操作太迅捷啦,请稍候再点吧');
|
||
}
|
||
await context.operate(
|
||
'captcha',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
expired: true
|
||
},
|
||
filter: {
|
||
id: captcha.id!
|
||
}
|
||
},
|
||
{}
|
||
);
|
||
code = await getCode();
|
||
} else {
|
||
code = await getCode();
|
||
}
|
||
if (mockSend) {
|
||
closeRootMode();
|
||
return `验证码[${code}]已创建`;
|
||
} else {
|
||
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 sendEmail(emailOptions, context);
|
||
closeRootMode();
|
||
if (result.success) {
|
||
return '验证码已发送';
|
||
}
|
||
console.error('邮件发送失败,原因:\n', result?.error);
|
||
return '验证码发送失败';
|
||
}
|
||
}
|
||
|
||
export async function switchTo<ED extends EntityDict>({ userId }: { userId: string }, context: BRC<ED>) {
|
||
const reallyRoot = context.isReallyRoot();
|
||
if (!reallyRoot) {
|
||
throw new OakOperationUnpermittedException('user', { id: 'switchTo', action: 'switch', data: {}, filter: { id: userId } });
|
||
}
|
||
const currentUserId = context.getCurrentUserId();
|
||
if (currentUserId === userId) {
|
||
throw new OakPreConditionUnsetException('您已经是当前用户');
|
||
}
|
||
|
||
const token = context.getToken()!;
|
||
await context.operate(
|
||
'token',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'update',
|
||
data: {
|
||
userId,
|
||
},
|
||
filter: {
|
||
id: token.id,
|
||
},
|
||
},
|
||
{}
|
||
);
|
||
}
|
||
|
||
export async function getWechatMpUserPhoneNumber<ED extends EntityDict>(
|
||
{ code, env }: { code: string; env: WechatMpEnv },
|
||
context: BRC<ED>
|
||
) {
|
||
const application = context.getApplication();
|
||
const { type, config, systemId } = application!;
|
||
assert(type === 'wechatMp' && config!.type === 'wechatMp');
|
||
const config2 = config as WechatMpConfig;
|
||
const { appId, appSecret } = config2;
|
||
const wechatInstance = WechatSDK.getInstance(
|
||
appId,
|
||
'wechatMp',
|
||
appSecret
|
||
) as WechatMpInstance;
|
||
const result = await wechatInstance.getUserPhoneNumber(code);
|
||
const closeRootMode = context.openRootMode();
|
||
//获取 绑定的手机号码
|
||
const phoneNumber = result?.phoneNumber;
|
||
const reuslt = await setupMobile<ED>(phoneNumber, env, context);
|
||
closeRootMode()
|
||
return reuslt;
|
||
|
||
}
|
||
|
||
export async function logout<ED extends EntityDict>(params: { tokenValue: string }, context: BRC<ED>) {
|
||
const { tokenValue } = params;
|
||
if (tokenValue) {
|
||
const closeRootMode = context.openRootMode();
|
||
|
||
try {
|
||
await context.operate(
|
||
'token',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'disable',
|
||
data: {},
|
||
filter: {
|
||
value: tokenValue,
|
||
ableState: 'enabled',
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
} catch (err) {
|
||
closeRootMode();
|
||
throw err;
|
||
}
|
||
closeRootMode();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建一个当前parasite上的token
|
||
* @param params
|
||
* @param context
|
||
* @returns
|
||
*/
|
||
export async function wakeupParasite<ED extends EntityDict>(
|
||
params: {
|
||
id: string;
|
||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||
},
|
||
context: BRC<ED>
|
||
) {
|
||
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 OakRowInconsistencyException<EntityDict>(
|
||
'数据已经过期'
|
||
);
|
||
e.addData('parasite', [parasite], context.getSchema());
|
||
throw e;
|
||
}
|
||
if (parasite.user?.userState !== 'shadow') {
|
||
throw new OakUserException('此用户已经登录过系统,不允许借用身份');
|
||
}
|
||
|
||
const closeRootMode = context.openRootMode();
|
||
if (!parasite.multiple) {
|
||
await context.operate(
|
||
'parasite',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'wakeup',
|
||
data: {
|
||
expired: true,
|
||
},
|
||
filter: {
|
||
id,
|
||
},
|
||
},
|
||
{ dontCollect: true }
|
||
);
|
||
}
|
||
|
||
const tokenValue = await generateNewIdAsync();
|
||
await context.operate(
|
||
'token',
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await 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<ED>(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
}
|
||
|
||
/**
|
||
* todo 检查登录环境一致性,同一个token不能跨越不同设备
|
||
* @param env1
|
||
* @param env2
|
||
* @returns
|
||
*/
|
||
function checkTokenEnvConsistency(env1: WebEnv | WechatMpEnv | NativeEnv, env2: WebEnv | WechatMpEnv | NativeEnv) {
|
||
if (env1.type !== env2.type) {
|
||
return false;
|
||
}
|
||
|
||
switch (env1.type) {
|
||
case 'web': {
|
||
return env1.visitorId === (<WebEnv>env2).visitorId;
|
||
}
|
||
case 'wechatMp': {
|
||
return true;
|
||
}
|
||
case 'native': {
|
||
return env1.visitorId === (<NativeEnv>env2).visitorId;
|
||
}
|
||
default: {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
export async function refreshToken<ED extends EntityDict>(
|
||
params: {
|
||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||
tokenValue: string;
|
||
applicationId: string;
|
||
},
|
||
context: BRC<ED>
|
||
) {
|
||
const { env, tokenValue, applicationId: appId } = params;
|
||
const closeRootMode = context.openRootMode();
|
||
let [token] = await context.select(
|
||
'token',
|
||
{
|
||
data: Object.assign({
|
||
env: 1,
|
||
...tokenProjection,
|
||
}),
|
||
filter: {
|
||
$or: [
|
||
{
|
||
value: tokenValue,
|
||
},
|
||
{
|
||
oldValue: tokenValue,
|
||
// refreshedAt: {
|
||
// $gte: Date.now() - 300 * 1000,
|
||
// },
|
||
},
|
||
],
|
||
},
|
||
},
|
||
{}
|
||
);
|
||
|
||
assert(token, `tokenValue: ${tokenValue}`);
|
||
const now = Date.now();
|
||
if (!checkTokenEnvConsistency(env, token.env as WebEnv)) {
|
||
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 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) {
|
||
assert(appId, '从上下文中获取applicationId为空');
|
||
|
||
const [application] = await context.select('application', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
id: appId,
|
||
},
|
||
}, {
|
||
dontCollect: true
|
||
});
|
||
assert(application, 'application找不到');
|
||
await context.operate('token', {
|
||
id: await 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 as number) > interval) {
|
||
const newValue = await 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 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<ED extends EntityDict>(mpToken: string, env: WebEnv, context: BRC<ED>) {
|
||
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 }
|
||
);
|
||
assert(token);
|
||
const { user } = token;
|
||
return await setUpTokenAndUser<ED>(
|
||
env,
|
||
context,
|
||
token.entity!,
|
||
token.entityId,
|
||
undefined,
|
||
user as Partial<ED['user']['Schema']>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 小程序web-view处理token
|
||
* @param mpToken
|
||
* @param env
|
||
* @param context
|
||
* @returns
|
||
*/
|
||
export async function loginWebByMpToken<ED extends EntityDict>(params: { mpToken: string, env: WebEnv, }, context: BRC<ED>): Promise<string> {
|
||
const { mpToken, env } = params;
|
||
const closeRootMode = context.openRootMode();
|
||
const tokenValue = await setupWechatMp<ED>(mpToken, env, context);
|
||
await loadTokenInfo<ED>(tokenValue, context);
|
||
closeRootMode();
|
||
return tokenValue;
|
||
} |