feat: 密码登录方式调整至系统配置管理下

This commit is contained in:
lxy 2025-11-12 14:50:35 +08:00
parent 5fb1988b0d
commit 5b702a0d7d
36 changed files with 417 additions and 215 deletions

View File

@ -536,23 +536,9 @@ export async function loginByMobile(params, context) {
}
export async function verifyPassword(params, context) {
const { password } = params;
const systemId = context.getSystemId();
const [pwdPassport] = await context.select('passport', {
data: {
id: 1,
systemId: 1,
config: 1,
type: 1,
enabled: 1,
},
filter: {
systemId,
enabled: true,
type: 'password',
}
}, { forUpdate: true });
// assert(pwdPassport);
const pwdMode = pwdPassport?.config?.mode ?? 'all';
const { system } = context.getApplication();
const pwdConfig = system?.config.Password;
const pwdMode = pwdConfig?.mode ?? 'all';
let pwdFilter = {};
if (pwdMode === 'all') {
pwdFilter = {
@ -664,13 +650,15 @@ export async function loginByAccount(params, context) {
id: 1,
type: 1,
systemId: 1,
}
},
allowPwd: 1,
},
filter: {
passport: {
systemId,
},
applicationId,
allowPwd: true,
}
}, {
dontCollect: true,
@ -761,13 +749,15 @@ export async function loginByAccount(params, context) {
id: 1,
type: 1,
systemId: 1,
}
},
allowPwd: 1,
},
filter: {
passport: {
systemId,
},
applicationId,
allowPwd: true,
}
}, {
dontCollect: true,
@ -875,29 +865,9 @@ export async function loginByAccount(params, context) {
}
};
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 pwdMode = applicationPassport?.passport?.config?.mode ?? 'all';
const { system } = context.getApplication();
const pwdConfig = system?.config.Password;
const pwdMode = pwdConfig?.mode ?? 'all';
let pwdFilter = {}, updateData = {};
if (pwdMode === 'all') {
pwdFilter = {
@ -2546,8 +2516,8 @@ export async function refreshToken(params, context) {
// 只有server模式去刷新token
// 'development' | 'production' | 'staging'
const intervals = {
development: 7200 * 1000,
staging: 600 * 1000,
development: 7200 * 1000, // 2小时
staging: 600 * 1000, // 十分钟
production: 600 * 1000, // 十分钟
};
let applicationId = token.applicationId;

View File

@ -1,3 +1,2 @@
/// <reference types="wechat-miniprogram" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "user", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
export default _default;

View File

@ -54,9 +54,8 @@ export default OakComponent({
lifetimes: {
async ready() {
const lastSendAt = await this.load(SEND_KEY);
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig = applicationPassports.find((ele) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
const pwdMin = passwordConfig?.min ?? 8;
const pwdMax = passwordConfig?.max ?? 24;

View File

@ -1,3 +1,2 @@
/// <reference types="wechat-miniprogram" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "user", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
export default _default;

View File

@ -23,9 +23,8 @@ export default OakComponent({
},
lifetimes: {
async ready() {
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig = applicationPassports.find((ele) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
const pwdMin = passwordConfig?.min ?? 8;
const pwdMax = passwordConfig?.max ?? 24;

View File

@ -1,7 +1,7 @@
import { Style } from '../../../../types/Style';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../../oak-app-domain").EntityDict, keyof import("../../../../oak-app-domain").EntityDict, false, {
style: Style;
entity: "platform" | "system" | "application";
entity: "system" | "platform" | "application";
entityId: string;
name: string;
}>) => React.ReactElement;

View File

@ -1,7 +1,7 @@
import { Config } from '../../../types/Config';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, false, {
config: Config;
entity: "platform" | "system";
entity: "system" | "platform";
name: string;
entityId: string;
}>) => React.ReactElement;

View File

@ -0,0 +1,7 @@
import React from 'react';
import { Config } from '../../../../types/Config';
export default function Security(props: {
password: Required<Config>['Password'];
setValue: (path: string, value: any) => void;
setValues: (value: Record<string, any>) => void;
}): React.JSX.Element;

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react';
import { Col, Divider, Input, Form, Space, Radio, InputNumber, Switch, } from 'antd';
import Styles from './web.module.less';
import EditorRegexs from '../../../passport/password/editorRegexs';
export default function Security(props) {
const { password, setValue, setValues } = props;
const { mode, min, max, verify, regexs, tip } = password || {};
const [newTip, setNewTip] = useState('');
useEffect(() => {
const { password } = props;
if (!password.mode) {
setValues({
mode: 'all',
min: 8,
max: 24,
});
}
}, [password]);
useEffect(() => {
if (tip && !newTip) {
setNewTip(tip);
}
}, [tip]);
return (<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
<Col flex="auto">
<Divider orientation="left" className={Styles.title}>
密码设置
</Divider>
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }}>
<Form.Item label="密码存储模式" tooltip="密码存储模式">
<Radio.Group onChange={({ target }) => {
const { value } = target;
setValue('mode', value);
}} value={mode}>
<Radio value="all">明文与SHA1加密</Radio>
<Radio value="plain">仅明文</Radio>
<Radio value="sha1">仅SHA1加密</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="密码位数范围" tooltip="密码位数范围">
<Space>
<InputNumber min={1} max={max} value={min} onChange={(value) => {
setValue('min', value);
}}/>
<div>~</div>
<InputNumber min={min} value={max} onChange={(value) => {
setValue('max', value);
}}/>
</Space>
</Form.Item>
<Form.Item label="开启正则校验" tooltip="开启后将使用下方设置的正则表达式对密码进行校验">
<Switch checkedChildren="开启" unCheckedChildren="关闭" checked={!!verify} onChange={(checked) => {
setValue('verigy', checked);
}}/>
</Form.Item>
<Form.Item label="正则" tooltip="可同时设置多组正则,系统将按照【与】逻辑进行校验,每个正则请以^开头以$结尾">
<>
{!!verify ? (<>
<EditorRegexs regexs={regexs || []} updateRegexs={(regexs) => {
setValue('regexs', regexs);
}}/>
</>) : (<div></div>)}
</>
</Form.Item>
<Form.Item label="密码提示语" tooltip="此提示语将显示在用户设置密码的输入框附近,用于清晰告知用户密码的格式要求">
<Input placeholder="请输入密码提示语" type="text" value={tip} onChange={(e) => {
setNewTip(e.target.value);
}} onBlur={() => {
if (newTip && newTip !== tip) {
setValue('tip', newTip);
}
}}/>
</Form.Item>
</Form>
</Col>
</Space>);
}

View File

@ -0,0 +1,16 @@
.label {
color: var(--oak-text-color-primary);
font-size: 28px;
line-height: 36px;
}
.tips {
color: var(--oak-text-color-placeholder);
font-size: 12px;
}
.title {
margin-bottom: 0px;
margin-top:36px;
}

View File

@ -9,10 +9,11 @@ import Sms from './sms/index';
import Email from './email/index';
import Basic from './basic/index';
import Security from './security/index';
import Password from './password/index';
export default function Render(props) {
const { entity, name, currentConfig, dirty } = props.data;
const { resetConfig, updateConfig, setValue, setValues, removeItem, cleanKey, t } = props.methods;
const { Account: account, Cos: cos, Map: map, Live: live, Sms: sms, App: app, Emails: emails, Security: security, } = currentConfig || {};
const { Account: account, Cos: cos, Map: map, Live: live, Sms: sms, App: app, Emails: emails, Security: security, Password: password, } = currentConfig || {};
return (<>
<Affix offsetTop={64}>
<Alert message={<div>
@ -84,6 +85,15 @@ export default function Render(props) {
});
}}/>),
},
{
key: '密码设置',
label: '密码设置',
children: (<Password password={password || {}} setValue={(path, value) => setValue(`Password.${path}`, value)} setValues={(value) => {
setValues({
Password: value
});
}}/>),
},
]}></Tabs>
</div>
</>);

View File

@ -34,9 +34,8 @@ export default OakComponent({
},
lifetimes: {
async ready() {
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig = applicationPassports.find((ele) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
const pwdMin = passwordConfig?.min ?? 8;
const pwdMax = passwordConfig?.max ?? 24;

View File

@ -1,4 +1,4 @@
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../../oak-app-domain").EntityDict, keyof import("../../../../oak-app-domain").EntityDict, boolean, {
onVerified: (() => void) | undefined;
onVerified: undefined | (() => void);
}>) => React.ReactElement;
export default _default;

View File

@ -11,9 +11,8 @@ export default OakComponent({
lifetimes: {
async ready() {
this.features.token.getToken();
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig = applicationPassports.find((ele) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
this.setState({
mode,

View File

@ -1,4 +1,3 @@
/// <reference types="react" />
import { WebComponentProps } from "oak-frontend-base";
import { EntityDict } from "../../../../oak-app-domain";
export default function Render(props: WebComponentProps<EntityDict, 'user', false, {

View File

@ -1,4 +1,3 @@
/// <reference types="react" />
import { WebComponentProps } from "oak-frontend-base";
import { EntityDict } from "../../../../oak-app-domain";
export default function Render(props: WebComponentProps<EntityDict, 'user', false, {

View File

@ -4,9 +4,9 @@ import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2;
entityId: string;
relations: EntityDict['relation']['OpSchema'][];
passwordRequired?: boolean | undefined;
allowUpdateName?: boolean | undefined;
allowUpdateNickname?: boolean | undefined;
relations: EntityDict["relation"]["OpSchema"][];
passwordRequired?: boolean;
allowUpdateName?: boolean;
allowUpdateNickname?: boolean;
}>) => React.ReactElement;
export default _default;

View File

@ -4,7 +4,7 @@ import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
entity: keyof ED2;
entityId: string;
allowUpdateName?: boolean | undefined;
allowUpdateNickname?: boolean | undefined;
allowUpdateName?: boolean;
allowUpdateNickname?: boolean;
}>) => React.ReactElement;
export default _default;

View File

@ -7,12 +7,12 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
entityId: string;
redirectToAfterConfirm: ED2["userEntityGrant"]["Schema"]["redirectTo"];
qrCodeType: QrCodeType;
type: EntityDict['userEntityGrant']['Schema']['type'];
relations: EntityDict['relation']['OpSchema'][];
type: EntityDict["userEntityGrant"]["Schema"]["type"];
relations: EntityDict["relation"]["OpSchema"][];
claimUrl: string;
multiple: boolean;
rule: EntityDict['userEntityGrant']['Schema']['rule'];
ruleOnRow: EntityDict['userEntityGrant']['OpSchema']['ruleOnRow'];
onUserEntityGrantCreated?: ((id: string) => void) | undefined;
rule: EntityDict["userEntityGrant"]["Schema"]["rule"];
ruleOnRow: EntityDict["userEntityGrant"]["OpSchema"]["ruleOnRow"];
onUserEntityGrantCreated?: (id: string) => void;
}>) => React.ReactElement;
export default _default;

View File

@ -7,8 +7,8 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
redirectToAfterConfirm: ED2["userEntityGrant"]["Schema"]["redirectTo"];
claimUrl: string;
qrCodeType: string;
passwordRequired?: boolean | undefined;
disabledMethods: Array<'email' | 'mobile' | 'userEntityGrant'>;
mode: 'byMobile' | 'byUserEntityGrant' | 'byEmail';
passwordRequired?: boolean;
disabledMethods: Array<"email" | "mobile" | "userEntityGrant">;
mode: "byMobile" | "byUserEntityGrant" | "byEmail";
}>) => React.ReactElement;
export default _default;

View File

@ -2,7 +2,7 @@ import { EntityDict } from '../../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "user", false, {
entity: keyof EntityDict;
entityId: string;
relations: import("../../../../oak-app-domain/Relation/_baseSchema").OpSchema[];
relations: EntityDict["relation"]["OpSchema"][];
mobile: string;
setPasswordConfirm: (value: boolean) => void;
passwordRequired: boolean;

View File

@ -84,9 +84,8 @@ export default OakComponent({
},
lifetimes: {
async ready() {
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig = applicationPassports.find((ele) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
const pwdMin = passwordConfig?.min ?? 8;
const pwdMax = passwordConfig?.max ?? 24;

View File

@ -2,6 +2,6 @@ import { EntityDict } from '../../../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "userRelation", true, {
entity: keyof EntityDict;
entityId: string;
relations: import("../../../../../oak-app-domain/Relation/_baseSchema").OpSchema[];
relations: EntityDict["relation"]["OpSchema"][];
}>) => React.ReactElement;
export default _default;

View File

@ -151,6 +151,14 @@ export type EmailConfig = {
name?: string;
secure?: boolean;
};
export type PasswordConfig = {
mode?: 'all' | 'plain' | 'sha1';
min?: number;
max?: number;
verify?: boolean;
regexs?: string[];
tip?: string;
};
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
export type Config = {
Account?: {
@ -202,6 +210,7 @@ export type Config = {
level?: 'weak' | 'medium' | 'strong';
passwordVerifyGap?: number;
};
Password?: PasswordConfig;
};
export type AccountOrigin = 'ali' | 'tencent' | 'qiniu' | 'amap' | 'ctyun' | 'local' | 's3';
export type CosOrigin = 'qiniu' | 'wechat' | 'ctyun' | 'aliyun' | 'tencent' | 'local' | 'unknown' | 's3';

View File

@ -1,6 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loginWebByMpToken = exports.refreshToken = exports.wakeupParasite = exports.logout = exports.getWechatMpUserPhoneNumber = exports.switchTo = exports.sendCaptchaByEmail = exports.sendCaptchaByMobile = exports.syncUserInfoWechatMp = exports.loginWechatMp = exports.loginWechat = exports.loginWechatNative = exports.loginByWechat = exports.refreshWechatPublicUserInfo = exports.setUserAvatarFromWechat = exports.bindByEmail = exports.bindByMobile = exports.loginByEmail = exports.loginByAccount = exports.verifyPassword = exports.loginByMobile = exports.loadTokenInfo = exports.setUpTokenAndUser = void 0;
exports.setUpTokenAndUser = setUpTokenAndUser;
exports.loadTokenInfo = loadTokenInfo;
exports.loginByMobile = loginByMobile;
exports.verifyPassword = verifyPassword;
exports.loginByAccount = loginByAccount;
exports.loginByEmail = loginByEmail;
exports.bindByMobile = bindByMobile;
exports.bindByEmail = bindByEmail;
exports.setUserAvatarFromWechat = setUserAvatarFromWechat;
exports.refreshWechatPublicUserInfo = refreshWechatPublicUserInfo;
exports.loginByWechat = loginByWechat;
exports.loginWechatNative = loginWechatNative;
exports.loginWechat = loginWechat;
exports.loginWechatMp = loginWechatMp;
exports.syncUserInfoWechatMp = syncUserInfoWechatMp;
exports.sendCaptchaByMobile = sendCaptchaByMobile;
exports.sendCaptchaByEmail = sendCaptchaByEmail;
exports.switchTo = switchTo;
exports.getWechatMpUserPhoneNumber = getWechatMpUserPhoneNumber;
exports.logout = logout;
exports.wakeupParasite = wakeupParasite;
exports.refreshToken = refreshToken;
exports.loginWebByMpToken = loginWebByMpToken;
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"));
@ -380,7 +402,6 @@ createData, user) {
}
}
}
exports.setUpTokenAndUser = setUpTokenAndUser;
async function setupMobile(mobile, env, context) {
const result2 = await context.select('mobile', {
data: {
@ -444,7 +465,6 @@ async function loadTokenInfo(tokenValue, context) {
},
}, {});
}
exports.loadTokenInfo = loadTokenInfo;
async function loginByMobile(params, context) {
const { mobile, captcha, env, disableRegister } = params;
const loginLogic = async (isRoot) => {
@ -540,26 +560,11 @@ async function loginByMobile(params, context) {
closeRootMode();
return tokenValue;
}
exports.loginByMobile = loginByMobile;
async function verifyPassword(params, context) {
const { password } = params;
const systemId = context.getSystemId();
const [pwdPassport] = await context.select('passport', {
data: {
id: 1,
systemId: 1,
config: 1,
type: 1,
enabled: 1,
},
filter: {
systemId,
enabled: true,
type: 'password',
}
}, { forUpdate: true });
// assert(pwdPassport);
const pwdMode = pwdPassport?.config?.mode ?? 'all';
const { system } = context.getApplication();
const pwdConfig = system?.config.Password;
const pwdMode = pwdConfig?.mode ?? 'all';
let pwdFilter = {};
if (pwdMode === 'all') {
pwdFilter = {
@ -605,7 +610,6 @@ async function verifyPassword(params, context) {
}
}, {});
}
exports.verifyPassword = verifyPassword;
async function loginByAccount(params, context) {
const { account, password, env } = params;
let needUpdatePassword = false;
@ -672,13 +676,15 @@ async function loginByAccount(params, context) {
id: 1,
type: 1,
systemId: 1,
}
},
allowPwd: 1,
},
filter: {
passport: {
systemId,
},
applicationId,
allowPwd: true,
}
}, {
dontCollect: true,
@ -769,13 +775,15 @@ async function loginByAccount(params, context) {
id: 1,
type: 1,
systemId: 1,
}
},
allowPwd: 1,
},
filter: {
passport: {
systemId,
},
applicationId,
allowPwd: true,
}
}, {
dontCollect: true,
@ -883,29 +891,9 @@ async function loginByAccount(params, context) {
}
};
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 pwdMode = applicationPassport?.passport?.config?.mode ?? 'all';
const { system } = context.getApplication();
const pwdConfig = system?.config.Password;
const pwdMode = pwdConfig?.mode ?? 'all';
let pwdFilter = {}, updateData = {};
if (pwdMode === 'all') {
pwdFilter = {
@ -958,7 +946,6 @@ async function loginByAccount(params, context) {
closeRootMode();
return tokenValue;
}
exports.loginByAccount = loginByAccount;
async function loginByEmail(params, context) {
const { email, captcha, env, disableRegister } = params;
const loginLogic = async () => {
@ -1029,7 +1016,6 @@ async function loginByEmail(params, context) {
closeRootMode();
return tokenValue;
}
exports.loginByEmail = loginByEmail;
async function bindByMobile(params, context) {
const { mobile, captcha, env, } = params;
const userId = context.getCurrentUserId();
@ -1141,7 +1127,6 @@ async function bindByMobile(params, context) {
await bindLogic();
closeRootMode();
}
exports.bindByMobile = bindByMobile;
async function bindByEmail(params, context) {
const { email, captcha, env, } = params;
const userId = context.getCurrentUserId();
@ -1254,7 +1239,6 @@ async function bindByEmail(params, context) {
await bindLogic();
closeRootMode();
}
exports.bindByEmail = bindByEmail;
async function setupLoginName(name, env, context) {
const result2 = await context.select('loginName', {
data: {
@ -1495,7 +1479,6 @@ async function setUserAvatarFromWechat(params, context) {
}, {});
}
}
exports.setUserAvatarFromWechat = setUserAvatarFromWechat;
async function tryRefreshWechatPublicUserInfo(wechatUserId, context) {
const [wechatUser] = await context.select('wechatUser', {
data: {
@ -1584,7 +1567,6 @@ async function refreshWechatPublicUserInfo({}, context) {
(0, assert_1.assert)(token.entityId);
return await tryRefreshWechatPublicUserInfo(token.entityId, context);
}
exports.refreshWechatPublicUserInfo = refreshWechatPublicUserInfo;
// 用户在微信端授权登录后在web端触发该方法
async function loginByWechat(params, context) {
const { wechatLoginId, env } = params;
@ -1614,7 +1596,6 @@ async function loginByWechat(params, context) {
closeRootMode();
return tokenValue;
}
exports.loginByWechat = loginByWechat;
async function loginFromWechatEnv(code, env, context, wechatLoginId) {
const application = context.getApplication();
const { type, config, systemId } = application;
@ -1958,7 +1939,6 @@ async function loginWechatNative({ code, env, }, context) {
closeRootMode();
return tokenValue;
}
exports.loginWechatNative = loginWechatNative;
/**
* 公众号授权登录
* @param param0
@ -1989,7 +1969,6 @@ async function loginWechat({ code, env, wechatLoginId, }, context) {
closeRootMode();
return tokenValue;
}
exports.loginWechat = loginWechat;
/**
* 小程序授权登录
* @param param0
@ -2003,7 +1982,6 @@ async function loginWechatMp({ code, env, }, context) {
closeRootMode();
return tokenValue;
}
exports.loginWechatMp = loginWechatMp;
/**
* 同步从wx.getUserProfile拿到的用户信息
* @param param0
@ -2057,7 +2035,6 @@ async function syncUserInfoWechatMp({ nickname, avatarUrl, encryptedData, iv, si
// 实测发现解密出来的和userInfo完全一致……
await setUserInfoFromWechat(user, { nickname, avatar: avatarUrl }, context);
}
exports.syncUserInfoWechatMp = syncUserInfoWechatMp;
async function sendCaptchaByMobile({ mobile, env, type: captchaType, }, context) {
const { type } = env;
let visitorId = mobile;
@ -2222,7 +2199,6 @@ async function sendCaptchaByMobile({ mobile, env, type: captchaType, }, context)
return '验证码发送失败';
}
}
exports.sendCaptchaByMobile = sendCaptchaByMobile;
async function sendCaptchaByEmail({ email, env, type: captchaType, }, context) {
const { type } = env;
let visitorId = email;
@ -2370,7 +2346,6 @@ async function sendCaptchaByEmail({ email, env, type: captchaType, }, context) {
return '验证码发送失败';
}
}
exports.sendCaptchaByEmail = sendCaptchaByEmail;
async function switchTo({ userId }, context) {
const reallyRoot = context.isReallyRoot();
if (!reallyRoot) {
@ -2392,7 +2367,6 @@ async function switchTo({ userId }, context) {
},
}, {});
}
exports.switchTo = switchTo;
async function getWechatMpUserPhoneNumber({ code, env }, context) {
const application = context.getApplication();
const { type, config, systemId } = application;
@ -2408,7 +2382,6 @@ async function getWechatMpUserPhoneNumber({ code, env }, context) {
closeRootMode();
return reuslt;
}
exports.getWechatMpUserPhoneNumber = getWechatMpUserPhoneNumber;
async function logout(params, context) {
const { tokenValue } = params;
if (tokenValue) {
@ -2431,7 +2404,6 @@ async function logout(params, context) {
closeRootMode();
}
}
exports.logout = logout;
/**
* 创建一个当前parasite上的token
* @param params
@ -2498,7 +2470,6 @@ async function wakeupParasite(params, context) {
closeRootMode();
return tokenValue;
}
exports.wakeupParasite = wakeupParasite;
/**
* todo 检查登录环境一致性同一个token不能跨越不同设备
* @param env1
@ -2571,8 +2542,8 @@ async function refreshToken(params, context) {
// 只有server模式去刷新token
// 'development' | 'production' | 'staging'
const intervals = {
development: 7200 * 1000,
staging: 600 * 1000,
development: 7200 * 1000, // 2小时
staging: 600 * 1000, // 十分钟
production: 600 * 1000, // 十分钟
};
let applicationId = token.applicationId;
@ -2645,7 +2616,6 @@ async function refreshToken(params, context) {
closeRootMode();
return tokenValue;
}
exports.refreshToken = refreshToken;
/**
* 使用微信小程序中的token登录web
* @param tokenValue
@ -2713,4 +2683,3 @@ async function loginWebByMpToken(params, context) {
closeRootMode();
return tokenValue;
}
exports.loginWebByMpToken = loginWebByMpToken;

View File

@ -151,6 +151,14 @@ export type EmailConfig = {
name?: string;
secure?: boolean;
};
export type PasswordConfig = {
mode?: 'all' | 'plain' | 'sha1';
min?: number;
max?: number;
verify?: boolean;
regexs?: string[];
tip?: string;
};
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
export type Config = {
Account?: {
@ -202,6 +210,7 @@ export type Config = {
level?: 'weak' | 'medium' | 'strong';
passwordVerifyGap?: number;
};
Password?: PasswordConfig;
};
export type AccountOrigin = 'ali' | 'tencent' | 'qiniu' | 'amap' | 'ctyun' | 'local' | 's3';
export type CosOrigin = 'qiniu' | 'wechat' | 'ctyun' | 'aliyun' | 'tencent' | 'local' | 'unknown' | 's3';

View File

@ -40,7 +40,7 @@ import { sendSms } from '../utils/sms';
import { mergeUser } from './user';
import { cloneDeep, pick } from 'oak-domain/lib/utils/lodash';
import { BRC } from '../types/RuntimeCxt';
import { PwdConfig, SmsConfig } from '../entities/Passport';
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';
@ -730,23 +730,9 @@ export async function verifyPassword<ED extends EntityDict>(
context: BRC<ED>
) {
const { password } = params;
const systemId = context.getSystemId();
const [pwdPassport] = await context.select('passport', {
data: {
id: 1,
systemId: 1,
config: 1,
type: 1,
enabled: 1,
},
filter: {
systemId,
enabled: true,
type: 'password',
}
}, { forUpdate: true });
// assert(pwdPassport);
const pwdMode = (pwdPassport?.config as PwdConfig)?.mode ?? 'all';
const { system } = context.getApplication()!;
const pwdConfig = system?.config.Password;
const pwdMode = pwdConfig?.mode ?? 'all';
let pwdFilter = {};
if (pwdMode === 'all') {
pwdFilter = {
@ -881,13 +867,15 @@ export async function loginByAccount<ED extends EntityDict>(
id: 1,
type: 1,
systemId: 1,
}
},
allowPwd: 1,
},
filter: {
passport: {
systemId,
},
applicationId,
allowPwd: true,
}
},
{
@ -991,13 +979,15 @@ export async function loginByAccount<ED extends EntityDict>(
id: 1,
type: 1,
systemId: 1,
}
},
allowPwd: 1,
},
filter: {
passport: {
systemId,
},
applicationId,
allowPwd: true,
}
},
{
@ -1119,32 +1109,9 @@ export async function loginByAccount<ED extends EntityDict>(
}
};
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 pwdMode = (applicationPassport?.passport?.config as PwdConfig)?.mode ?? 'all';
const { system } = context.getApplication()!;
const pwdConfig = system?.config.Password;
const pwdMode = pwdConfig?.mode ?? 'all';
let pwdFilter = {}, updateData = {};
if (pwdMode === 'all') {
pwdFilter = {
@ -3324,7 +3291,7 @@ export async function refreshToken<ED extends EntityDict>(
if (!token) {
throw new OakUnloggedInException("Token令牌已失效请重新登录");
}
const now = Date.now();
if (!checkTokenEnvConsistency(env, token.env as WebEnv)) {
console.log('####### refreshToken 环境改变 start #######\n');

View File

@ -63,9 +63,8 @@ export default OakComponent({
lifetimes: {
async ready() {
const lastSendAt = await this.load(SEND_KEY);
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig: PwdConfig = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
const pwdMin = passwordConfig?.min ?? 8;
const pwdMax = passwordConfig?.max ?? 24;

View File

@ -26,9 +26,8 @@ export default OakComponent({
},
lifetimes: {
async ready() {
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig: PwdConfig = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
const pwdMin = passwordConfig?.min ?? 8;
const pwdMax = passwordConfig?.max ?? 24;

View File

@ -0,0 +1,136 @@
import React, { useEffect, useState } from 'react';
import {
Tabs,
Row,
Col,
Card,
Divider,
Input,
Form,
Space,
Select,
Radio,
InputNumber,
Switch,
} from 'antd';
import Styles from './web.module.less';
import { Config } from '../../../../types/Config';
import EditorRegexs from '../../../passport/password/editorRegexs';
export default function Security(props: {
password: Required<Config>['Password'];
setValue: (path: string, value: any) => void;
setValues: (value: Record<string, any>) => void;
}) {
const { password, setValue, setValues } = props;
const { mode, min, max, verify, regexs, tip } = password || {};
const [newTip, setNewTip] = useState('');
useEffect(() => {
const { password } = props;
if (!password.mode) {
setValues({
mode: 'all',
min: 8,
max: 24,
});
}
}, [password]);
useEffect(() => {
if (tip && !newTip) {
setNewTip(tip)
}
}, [tip]);
return (
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
<Col flex="auto">
<Divider orientation="left" className={Styles.title}>
</Divider>
<Form
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
>
<Form.Item
label="密码存储模式"
tooltip="密码存储模式"
>
<Radio.Group
onChange={({ target }) => {
const { value } = target;
setValue('mode', value);
}}
value={mode}
>
<Radio value="all">SHA1加密</Radio>
<Radio value="plain"></Radio>
<Radio value="sha1">SHA1加密</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="密码位数范围"
tooltip="密码位数范围"
>
<Space>
<InputNumber min={1} max={max} value={min} onChange={(value) => {
setValue('min', value);
}} />
<div>~</div>
<InputNumber min={min} value={max} onChange={(value) => {
setValue('max', value);
}} />
</Space>
</Form.Item>
<Form.Item
label="开启正则校验"
tooltip="开启后将使用下方设置的正则表达式对密码进行校验"
>
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
checked={!!verify}
onChange={(checked) => {
setValue('verigy', checked)
}}
/>
</Form.Item>
<Form.Item
label="正则"
tooltip="可同时设置多组正则,系统将按照【与】逻辑进行校验,每个正则请以^开头以$结尾"
>
<>
{!!verify ? (
<>
<EditorRegexs regexs={regexs || []} updateRegexs={(regexs) => {
setValue('regexs', regexs);
}} />
</>
) : (
<div></div>
)}
</>
</Form.Item>
<Form.Item label="密码提示语" tooltip="此提示语将显示在用户设置密码的输入框附近,用于清晰告知用户密码的格式要求">
<Input
placeholder="请输入密码提示语"
type="text"
value={tip}
onChange={(e) => {
setNewTip(e.target.value);
}}
onBlur={() => {
if (newTip && newTip !== tip) {
setValue('tip', newTip);
}
}}
/>
</Form.Item>
</Form>
</Col>
</Space>
);
}

View File

@ -0,0 +1,16 @@
.label {
color: var(--oak-text-color-primary);
font-size: 28px;
line-height: 36px;
}
.tips {
color: var(--oak-text-color-placeholder);
font-size: 12px;
}
.title {
margin-bottom: 0px;
margin-top:36px;
}

View File

@ -9,6 +9,7 @@ import Sms from './sms/index';
import Email from './email/index';
import Basic from './basic/index';
import Security from './security/index';
import Password from './password/index';
import { Config } from '../../../types/Config';
import { EntityDict } from '../../../oak-app-domain';
@ -47,6 +48,7 @@ export default function Render(
App: app,
Emails: emails,
Security: security,
Password: password,
} = currentConfig || {};
return (
<>
@ -228,6 +230,23 @@ export default function Render(
/>
),
},
{
key: '密码设置',
label: '密码设置',
children: (
<Password
password={password || {}}
setValue={(path, value) =>
setValue(`Password.${path}`, value)
}
setValues={(value) => {
setValues({
Password: value
});
}}
/>
),
},
]}
></Tabs>
</div>

View File

@ -38,9 +38,8 @@ export default OakComponent({
},
lifetimes: {
async ready() {
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig: PwdConfig = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
const pwdMin = passwordConfig?.min ?? 8;
const pwdMax = passwordConfig?.max ?? 24;

View File

@ -14,9 +14,8 @@ export default OakComponent({
lifetimes: {
async ready() {
this.features.token.getToken();
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig: PwdConfig = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
this.setState({
mode,

View File

@ -90,9 +90,8 @@ export default OakComponent({
},
lifetimes: {
async ready() {
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const passwordConfig: PwdConfig = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type === 'password')?.passport.config;
const system = this.features.application.getApplication().system;
const passwordConfig = system?.config.Password;
const mode = passwordConfig?.mode ?? 'all';
const pwdMin = passwordConfig?.min ?? 8;
const pwdMax = passwordConfig?.max ?? 24;

View File

@ -81,7 +81,7 @@ export type LocalCosConfig = {
};
// S3/Minio 的区域类型(可以根据实际需要扩展)
export type S3Zone =
export type S3Zone =
| 'us-east-1'
| 'us-west-1'
| 'us-west-2'
@ -185,6 +185,15 @@ export type EmailConfig = {
secure?: boolean; //是否ssl
};
export type PasswordConfig = {
mode?: 'all' | 'plain' | 'sha1' //密码存储模式默认为all
min?: number; //位数最小值默认为8
max?: number; //位数最大值默认为24
verify?: boolean; //开启正则校验,默认不开启
regexs?: string[];
tip?: string; //登录提示语
};
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
export type Config = {
@ -236,7 +245,8 @@ export type Config = {
type?: 'password', // 采用密码作为第一安全元素
level?: 'weak' | 'medium' | 'strong'; // 强度
passwordVerifyGap?: number; // 在密码验证后的多长时间内认为是安全的,可以做敏感动作
}
};
Password?: PasswordConfig;
};
export type AccountOrigin = 'ali' | 'tencent' | 'qiniu' | 'amap' | 'ctyun' | 'local' | 's3';