diff --git a/es/aspects/AspectDict.d.ts b/es/aspects/AspectDict.d.ts index 828e99607..267fbb9c9 100644 --- a/es/aspects/AspectDict.d.ts +++ b/es/aspects/AspectDict.d.ts @@ -16,6 +16,16 @@ export type AspectDict = { code: string; env: WechatMpEnv; }, context: BackendRuntimeContext) => Promise; + bindByMobile: (params: { + mobile: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; + }, context: BackendRuntimeContext) => Promise; + bindByEmail: (params: { + email: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; + }, context: BackendRuntimeContext) => Promise; loginByMobile: (params: { mobile: string; captcha: string; diff --git a/es/aspects/index.d.ts b/es/aspects/index.d.ts index 7d1f9fd00..fe288bb6a 100644 --- a/es/aspects/index.d.ts +++ b/es/aspects/index.d.ts @@ -1,4 +1,4 @@ -import { loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken } from './token'; +import { bindByEmail, bindByMobile, loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken } from './token'; import { getInfoByUrl } from './extraFile'; import { getApplication, signatureJsSDK, uploadWechatMedia, batchGetArticle, getArticle, batchGetMaterialList, getMaterial, deleteMaterial } from './application'; import { updateConfig, updateApplicationConfig, updateStyle } from './config'; @@ -15,6 +15,8 @@ import { getTagUsers, batchtagging, batchuntagging, getUserTags, getUsers, taggi import { wechatMpJump } from './wechatMpJump'; import { getApplicationPassports, removeApplicationPassportsByPIds } from './applicationPassport'; declare const aspectDict: { + bindByEmail: typeof bindByEmail; + bindByMobile: typeof bindByMobile; loginByAccount: typeof loginByAccount; loginByEmail: typeof loginByEmail; mergeUser: typeof mergeUser; diff --git a/es/aspects/index.js b/es/aspects/index.js index 2b19c2b84..1db0205c0 100644 --- a/es/aspects/index.js +++ b/es/aspects/index.js @@ -1,4 +1,4 @@ -import { loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken, } from './token'; +import { bindByEmail, bindByMobile, loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken, } from './token'; import { getInfoByUrl } from './extraFile'; import { getApplication, signatureJsSDK, uploadWechatMedia, batchGetArticle, getArticle, batchGetMaterialList, getMaterial, deleteMaterial, } from './application'; import { updateConfig, updateApplicationConfig, updateStyle } from './config'; @@ -15,6 +15,8 @@ import { getTagUsers, batchtagging, batchuntagging, getUserTags, getUsers, taggi import { wechatMpJump, } from './wechatMpJump'; import { getApplicationPassports, removeApplicationPassportsByPIds } from './applicationPassport'; const aspectDict = { + bindByEmail, + bindByMobile, loginByAccount, loginByEmail, mergeUser, diff --git a/es/aspects/token.d.ts b/es/aspects/token.d.ts index 5bd0aa97f..c357c48c0 100644 --- a/es/aspects/token.d.ts +++ b/es/aspects/token.d.ts @@ -18,6 +18,16 @@ export declare function loginByEmail(params: { disableRegister?: boolean; env: WebEnv | WechatMpEnv | NativeEnv; }, context: BRC): Promise; +export declare function bindByMobile(params: { + mobile: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; +}, context: BRC): Promise; +export declare function bindByEmail(params: { + email: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; +}, context: BRC): Promise; export declare function refreshWechatPublicUserInfo({}: {}, context: BRC): Promise; export declare function loginByWechat(params: { wechatLoginId: string; diff --git a/es/aspects/token.js b/es/aspects/token.js index 39bd8adc5..f45306406 100644 --- a/es/aspects/token.js +++ b/es/aspects/token.js @@ -686,6 +686,208 @@ export async function loginByEmail(params, context) { closeRootMode(); return tokenValue; } +export 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 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 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(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 OakUserException('验证码已经过期'); + } + // 到这里说明验证码已经通过 + //检查当前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 [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(name, env, context) { const result2 = await context.select('loginName', { data: { @@ -1339,40 +1541,40 @@ export async function sendCaptchaByMobile({ mobile, env, type: type2, }, context } const application = context.getApplication(); const { system } = application; - let mockSend = system?.config?.Sms?.mockSend; - let codeTemplateName = system?.config?.Sms?.defaultCodeTemplateName; - let origin = system?.config?.Sms?.defaultOrigin; - let duration = system?.config?.Sms?.defaultCodeDuration || 1; //多少分钟内有效; - let digit = 4; //验证码位数; - if (type2 === 'login') { - const [applicationPassport] = await context.select('applicationPassport', { - data: { + // let mockSend = system?.config?.Sms?.mockSend; + // let codeTemplateName = system?.config?.Sms?.defaultCodeTemplateName; + // let origin = system?.config?.Sms?.defaultOrigin; + // let duration = system?.config?.Sms?.defaultCodeDuration || 1; //多少分钟内有效; + // let digit = 4 //验证码位数; + // if (type2 === 'login') { + const [applicationPassport] = await context.select('applicationPassport', { + data: { + id: 1, + passportId: 1, + passport: { id: 1, - passportId: 1, - passport: { - id: 1, - config: 1, - type: 1, - }, - applicationId: 1, + config: 1, + type: 1, }, - filter: { - applicationId: application?.id, - passport: { - type: 'sms' - }, - } - }, { - dontCollect: true, - }); - assert(applicationPassport?.passport); - const config = applicationPassport.passport.config; - mockSend = config.mockSend; - codeTemplateName = config.templateName; - origin = config.defaultOrigin; - duration = config.codeDuration || 1; - digit = config.digit || 4; - } + applicationId: 1, + }, + filter: { + applicationId: application?.id, + passport: { + type: 'sms' + }, + } + }, { + dontCollect: true, + }); + 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) { diff --git a/lib/aspects/AspectDict.d.ts b/lib/aspects/AspectDict.d.ts index 828e99607..267fbb9c9 100644 --- a/lib/aspects/AspectDict.d.ts +++ b/lib/aspects/AspectDict.d.ts @@ -16,6 +16,16 @@ export type AspectDict = { code: string; env: WechatMpEnv; }, context: BackendRuntimeContext) => Promise; + bindByMobile: (params: { + mobile: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; + }, context: BackendRuntimeContext) => Promise; + bindByEmail: (params: { + email: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; + }, context: BackendRuntimeContext) => Promise; loginByMobile: (params: { mobile: string; captcha: string; diff --git a/lib/aspects/index.d.ts b/lib/aspects/index.d.ts index 7d1f9fd00..fe288bb6a 100644 --- a/lib/aspects/index.d.ts +++ b/lib/aspects/index.d.ts @@ -1,4 +1,4 @@ -import { loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken } from './token'; +import { bindByEmail, bindByMobile, loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken } from './token'; import { getInfoByUrl } from './extraFile'; import { getApplication, signatureJsSDK, uploadWechatMedia, batchGetArticle, getArticle, batchGetMaterialList, getMaterial, deleteMaterial } from './application'; import { updateConfig, updateApplicationConfig, updateStyle } from './config'; @@ -15,6 +15,8 @@ import { getTagUsers, batchtagging, batchuntagging, getUserTags, getUsers, taggi import { wechatMpJump } from './wechatMpJump'; import { getApplicationPassports, removeApplicationPassportsByPIds } from './applicationPassport'; declare const aspectDict: { + bindByEmail: typeof bindByEmail; + bindByMobile: typeof bindByMobile; loginByAccount: typeof loginByAccount; loginByEmail: typeof loginByEmail; mergeUser: typeof mergeUser; diff --git a/lib/aspects/index.js b/lib/aspects/index.js index 85cf49b69..a71c1cf70 100644 --- a/lib/aspects/index.js +++ b/lib/aspects/index.js @@ -17,6 +17,8 @@ const userWechatPublicTag_1 = require("./userWechatPublicTag"); const wechatMpJump_1 = require("./wechatMpJump"); const applicationPassport_1 = require("./applicationPassport"); const aspectDict = { + bindByEmail: token_1.bindByEmail, + bindByMobile: token_1.bindByMobile, loginByAccount: token_1.loginByAccount, loginByEmail: token_1.loginByEmail, mergeUser: user_1.mergeUser, diff --git a/lib/aspects/token.d.ts b/lib/aspects/token.d.ts index 5bd0aa97f..c357c48c0 100644 --- a/lib/aspects/token.d.ts +++ b/lib/aspects/token.d.ts @@ -18,6 +18,16 @@ export declare function loginByEmail(params: { disableRegister?: boolean; env: WebEnv | WechatMpEnv | NativeEnv; }, context: BRC): Promise; +export declare function bindByMobile(params: { + mobile: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; +}, context: BRC): Promise; +export declare function bindByEmail(params: { + email: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; +}, context: BRC): Promise; export declare function refreshWechatPublicUserInfo({}: {}, context: BRC): Promise; export declare function loginByWechat(params: { wechatLoginId: string; diff --git a/lib/aspects/token.js b/lib/aspects/token.js index 3ce825a83..ab83f2cc5 100644 --- a/lib/aspects/token.js +++ b/lib/aspects/token.js @@ -1,6 +1,6 @@ "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.loginByWechat = exports.refreshWechatPublicUserInfo = exports.loginByEmail = exports.loginByAccount = exports.loginByMobile = void 0; +exports.refreshToken = exports.wakeupParasite = exports.logout = exports.getWechatMpUserPhoneNumber = exports.switchTo = exports.sendCaptchaByEmail = exports.sendCaptchaByMobile = exports.syncUserInfoWechatMp = exports.loginWechatMp = exports.loginWechat = exports.loginByWechat = exports.refreshWechatPublicUserInfo = exports.bindByEmail = exports.bindByMobile = exports.loginByEmail = exports.loginByAccount = 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")); @@ -693,6 +693,210 @@ async function loginByEmail(params, context) { 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('验证码已经过期'); + } + // 到这里说明验证码已经通过 + //检查当前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 [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: { @@ -1351,40 +1555,40 @@ async function sendCaptchaByMobile({ mobile, env, type: type2, }, context) { } const application = context.getApplication(); const { system } = application; - let mockSend = system?.config?.Sms?.mockSend; - let codeTemplateName = system?.config?.Sms?.defaultCodeTemplateName; - let origin = system?.config?.Sms?.defaultOrigin; - let duration = system?.config?.Sms?.defaultCodeDuration || 1; //多少分钟内有效; - let digit = 4; //验证码位数; - if (type2 === 'login') { - const [applicationPassport] = await context.select('applicationPassport', { - data: { + // let mockSend = system?.config?.Sms?.mockSend; + // let codeTemplateName = system?.config?.Sms?.defaultCodeTemplateName; + // let origin = system?.config?.Sms?.defaultOrigin; + // let duration = system?.config?.Sms?.defaultCodeDuration || 1; //多少分钟内有效; + // let digit = 4 //验证码位数; + // if (type2 === 'login') { + const [applicationPassport] = await context.select('applicationPassport', { + data: { + id: 1, + passportId: 1, + passport: { id: 1, - passportId: 1, - passport: { - id: 1, - config: 1, - type: 1, - }, - applicationId: 1, + config: 1, + type: 1, }, - filter: { - applicationId: application?.id, - passport: { - type: 'sms' - }, - } - }, { - dontCollect: true, - }); - (0, assert_1.assert)(applicationPassport?.passport); - const config = applicationPassport.passport.config; - mockSend = config.mockSend; - codeTemplateName = config.templateName; - origin = config.defaultOrigin; - duration = config.codeDuration || 1; - digit = config.digit || 4; - } + 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) { diff --git a/lib/features/token.d.ts b/lib/features/token.d.ts index 073c35b81..062c9d711 100644 --- a/lib/features/token.d.ts +++ b/lib/features/token.d.ts @@ -14,6 +14,8 @@ export declare class Token extends Feature { loginByMobile(mobile: string, captcha?: string, disableRegister?: boolean): Promise; loginByEmail(email: string, captcha: string, disableRegister?: boolean): Promise; loginByAccount(account: string, password: string): Promise; + bindByMobile(mobile: string, captcha?: string): Promise; + bindByEmail(email: string, captcha?: string): Promise; loginByWechatInWebEnv(wechatLoginId: string): Promise; loginWechat(code: string, params?: { wechatLoginId?: string; diff --git a/lib/features/token.js b/lib/features/token.js index 36979df8d..2e7925a50 100644 --- a/lib/features/token.js +++ b/lib/features/token.js @@ -105,6 +105,24 @@ class Token extends Feature_1.Feature { await this.storage.save(constants_1.LOCAL_STORAGE_KEYS.token, result); this.publish(); } + async bindByMobile(mobile, captcha) { + const env = await this.environment.getEnv(); + await this.cache.exec('bindByMobile', { + mobile, + captcha, + env, + }, undefined, true); + this.publish(); + } + async bindByEmail(email, captcha) { + const env = await this.environment.getEnv(); + await this.cache.exec('bindByEmail', { + email, + captcha, + env, + }, undefined, true); + this.publish(); + } async loginByWechatInWebEnv(wechatLoginId) { const env = await this.environment.getEnv(); const { result } = await this.cache.exec('loginByWechat', { diff --git a/src/aspects/AspectDict.ts b/src/aspects/AspectDict.ts index feca35a57..74cf1d96b 100644 --- a/src/aspects/AspectDict.ts +++ b/src/aspects/AspectDict.ts @@ -20,6 +20,22 @@ export type AspectDict = { params: { code: string; env: WechatMpEnv }, context: BackendRuntimeContext ) => Promise; + bindByMobile: ( + params: { + mobile: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; + }, + context: BackendRuntimeContext + ) => Promise; + bindByEmail: ( + params: { + email: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; + }, + context: BackendRuntimeContext + ) => Promise; loginByMobile: ( params: { mobile: string; diff --git a/src/aspects/index.ts b/src/aspects/index.ts index b1d28358a..a518f2b30 100644 --- a/src/aspects/index.ts +++ b/src/aspects/index.ts @@ -1,4 +1,6 @@ import { + bindByEmail, + bindByMobile, loginByAccount, loginByEmail, loginByMobile, @@ -66,6 +68,8 @@ import { import { getApplicationPassports, removeApplicationPassportsByPIds } from './applicationPassport'; const aspectDict = { + bindByEmail, + bindByMobile, loginByAccount, loginByEmail, mergeUser, diff --git a/src/aspects/token.ts b/src/aspects/token.ts index 0756d6de5..6298412cb 100644 --- a/src/aspects/token.ts +++ b/src/aspects/token.ts @@ -913,6 +913,268 @@ export async function loginByEmail( return tokenValue; } +export async function bindByMobile( + params: { + mobile: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; + }, + context: BRC +) { + 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('验证码已经过期'); + } + + // 到这里说明验证码已经通过 + //检查当前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( + params: { + email: string; + captcha: string; + env: WebEnv | WechatMpEnv | NativeEnv; + }, + context: BRC +) { + 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('验证码已经过期'); + } + + // 到这里说明验证码已经通过 + //检查当前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 [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(name: string, env: WebEnv | WechatMpEnv | NativeEnv, context: BRC) { const result2 = await context.select( 'loginName', @@ -1869,43 +2131,43 @@ export async function sendCaptchaByMobile( } const application = context.getApplication(); const { system } = application!; - let mockSend = system?.config?.Sms?.mockSend; - let codeTemplateName = system?.config?.Sms?.defaultCodeTemplateName; - let origin = system?.config?.Sms?.defaultOrigin; - let duration = system?.config?.Sms?.defaultCodeDuration || 1; //多少分钟内有效; - let digit = 4 //验证码位数; - if (type2 === 'login') { - const [applicationPassport] = await context.select('applicationPassport', - { - data: { + // let mockSend = system?.config?.Sms?.mockSend; + // let codeTemplateName = system?.config?.Sms?.defaultCodeTemplateName; + // let origin = system?.config?.Sms?.defaultOrigin; + // let duration = system?.config?.Sms?.defaultCodeDuration || 1; //多少分钟内有效; + // let digit = 4 //验证码位数; + // if (type2 === 'login') { + const [applicationPassport] = await context.select('applicationPassport', + { + data: { + id: 1, + passportId: 1, + passport: { id: 1, - passportId: 1, - passport: { - id: 1, - config: 1, - type: 1, - }, - applicationId: 1, + config: 1, + type: 1, }, - filter: { - applicationId: application?.id!, - passport: { - type: 'sms' - }, - } + applicationId: 1, }, - { - dontCollect: true, + filter: { + applicationId: application?.id!, + passport: { + type: 'sms' + }, } - ); - assert(applicationPassport?.passport); - const config = applicationPassport.passport.config as SmsConfig; - mockSend = config.mockSend; - codeTemplateName = config.templateName; - origin = config.defaultOrigin; - duration = config.codeDuration || 1; - digit = config.digit || 4; - } + }, + { + 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) { diff --git a/src/features/token.ts b/src/features/token.ts index eb17eb5c5..0ea9f4668 100644 --- a/src/features/token.ts +++ b/src/features/token.ts @@ -160,6 +160,41 @@ export class Token extends Feature { this.publish(); } + async bindByMobile( + mobile: string, + captcha?: string, + ) { + const env = await this.environment.getEnv(); + await this.cache.exec( + 'bindByMobile', + { + mobile, + captcha, + env, + }, + undefined, + true + ); + this.publish(); + } + + async bindByEmail( + email: string, + captcha?: string, + ) { + const env = await this.environment.getEnv(); + await this.cache.exec( + 'bindByEmail', + { + email, + captcha, + env, + }, + undefined, + true + ); + this.publish(); + } async loginByWechatInWebEnv(wechatLoginId: string) { const env = await this.environment.getEnv();