密码登录支持邮箱
This commit is contained in:
parent
48f1d59a7d
commit
b63361782d
|
|
@ -30,6 +30,15 @@ export type AspectDict<ED extends EntityDict> = {
|
|||
},
|
||||
context: BackendRuntimeContext<ED>
|
||||
) => Promise<string>;
|
||||
loginByAccount: (
|
||||
params: {
|
||||
account: string;
|
||||
password: string;
|
||||
disableRegister?: boolean;
|
||||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||||
},
|
||||
context: BackendRuntimeContext<ED>
|
||||
) => Promise<string>;
|
||||
loginByEmail: (
|
||||
params: {
|
||||
email: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
loginByAccount,
|
||||
loginByEmail,
|
||||
loginByMobile,
|
||||
loginWechat,
|
||||
|
|
@ -65,6 +66,7 @@ import {
|
|||
import { getApplicationPassports, removeApplicationPassportsByPIds } from './applicationPassport';
|
||||
|
||||
const aspectDict = {
|
||||
loginByAccount,
|
||||
loginByEmail,
|
||||
mergeUser,
|
||||
switchTo,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ 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';
|
||||
|
||||
|
||||
async function makeDistinguishException<ED extends EntityDict>(userId: string, context: BRC<ED>, message?: string) {
|
||||
|
|
@ -717,6 +718,189 @@ export async function loginByMobile<ED extends EntityDict>(
|
|||
return tokenValue;
|
||||
}
|
||||
|
||||
export async function loginByAccount<ED extends EntityDict>(
|
||||
params: {
|
||||
account: string;
|
||||
password: string;
|
||||
disableRegister?: boolean;
|
||||
env: WebEnv | WechatMpEnv | NativeEnv;
|
||||
},
|
||||
context: BRC<ED>
|
||||
): Promise<string> {
|
||||
const { account, password, env, disableRegister } = params;
|
||||
|
||||
const loginLogic = async () => {
|
||||
const systemId = context.getSystemId();
|
||||
assert(password);
|
||||
const result = await context.select(
|
||||
'user',
|
||||
{
|
||||
data: {
|
||||
id: 1,
|
||||
mobile$user: {
|
||||
$entity: 'mobile',
|
||||
data: {
|
||||
id: 1,
|
||||
mobile: 1,
|
||||
ableState: 1,
|
||||
},
|
||||
},
|
||||
email$user: {
|
||||
$entity: 'email',
|
||||
data: {
|
||||
id: 1,
|
||||
email: 1,
|
||||
ableState: 1,
|
||||
}
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
$and: [
|
||||
{
|
||||
$or: [
|
||||
{
|
||||
mobile$user: {
|
||||
mobile: account,
|
||||
}
|
||||
},
|
||||
{
|
||||
email$user: {
|
||||
email: account,
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
$or: [
|
||||
{
|
||||
password,
|
||||
},
|
||||
{
|
||||
passwordSha1: encryptPasswordSha1(password),
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
|
||||
},
|
||||
},
|
||||
{
|
||||
dontCollect: true,
|
||||
}
|
||||
);
|
||||
switch (result.length) {
|
||||
case 0: {
|
||||
throw new OakUserException('用户名与密码不匹配');
|
||||
}
|
||||
case 1: {
|
||||
const [userRow] = result;
|
||||
const { mobile$user, email$user, id: userId, } = userRow;
|
||||
if (mobile$user && mobile$user.length > 0) {
|
||||
const ableState = mobile$user[0].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 if (email$user && email$user.length > 0) {
|
||||
const ableState = email$user[0].ableState;
|
||||
if (ableState === 'disabled') {
|
||||
// 虽然密码和邮箱匹配,但邮箱已经禁用了,在可能的情况下提醒用户使用其它方法登录
|
||||
const exception = await tryMakeChangeLoginWay<ED>(
|
||||
userId as string,
|
||||
context
|
||||
);
|
||||
if (exception) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
return await setupEmail<ED>(account, env, context);
|
||||
}
|
||||
return ''
|
||||
}
|
||||
default: {
|
||||
throw new Error(
|
||||
// `手机号和密码匹配出现雷同,mobile id是[${result
|
||||
// .map((ele) => ele.id)
|
||||
// .join(',')}], mobile是${mobile}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
const closeRootMode = context.openRootMode();
|
||||
if (disableRegister) {
|
||||
if (isMobile(account)) {
|
||||
const [existMobile] = await context.select(
|
||||
'mobile',
|
||||
{
|
||||
data: {
|
||||
id: 1,
|
||||
mobile: 1,
|
||||
},
|
||||
filter: {
|
||||
mobile: account!,
|
||||
ableState: 'enabled',
|
||||
},
|
||||
},
|
||||
{ dontCollect: true }
|
||||
);
|
||||
if (!existMobile) {
|
||||
closeRootMode();
|
||||
throw new OakUserException('账号不存在');
|
||||
}
|
||||
} else if (isEmail(account)) {
|
||||
const [existEmail] = await context.select(
|
||||
'email',
|
||||
{
|
||||
data: {
|
||||
id: 1,
|
||||
email: 1,
|
||||
},
|
||||
filter: {
|
||||
email: account!,
|
||||
ableState: 'enabled',
|
||||
},
|
||||
},
|
||||
{ dontCollect: true }
|
||||
);
|
||||
if (!existEmail) {
|
||||
closeRootMode();
|
||||
throw new OakUserException('账号不存在');
|
||||
}
|
||||
} else {
|
||||
// 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 loginByEmail<ED extends EntityDict>(
|
||||
params: {
|
||||
email: string;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default OakComponent({
|
|||
inputOptions: [] as Option[],
|
||||
scanOptions: [] as Option[],
|
||||
allowSms: false,
|
||||
allowEmail: false,
|
||||
allowPassword: false,
|
||||
allowWechatMp: false,
|
||||
setLoginModeMp(value: string) { this.setLoginMode(value) },
|
||||
|
|
@ -131,7 +132,7 @@ export default OakComponent({
|
|||
let appId;
|
||||
let domain; //网站扫码授权回调域
|
||||
let isSupportWechatGrant = false; // 微信公众号授权登录
|
||||
let allowSms = false, allowPassword = false, allowWechatMp = false; //小程序登录显示
|
||||
|
||||
if (appType === 'wechatPublic') {
|
||||
const config2 = config as WechatPublicConfig;
|
||||
const isService = config2?.isService; //是否服务号 服务号才能授权登录
|
||||
|
|
@ -141,12 +142,14 @@ export default OakComponent({
|
|||
const config2 = config as WebConfig;
|
||||
appId = config2?.wechat?.appId;
|
||||
domain = config2?.wechat?.domain;
|
||||
} else if (appType === 'wechatMp') {
|
||||
allowSms = passportTypes.includes('sms') && !onlyPassword;
|
||||
allowPassword = passportTypes.includes('password') && !onlyCaptcha;
|
||||
allowWechatMp = passportTypes.includes('wechatMp') && !onlyCaptcha && !onlyPassword;
|
||||
}
|
||||
|
||||
const allowSms = passportTypes.includes('sms') && !onlyPassword;
|
||||
const allowEmail = passportTypes.includes('email') && !onlyCaptcha && !onlyPassword;
|
||||
const allowPassword = passportTypes.includes('password') && !onlyCaptcha;
|
||||
const allowWechatMp = passportTypes.includes('wechatMp') && !onlyCaptcha && !onlyPassword;
|
||||
|
||||
|
||||
this.setState(
|
||||
{
|
||||
loginMode,
|
||||
|
|
@ -157,6 +160,7 @@ export default OakComponent({
|
|||
inputOptions,
|
||||
scanOptions,
|
||||
allowSms,
|
||||
allowEmail,
|
||||
allowPassword,
|
||||
allowWechatMp,
|
||||
smsDigit,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { WebConfig, WechatPublicConfig, AppType } from "../../../../entities/App
|
|||
|
||||
import { LOCAL_STORAGE_KEYS } from '../../../../config/constants';
|
||||
import { EntityDict } from "../../../../oak-app-domain";
|
||||
import { isMobile, isPassword } from "oak-domain/lib/utils/validator";
|
||||
import { isEmail, isMobile, isPassword } from "oak-domain/lib/utils/validator";
|
||||
|
||||
export default OakComponent({
|
||||
isList: false,
|
||||
|
|
@ -14,9 +14,10 @@ export default OakComponent({
|
|||
counter: 0,
|
||||
loading: false,
|
||||
domain: undefined as string | undefined,
|
||||
mobile: '',
|
||||
account: '',
|
||||
password: '',
|
||||
validMobile: false,
|
||||
vaildEmail: false,
|
||||
validPassword: false,
|
||||
allowSubmit: false,
|
||||
},
|
||||
|
|
@ -25,23 +26,24 @@ export default OakComponent({
|
|||
redirectUri: '', // 微信登录后的redirectUri,要指向wechatUser/login去处理
|
||||
url: '', // 登录系统之后要返回的页面
|
||||
callback: undefined as (() => void) | undefined, // 登录成功回调,排除微信登录方式
|
||||
allowSms: false, //小程序切换短信登录
|
||||
allowSms: false,
|
||||
allowEmail: false,
|
||||
allowWechatMp: false, //小程序切换授权登录
|
||||
setLoginMode: (value: string) => undefined as void,
|
||||
},
|
||||
lifetimes: {
|
||||
},
|
||||
listeners: {
|
||||
'validMobile,validPassword'(prev, next) {
|
||||
'validMobile,vaildEmail,validPassword'(prev, next) {
|
||||
const { allowSubmit } = this.state;
|
||||
if (allowSubmit) {
|
||||
if (!(next.validMobile && next.validPassword)) {
|
||||
if (!((next.validMobile || next.vaildEmail) && next.validPassword)) {
|
||||
this.setState({
|
||||
allowSubmit: false,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (next.validMobile && next.validPassword) {
|
||||
if ((next.validMobile || next.vaildEmail) && next.validPassword) {
|
||||
this.setState({
|
||||
allowSubmit: true,
|
||||
})
|
||||
|
|
@ -52,13 +54,13 @@ export default OakComponent({
|
|||
methods: {
|
||||
async loginByPassword() {
|
||||
const { url, callback } = this.props;
|
||||
const { mobile, password } = this.state;
|
||||
const { account, password } = this.state;
|
||||
try {
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
await this.features.token.loginByMobile(
|
||||
mobile,
|
||||
await this.features.token.loginByAccount(
|
||||
account,
|
||||
password,
|
||||
);
|
||||
this.setState({
|
||||
|
|
@ -84,13 +86,16 @@ export default OakComponent({
|
|||
});
|
||||
}
|
||||
},
|
||||
inputChange(type: 'mobile' | 'password', value: string) {
|
||||
inputChange(type: 'account' | 'password', value: string) {
|
||||
const { allowSms, allowEmail } = this.props;
|
||||
switch (type) {
|
||||
case 'mobile':
|
||||
const validMobile = !!isMobile(value);
|
||||
case 'account':
|
||||
const validMobile = allowSms && !!isMobile(value);
|
||||
const vaildEmail = allowEmail && !!isEmail(value);
|
||||
this.setState({
|
||||
mobile: value,
|
||||
account: value,
|
||||
validMobile,
|
||||
vaildEmail,
|
||||
})
|
||||
break;
|
||||
case 'password':
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
import { Form, Input, Button, Checkbox, Typography, Segmented } from 'antd';
|
||||
import {
|
||||
LockOutlined,
|
||||
MobileOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { WebComponentProps } from 'oak-frontend-base';
|
||||
import { EntityDict } from '../../../oak-app-domain';
|
||||
|
|
@ -36,7 +36,7 @@ export default function Render(
|
|||
},
|
||||
{
|
||||
loginByPassword: () => Promise<void>;
|
||||
inputChange: (type: 'mobile' | 'password', value: string) => void;
|
||||
inputChange: (type: 'account' | 'password', value: string) => void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
|
|
@ -52,11 +52,11 @@ export default function Render(
|
|||
value={mobile}
|
||||
type="tel"
|
||||
size="large"
|
||||
maxLength={11}
|
||||
prefix={<MobileOutlined />}
|
||||
// maxLength={11}
|
||||
prefix={<UserOutlined />}
|
||||
placeholder={t('placeholder.Mobile')}
|
||||
onChange={(e) => {
|
||||
inputChange('mobile', e.target.value);
|
||||
inputChange('account', e.target.value);
|
||||
}}
|
||||
className={Style['loginbox-input']}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ export default function Render(
|
|||
scanOptions: Option[];
|
||||
smsDigit: number;
|
||||
emailDigit: number;
|
||||
allowSms: boolean;
|
||||
allowEmail: boolean;
|
||||
},
|
||||
{
|
||||
setLoginMode: (value: number) => void;
|
||||
|
|
@ -76,6 +78,8 @@ export default function Render(
|
|||
scanOptions,
|
||||
smsDigit,
|
||||
emailDigit,
|
||||
allowSms,
|
||||
allowEmail,
|
||||
} = data;
|
||||
const { t, setLoginMode } = methods;
|
||||
|
||||
|
|
@ -208,6 +212,8 @@ export default function Render(
|
|||
disabled={disabled}
|
||||
url={url}
|
||||
callback={callback}
|
||||
allowSms={allowSms}
|
||||
allowEmail={allowEmail}
|
||||
/>
|
||||
{Tip}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -142,6 +142,29 @@ export class Token<ED extends EntityDict> extends Feature {
|
|||
this.publish();
|
||||
}
|
||||
|
||||
async loginByAccount(
|
||||
account: string,
|
||||
password: string,
|
||||
disableRegister?: boolean
|
||||
) {
|
||||
const env = await this.environment.getEnv();
|
||||
const { result } = await this.cache.exec(
|
||||
'loginByAccount',
|
||||
{
|
||||
account,
|
||||
password,
|
||||
disableRegister,
|
||||
env,
|
||||
},
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
this.tokenValue = result;
|
||||
// await this.storage.save(LOCAL_STORAGE_KEYS.token, result);
|
||||
this.publish();
|
||||
}
|
||||
|
||||
|
||||
async loginByWechatInWebEnv(wechatLoginId: string) {
|
||||
const env = await this.environment.getEnv();
|
||||
const { result } = await this.cache.exec('loginByWechat', {
|
||||
|
|
@ -295,7 +318,7 @@ export class Token<ED extends EntityDict> extends Feature {
|
|||
type,
|
||||
});
|
||||
return result as string;
|
||||
}else {
|
||||
} else {
|
||||
const { result } = await this.cache.exec('sendCaptchaByEmail', {
|
||||
email: content,
|
||||
env: env as WebEnv,
|
||||
|
|
|
|||
Loading…
Reference in New Issue