密码登录支持邮箱
This commit is contained in:
parent
48f1d59a7d
commit
b63361782d
|
|
@ -30,6 +30,15 @@ export type AspectDict<ED extends EntityDict> = {
|
||||||
},
|
},
|
||||||
context: BackendRuntimeContext<ED>
|
context: BackendRuntimeContext<ED>
|
||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
|
loginByAccount: (
|
||||||
|
params: {
|
||||||
|
account: string;
|
||||||
|
password: string;
|
||||||
|
disableRegister?: boolean;
|
||||||
|
env: WebEnv | WechatMpEnv | NativeEnv;
|
||||||
|
},
|
||||||
|
context: BackendRuntimeContext<ED>
|
||||||
|
) => Promise<string>;
|
||||||
loginByEmail: (
|
loginByEmail: (
|
||||||
params: {
|
params: {
|
||||||
email: string;
|
email: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
loginByAccount,
|
||||||
loginByEmail,
|
loginByEmail,
|
||||||
loginByMobile,
|
loginByMobile,
|
||||||
loginWechat,
|
loginWechat,
|
||||||
|
|
@ -65,6 +66,7 @@ import {
|
||||||
import { getApplicationPassports, removeApplicationPassportsByPIds } from './applicationPassport';
|
import { getApplicationPassports, removeApplicationPassportsByPIds } from './applicationPassport';
|
||||||
|
|
||||||
const aspectDict = {
|
const aspectDict = {
|
||||||
|
loginByAccount,
|
||||||
loginByEmail,
|
loginByEmail,
|
||||||
mergeUser,
|
mergeUser,
|
||||||
switchTo,
|
switchTo,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import { BRC } from '../types/RuntimeCxt';
|
||||||
import { SmsConfig } from '../entities/Passport';
|
import { SmsConfig } from '../entities/Passport';
|
||||||
import { sendEmail } from '../utils/email';
|
import { sendEmail } from '../utils/email';
|
||||||
import { EmailConfig } from '../oak-app-domain/Passport/Schema';
|
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) {
|
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;
|
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>(
|
export async function loginByEmail<ED extends EntityDict>(
|
||||||
params: {
|
params: {
|
||||||
email: string;
|
email: string;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export default OakComponent({
|
||||||
inputOptions: [] as Option[],
|
inputOptions: [] as Option[],
|
||||||
scanOptions: [] as Option[],
|
scanOptions: [] as Option[],
|
||||||
allowSms: false,
|
allowSms: false,
|
||||||
|
allowEmail: false,
|
||||||
allowPassword: false,
|
allowPassword: false,
|
||||||
allowWechatMp: false,
|
allowWechatMp: false,
|
||||||
setLoginModeMp(value: string) { this.setLoginMode(value) },
|
setLoginModeMp(value: string) { this.setLoginMode(value) },
|
||||||
|
|
@ -131,7 +132,7 @@ export default OakComponent({
|
||||||
let appId;
|
let appId;
|
||||||
let domain; //网站扫码授权回调域
|
let domain; //网站扫码授权回调域
|
||||||
let isSupportWechatGrant = false; // 微信公众号授权登录
|
let isSupportWechatGrant = false; // 微信公众号授权登录
|
||||||
let allowSms = false, allowPassword = false, allowWechatMp = false; //小程序登录显示
|
|
||||||
if (appType === 'wechatPublic') {
|
if (appType === 'wechatPublic') {
|
||||||
const config2 = config as WechatPublicConfig;
|
const config2 = config as WechatPublicConfig;
|
||||||
const isService = config2?.isService; //是否服务号 服务号才能授权登录
|
const isService = config2?.isService; //是否服务号 服务号才能授权登录
|
||||||
|
|
@ -141,12 +142,14 @@ export default OakComponent({
|
||||||
const config2 = config as WebConfig;
|
const config2 = config as WebConfig;
|
||||||
appId = config2?.wechat?.appId;
|
appId = config2?.wechat?.appId;
|
||||||
domain = config2?.wechat?.domain;
|
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(
|
this.setState(
|
||||||
{
|
{
|
||||||
loginMode,
|
loginMode,
|
||||||
|
|
@ -157,6 +160,7 @@ export default OakComponent({
|
||||||
inputOptions,
|
inputOptions,
|
||||||
scanOptions,
|
scanOptions,
|
||||||
allowSms,
|
allowSms,
|
||||||
|
allowEmail,
|
||||||
allowPassword,
|
allowPassword,
|
||||||
allowWechatMp,
|
allowWechatMp,
|
||||||
smsDigit,
|
smsDigit,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { WebConfig, WechatPublicConfig, AppType } from "../../../../entities/App
|
||||||
|
|
||||||
import { LOCAL_STORAGE_KEYS } from '../../../../config/constants';
|
import { LOCAL_STORAGE_KEYS } from '../../../../config/constants';
|
||||||
import { EntityDict } from "../../../../oak-app-domain";
|
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({
|
export default OakComponent({
|
||||||
isList: false,
|
isList: false,
|
||||||
|
|
@ -14,9 +14,10 @@ export default OakComponent({
|
||||||
counter: 0,
|
counter: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
domain: undefined as string | undefined,
|
domain: undefined as string | undefined,
|
||||||
mobile: '',
|
account: '',
|
||||||
password: '',
|
password: '',
|
||||||
validMobile: false,
|
validMobile: false,
|
||||||
|
vaildEmail: false,
|
||||||
validPassword: false,
|
validPassword: false,
|
||||||
allowSubmit: false,
|
allowSubmit: false,
|
||||||
},
|
},
|
||||||
|
|
@ -25,23 +26,24 @@ export default OakComponent({
|
||||||
redirectUri: '', // 微信登录后的redirectUri,要指向wechatUser/login去处理
|
redirectUri: '', // 微信登录后的redirectUri,要指向wechatUser/login去处理
|
||||||
url: '', // 登录系统之后要返回的页面
|
url: '', // 登录系统之后要返回的页面
|
||||||
callback: undefined as (() => void) | undefined, // 登录成功回调,排除微信登录方式
|
callback: undefined as (() => void) | undefined, // 登录成功回调,排除微信登录方式
|
||||||
allowSms: false, //小程序切换短信登录
|
allowSms: false,
|
||||||
|
allowEmail: false,
|
||||||
allowWechatMp: false, //小程序切换授权登录
|
allowWechatMp: false, //小程序切换授权登录
|
||||||
setLoginMode: (value: string) => undefined as void,
|
setLoginMode: (value: string) => undefined as void,
|
||||||
},
|
},
|
||||||
lifetimes: {
|
lifetimes: {
|
||||||
},
|
},
|
||||||
listeners: {
|
listeners: {
|
||||||
'validMobile,validPassword'(prev, next) {
|
'validMobile,vaildEmail,validPassword'(prev, next) {
|
||||||
const { allowSubmit } = this.state;
|
const { allowSubmit } = this.state;
|
||||||
if (allowSubmit) {
|
if (allowSubmit) {
|
||||||
if (!(next.validMobile && next.validPassword)) {
|
if (!((next.validMobile || next.vaildEmail) && next.validPassword)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
allowSubmit: false,
|
allowSubmit: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (next.validMobile && next.validPassword) {
|
if ((next.validMobile || next.vaildEmail) && next.validPassword) {
|
||||||
this.setState({
|
this.setState({
|
||||||
allowSubmit: true,
|
allowSubmit: true,
|
||||||
})
|
})
|
||||||
|
|
@ -52,13 +54,13 @@ export default OakComponent({
|
||||||
methods: {
|
methods: {
|
||||||
async loginByPassword() {
|
async loginByPassword() {
|
||||||
const { url, callback } = this.props;
|
const { url, callback } = this.props;
|
||||||
const { mobile, password } = this.state;
|
const { account, password } = this.state;
|
||||||
try {
|
try {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
await this.features.token.loginByMobile(
|
await this.features.token.loginByAccount(
|
||||||
mobile,
|
account,
|
||||||
password,
|
password,
|
||||||
);
|
);
|
||||||
this.setState({
|
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) {
|
switch (type) {
|
||||||
case 'mobile':
|
case 'account':
|
||||||
const validMobile = !!isMobile(value);
|
const validMobile = allowSms && !!isMobile(value);
|
||||||
|
const vaildEmail = allowEmail && !!isEmail(value);
|
||||||
this.setState({
|
this.setState({
|
||||||
mobile: value,
|
account: value,
|
||||||
validMobile,
|
validMobile,
|
||||||
|
vaildEmail,
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
case 'password':
|
case 'password':
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import { Form, Input, Button, Checkbox, Typography, Segmented } from 'antd';
|
import { Form, Input, Button, Checkbox, Typography, Segmented } from 'antd';
|
||||||
import {
|
import {
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
MobileOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { WebComponentProps } from 'oak-frontend-base';
|
import { WebComponentProps } from 'oak-frontend-base';
|
||||||
import { EntityDict } from '../../../oak-app-domain';
|
import { EntityDict } from '../../../oak-app-domain';
|
||||||
|
|
@ -36,7 +36,7 @@ export default function Render(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loginByPassword: () => Promise<void>;
|
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}
|
value={mobile}
|
||||||
type="tel"
|
type="tel"
|
||||||
size="large"
|
size="large"
|
||||||
maxLength={11}
|
// maxLength={11}
|
||||||
prefix={<MobileOutlined />}
|
prefix={<UserOutlined />}
|
||||||
placeholder={t('placeholder.Mobile')}
|
placeholder={t('placeholder.Mobile')}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
inputChange('mobile', e.target.value);
|
inputChange('account', e.target.value);
|
||||||
}}
|
}}
|
||||||
className={Style['loginbox-input']}
|
className={Style['loginbox-input']}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ export default function Render(
|
||||||
scanOptions: Option[];
|
scanOptions: Option[];
|
||||||
smsDigit: number;
|
smsDigit: number;
|
||||||
emailDigit: number;
|
emailDigit: number;
|
||||||
|
allowSms: boolean;
|
||||||
|
allowEmail: boolean;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
setLoginMode: (value: number) => void;
|
setLoginMode: (value: number) => void;
|
||||||
|
|
@ -76,6 +78,8 @@ export default function Render(
|
||||||
scanOptions,
|
scanOptions,
|
||||||
smsDigit,
|
smsDigit,
|
||||||
emailDigit,
|
emailDigit,
|
||||||
|
allowSms,
|
||||||
|
allowEmail,
|
||||||
} = data;
|
} = data;
|
||||||
const { t, setLoginMode } = methods;
|
const { t, setLoginMode } = methods;
|
||||||
|
|
||||||
|
|
@ -208,6 +212,8 @@ export default function Render(
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
url={url}
|
url={url}
|
||||||
callback={callback}
|
callback={callback}
|
||||||
|
allowSms={allowSms}
|
||||||
|
allowEmail={allowEmail}
|
||||||
/>
|
/>
|
||||||
{Tip}
|
{Tip}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,29 @@ export class Token<ED extends EntityDict> extends Feature {
|
||||||
this.publish();
|
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) {
|
async loginByWechatInWebEnv(wechatLoginId: string) {
|
||||||
const env = await this.environment.getEnv();
|
const env = await this.environment.getEnv();
|
||||||
const { result } = await this.cache.exec('loginByWechat', {
|
const { result } = await this.cache.exec('loginByWechat', {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue