邮箱支持配置后缀,当设置后缀,会检查输入的邮箱是否符合要求
This commit is contained in:
parent
dae20932a9
commit
606b9e2023
|
|
@ -103,7 +103,7 @@ export async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'image',
|
||||
tag1: 'image',
|
||||
objectId: await generateNewIdAsync(),
|
||||
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
@ -128,7 +128,7 @@ export async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'video',
|
||||
tag1: 'video',
|
||||
objectId: await generateNewIdAsync(),
|
||||
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
@ -150,7 +150,7 @@ export async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'audio',
|
||||
tag1: 'audio',
|
||||
objectId: await generateNewIdAsync(),
|
||||
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
|
|||
|
|
@ -476,6 +476,28 @@ export async function loginByMobile(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: 'sms'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
assert(applicationPassport?.passport);
|
||||
if (disableRegister) {
|
||||
const [existMobile] = await context.select('mobile', {
|
||||
data: {
|
||||
|
|
@ -538,6 +560,49 @@ export async function loginByAccount(params, context) {
|
|||
assert(account);
|
||||
const accountType = isEmail(account) ? 'email' : (isMobile(account) ? 'mobile' : 'loginName');
|
||||
if (accountType === 'email') {
|
||||
// const application = context.getApplication();
|
||||
// const { system } = application!;
|
||||
// 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: 'email'
|
||||
// },
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// dontCollect: true,
|
||||
// }
|
||||
// );
|
||||
// assert(applicationPassport?.passport);
|
||||
// const config = applicationPassport.passport.config as EmailConfig;
|
||||
// const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
// assert(emailConfig);
|
||||
// const emailSuffix = config.emailSuffix;
|
||||
// // 检查邮箱后缀是否满足配置
|
||||
// if (emailSuffix?.length! > 0) {
|
||||
// let isValid = false;
|
||||
// for (const suffix of emailSuffix!) {
|
||||
// if (account.endsWith(suffix)) {
|
||||
// isValid = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (!isValid) {
|
||||
// throw new OakUserException('error::user.emailSuffixIsInvalid');
|
||||
// }
|
||||
// }
|
||||
const existEmail = await context.select('email', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
@ -822,6 +887,28 @@ 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 tokenValue = await loginLogic();
|
||||
const [tokenInfo] = await loadTokenInfo(tokenValue, context);
|
||||
const { userId } = tokenInfo;
|
||||
|
|
@ -889,6 +976,46 @@ export async function loginByEmail(params, context) {
|
|||
}
|
||||
};
|
||||
const closeRootMode = context.openRootMode();
|
||||
const application = context.getApplication();
|
||||
const { system } = application;
|
||||
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: 'email'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
assert(applicationPassport?.passport);
|
||||
const config = applicationPassport.passport.config;
|
||||
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
assert(emailConfig);
|
||||
const emailSuffix = config.emailSuffix;
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new OakUserException('邮箱后缀不符合要求');
|
||||
}
|
||||
}
|
||||
if (disableRegister) {
|
||||
const [existEmail] = await context.select('email', {
|
||||
data: {
|
||||
|
|
@ -942,7 +1069,7 @@ export async function bindByMobile(params, context) {
|
|||
throw new OakUserException('验证码已经过期');
|
||||
}
|
||||
// 到这里说明验证码已经通过
|
||||
//检查当前user是否已绑定mobile
|
||||
// 检查当前user是否已绑定mobile
|
||||
const [boundMobile] = await context.select('mobile', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
@ -1102,6 +1229,46 @@ export async function bindByEmail(params, context) {
|
|||
}
|
||||
};
|
||||
const closeRootMode = context.openRootMode();
|
||||
const application = context.getApplication();
|
||||
const { system } = application;
|
||||
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: 'email'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
assert(applicationPassport?.passport);
|
||||
const config = applicationPassport.passport.config;
|
||||
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
assert(emailConfig);
|
||||
const emailSuffix = config.emailSuffix;
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new OakUserException('邮箱后缀不符合要求');
|
||||
}
|
||||
}
|
||||
const [otherUserEmail] = await context.select('email', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
@ -2026,13 +2193,28 @@ export async function sendCaptchaByEmail({ email, env, type: captchaType, }, con
|
|||
const duration = config.codeDuration || 5;
|
||||
const digit = config.digit || 4;
|
||||
const mockSend = config.mockSend;
|
||||
const emailSuffix = config.emailSuffix;
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new OakUserException('邮箱后缀不符合要求');
|
||||
}
|
||||
}
|
||||
let emailOptions = {
|
||||
host: emailConfig.host,
|
||||
port: emailConfig.port,
|
||||
account: emailConfig.account,
|
||||
password: emailConfig.password,
|
||||
subject: config.subject,
|
||||
// host: emailConfig.host,
|
||||
// port: emailConfig.port,
|
||||
// secure: emailConfig.secure,
|
||||
// account: emailConfig.account,
|
||||
// password: emailConfig.password,
|
||||
from: emailConfig.name ? `"${emailConfig.name}" <${emailConfig.account}>` : emailConfig.account,
|
||||
subject: config.subject,
|
||||
to: email,
|
||||
text: config.text,
|
||||
html: config.html,
|
||||
|
|
@ -2341,8 +2523,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;
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export async function createWechatQrCode(options, context) {
|
|||
permanent,
|
||||
url,
|
||||
expired: false,
|
||||
expiresAt: Date.now() + 2592000 * 1000,
|
||||
expiresAt: Date.now() + 2592000 * 1000, // wecharQrCode里的过期时间都放到最大,由上层关联对象来主动过期(by Xc, 20230131)
|
||||
props,
|
||||
};
|
||||
// 直接创建
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ export default OakComponent({
|
|||
tocFixed: true,
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
headerTop: 0,
|
||||
headerTop: 0, //页面中吸顶部分高度
|
||||
className: '',
|
||||
scrollId: '',
|
||||
scrollId: '', // 滚动条所在容器id,不传默认body
|
||||
tocWidth: undefined,
|
||||
tocHeight: undefined,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ export default OakComponent({
|
|||
tocFixed: true,
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
headerTop: 0,
|
||||
headerTop: 0, //页面中吸顶部分高度
|
||||
className: '',
|
||||
scrollId: '',
|
||||
scrollId: '', // 滚动条所在容器id,不传默认body
|
||||
tocWidth: undefined,
|
||||
tocHeight: undefined,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export default OakComponent({
|
|||
properties: {
|
||||
articleMenuId: '',
|
||||
onChildEditArticleChange: (data) => undefined,
|
||||
show: 'edit',
|
||||
show: 'edit', // edit为编辑,doc为查看,preview为预览
|
||||
getBreadcrumbItemsByParent: (breadcrumbItems) => undefined,
|
||||
breadcrumbItems: [],
|
||||
drawerOpen: false,
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ export default OakComponent({
|
|||
properties: {
|
||||
articleMenuId: '',
|
||||
changeIsEdit: () => undefined,
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
onArticlePreview: (content, title) => undefined,
|
||||
origin: 'qiniu',
|
||||
scrollId: '',
|
||||
tocPosition: 'none', //目录显示位置,none为不显示目录
|
||||
highlightBgColor: 'none', //点击目录时标题高亮背景色,none为不显示高亮背景色
|
||||
onArticlePreview: (content, title) => undefined, //预览文章
|
||||
origin: 'qiniu', // 默认为七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
height: 600,
|
||||
},
|
||||
listeners: {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function customCheckImageFn(src, alt, url) {
|
|||
export default function Render(props) {
|
||||
const { methods, data } = props;
|
||||
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
|
||||
const { oakFullpath, id, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor = 'none', scrollId, tocWidth, tocHeight, height = 600 } = data;
|
||||
const { oakId, oakFullpath, id, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor = 'none', scrollId, tocWidth, tocHeight, height = 600 } = data;
|
||||
const [articleId, setArticleId] = useState('');
|
||||
const [toc, setToc] = useState([]);
|
||||
const [showToc, setShowToc] = useState(false);
|
||||
|
|
@ -78,7 +78,7 @@ export default function Render(props) {
|
|||
const filename = name.substring(0, name.lastIndexOf("."));
|
||||
const extraFile = {
|
||||
entity: "article",
|
||||
entityId: articleId,
|
||||
entityId: oakId || articleId,
|
||||
origin: origin,
|
||||
type: "image",
|
||||
tag1: "source",
|
||||
|
|
@ -109,7 +109,7 @@ export default function Render(props) {
|
|||
const filename = name.substring(0, name.lastIndexOf("."));
|
||||
const extraFile = {
|
||||
entity: "article",
|
||||
entityId: articleId,
|
||||
entityId: oakId || articleId,
|
||||
origin: origin,
|
||||
type: "video",
|
||||
tag1: "source",
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export default OakComponent({
|
|||
onRemove: () => undefined,
|
||||
onUpdateName: async (name) => undefined,
|
||||
onChildEditArticleChange: (data) => undefined,
|
||||
show: 'edit',
|
||||
show: 'edit', // edit为编辑,doc为查看,preview为预览
|
||||
getBreadcrumbItemsByParent: (breadcrumbItems) => undefined,
|
||||
breadItems: [],
|
||||
drawerOpen: false,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default OakComponent({
|
|||
entityId: '',
|
||||
parentId: '',
|
||||
onGrandChildEditArticleChange: (data) => undefined,
|
||||
show: 'edit',
|
||||
show: 'edit', // edit为编辑,doc为查看,preview为预览
|
||||
articleMenuId: '',
|
||||
articleId: '',
|
||||
getBreadcrumbItems: (breadcrumbItems) => undefined,
|
||||
|
|
|
|||
|
|
@ -14,18 +14,18 @@ export default OakComponent({
|
|||
properties: {
|
||||
entity: '',
|
||||
entityId: '',
|
||||
show: 'edit',
|
||||
articleMenuId: '',
|
||||
articleId: '',
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
onMenuView: () => undefined,
|
||||
onMenuViewById: (articleMenuId) => undefined,
|
||||
onArticleView: (articleId) => undefined,
|
||||
onArticlePreview: (content, title) => undefined,
|
||||
onArticleEdit: (articleId) => undefined,
|
||||
show: 'edit', // edit为编辑,doc为查看,preview为预览
|
||||
articleMenuId: '', // 菜单id
|
||||
articleId: '', //文章id
|
||||
tocPosition: 'none', //文章目录显示位置,none为不显示目录
|
||||
highlightBgColor: 'none', //点击文章目录时标题高亮背景色,none为不显示高亮背景色
|
||||
onMenuView: () => undefined, //查看全部菜单
|
||||
onMenuViewById: (articleMenuId) => undefined, //查看指定id菜单
|
||||
onArticleView: (articleId) => undefined, //查看文章
|
||||
onArticlePreview: (content, title) => undefined, //预览文章
|
||||
onArticleEdit: (articleId) => undefined, //编辑文章
|
||||
setCopyArticleUrl: (articleId) => '',
|
||||
origin: 'qiniu',
|
||||
origin: 'qiniu', // cos origin默认七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default OakComponent({
|
|||
code: '',
|
||||
title: '',
|
||||
desc: '',
|
||||
icon: '',
|
||||
icon: '', //web独有
|
||||
imagePath: '', //小程序独有
|
||||
},
|
||||
lifetimes: {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default OakComponent({
|
|||
color: '#666',
|
||||
selectedColor: '',
|
||||
border: false,
|
||||
selectedIconPath: '',
|
||||
selectedIconPath: '', //一般在list设置
|
||||
iconPath: '',
|
||||
},
|
||||
lifetimes: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Tabs, Col, Divider, Input, Form, Space, Modal, DatePicker, } from 'antd';
|
||||
import { Tabs, Col, Divider, Input, Form, Space, Modal, Switch, DatePicker, } from 'antd';
|
||||
import Styles from './web.module.less';
|
||||
import dayjs from 'dayjs';
|
||||
const { confirm } = Modal;
|
||||
|
|
@ -41,6 +41,11 @@ export default function Email(props) {
|
|||
setValue(`${idx}.port`, Number(e.target.value));
|
||||
}}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="启用SSL加密" tooltip="当连接使用的是SSL端口,请启用SSL加密功能以保障数据传输安全">
|
||||
<>
|
||||
<Switch checkedChildren="是" unCheckedChildren="否" checked={ele?.secure} onChange={(checked) => setValue(`${idx}.secure`, checked)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="账号">
|
||||
<Input placeholder="请输入邮箱账号(例:xxxx@163.com)" type="text" value={ele.account} onChange={(e) => {
|
||||
setValue(`${idx}.account`, e.target.value);
|
||||
|
|
|
|||
|
|
@ -47,19 +47,19 @@ export default OakComponent({
|
|||
bucket: '',
|
||||
autoUpload: false,
|
||||
maxNumber: 20,
|
||||
extension: [],
|
||||
selectCount: 1,
|
||||
sourceType: ['album', 'camera'],
|
||||
mediaType: ['image'],
|
||||
mode: 'aspectFit',
|
||||
size: 3,
|
||||
showUploadList: true,
|
||||
showUploadProgress: false,
|
||||
accept: 'image/*',
|
||||
disablePreview: false,
|
||||
disableDelete: false,
|
||||
disableAdd: false,
|
||||
disableDownload: false,
|
||||
extension: [], //小程序独有 chooseMessageFile
|
||||
selectCount: 1, // 每次打开图片时,可选中的数量 小程序独有
|
||||
sourceType: ['album', 'camera'], // 小程序独有 chooseMedia
|
||||
mediaType: ['image'], // 小程序独有 chooseMedia
|
||||
mode: 'aspectFit', // 图片显示模式
|
||||
size: 3, // 每行可显示的个数 小程序独有
|
||||
showUploadList: true, //web独有
|
||||
showUploadProgress: false, // web独有
|
||||
accept: 'image/*', // web独有
|
||||
disablePreview: false, // 图片是否可预览
|
||||
disableDelete: false, // 图片是否可删除
|
||||
disableAdd: false, // 上传按钮隐藏
|
||||
disableDownload: false, // 下载按钮隐藏
|
||||
type: 'image',
|
||||
origin: 'qiniu',
|
||||
tag1: '',
|
||||
|
|
@ -67,40 +67,40 @@ export default OakComponent({
|
|||
entity: '',
|
||||
entityId: '',
|
||||
theme: 'image',
|
||||
enableCrop: false,
|
||||
enableCompross: false,
|
||||
enableCrop: false, //启用裁剪
|
||||
enableCompross: false, //启用压缩
|
||||
//图片裁剪
|
||||
cropQuality: 1,
|
||||
showRest: false,
|
||||
showGrid: false,
|
||||
fillColor: 'white',
|
||||
rotationSlider: false,
|
||||
aspectSlider: false,
|
||||
zoomSlider: true,
|
||||
resetText: '重置',
|
||||
aspect: 1 / 1,
|
||||
minZoom: 1,
|
||||
maxZoom: 3,
|
||||
cropShape: 'rect',
|
||||
cropperProps: {},
|
||||
modalTitle: '编辑图片',
|
||||
modalWidth: '40vw',
|
||||
modalOk: '确定',
|
||||
modalCancel: '取消',
|
||||
cropQuality: 1, //图片裁剪质量,范围:0 ~ 1
|
||||
showRest: false, //显示重置按钮,重置缩放及旋转
|
||||
showGrid: false, //显示裁切区域网格(九宫格)
|
||||
fillColor: 'white', //裁切图像填充色
|
||||
rotationSlider: false, //图片旋转控制
|
||||
aspectSlider: false, //裁切比率控制
|
||||
zoomSlider: true, //图片缩放控制
|
||||
resetText: '重置', //重置按钮文字
|
||||
aspect: 1 / 1, //裁切区域宽高比,width / height
|
||||
minZoom: 1, //最小缩放倍数
|
||||
maxZoom: 3, //最大缩放倍数
|
||||
cropShape: 'rect', //裁切区域形状,'rect' 或 'round'
|
||||
cropperProps: {}, //recat-easy-crop的props
|
||||
modalTitle: '编辑图片', //弹窗标题
|
||||
modalWidth: '40vw', //弹窗宽度
|
||||
modalOk: '确定', //确定按钮文字
|
||||
modalCancel: '取消', //取消按钮的文字
|
||||
//图片压缩
|
||||
strict: true,
|
||||
checkOrientation: true,
|
||||
retainExif: false,
|
||||
maxWidth: Infinity,
|
||||
maxHeight: Infinity,
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
compressWidth: undefined,
|
||||
compressHeight: undefined,
|
||||
resize: 'none',
|
||||
compressQuality: 0.8,
|
||||
mimeType: 'auto',
|
||||
convertTypes: ['image/png'],
|
||||
strict: true, //当压缩后的图片尺寸大于原图尺寸时输出原图
|
||||
checkOrientation: true, //读取图像的Exif方向值并自动旋转或翻转图像(仅限 JPEG 图像)
|
||||
retainExif: false, //压缩后保留图片的Exif信息
|
||||
maxWidth: Infinity, //输出图片的最大宽度,值需大于0
|
||||
maxHeight: Infinity, //输出图片的最大高度,值需大于0
|
||||
minWidth: 0, //输出图片的最小宽度,值需大于0且不应大于maxWidth
|
||||
minHeight: 0, //输出图片的最小高度。值需大于0且不应大于maxHeight
|
||||
compressWidth: undefined, //输出图像的宽度。如果未指定,则将使用原始图像的宽度,若设置了height,则宽度将根据自然纵横比自动计算。
|
||||
compressHeight: undefined, //输出图像的高度。如果未指定,则将使用原始图像的高度,若设置了width,则高度将根据自然纵横比自动计算。
|
||||
resize: 'none', //仅在同时指定了width和height时生效
|
||||
compressQuality: 0.8, //输出图像的质量。范围:0 ~ 1
|
||||
mimeType: 'auto', //输出图片的 MIME 类型。默认情况下,将使用源图片文件的原始 MIME 类型。
|
||||
convertTypes: ['image/png'], //文件类型包含在其中且文件大小超过该convertSize值的文件将被转换为 JPEG。
|
||||
convertSize: Infinity, //文件类型包含在convertTypes中且文件大小超过此值的文件将转换为 JPEG,Infinity表示禁用该功能
|
||||
},
|
||||
features: ['extraFile'],
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default function render(props) {
|
|||
};
|
||||
return (<>
|
||||
{enableCrop ? (<ImgCrop showReset={showRest} showGrid={showGrid} fillColor={fillColor} rotationSlider={rotationSlider} aspectSlider={aspectSlider} zoomSlider={zoomSlider} resetText={resetText} aspect={aspect} minZoom={minZoom} maxZoom={maxZoom} cropShape={cropShape} quality={cropQuality} cropperProps={{
|
||||
restrictPosition: false,
|
||||
restrictPosition: false, //允许移动图片位置
|
||||
...cropperProps,
|
||||
}} modalTitle={modalTitle} modalWidth={modalWidth} modalOk={modalOk} modalCancel={modalCancel}>
|
||||
<ExtrafileUpload oakPath={oakFullpath} bucket={bucket} autoUpload={autoUpload} maxNumber={maxNumber} mode={mode} showUploadList={showUploadList} showUploadProgress={showUploadProgress} accept={accept} disablePreview={disablePreview} disableDelete={disableDelete} disableAdd={disableAdd} disableDownload={disableDownload} disabled={disabled} type={type} origin={origin} tag1={tag1} tag2={tag2} entity={entity} entityId={entityId} theme={theme} children={children} beforeUpload={async (file) => {
|
||||
|
|
|
|||
|
|
@ -55,9 +55,9 @@ export default OakComponent({
|
|||
data: {
|
||||
isModalOpen: false,
|
||||
isModalOpen1: false,
|
||||
renderImgs: [],
|
||||
renderImgs: [], // 读取的原文图片,在modal使用
|
||||
methodsType: '',
|
||||
bridgeUrl: '',
|
||||
bridgeUrl: '', // 通过桥接方式获得的url
|
||||
selectedId: -1,
|
||||
},
|
||||
properties: {
|
||||
|
|
|
|||
|
|
@ -67,10 +67,10 @@ export default OakComponent({
|
|||
},
|
||||
],
|
||||
properties: {
|
||||
mode: 'aspectFit',
|
||||
size: 3,
|
||||
disablePreview: false,
|
||||
disableDownload: false,
|
||||
mode: 'aspectFit', // 图片显示模式
|
||||
size: 3, // 每行可显示的个数 小程序独有
|
||||
disablePreview: false, // 图片是否可预览
|
||||
disableDownload: false, // 下载按钮隐藏
|
||||
tag1: '',
|
||||
tag2: '',
|
||||
entity: '',
|
||||
|
|
|
|||
|
|
@ -50,19 +50,19 @@ export default OakComponent({
|
|||
bucket: '',
|
||||
autoUpload: false,
|
||||
maxNumber: 20,
|
||||
extension: [],
|
||||
selectCount: 1,
|
||||
sourceType: ['album', 'camera'],
|
||||
mediaType: ['image'],
|
||||
mode: 'aspectFit',
|
||||
size: 3,
|
||||
showUploadList: true,
|
||||
showUploadProgress: false,
|
||||
accept: 'image/*',
|
||||
disablePreview: false,
|
||||
disableDelete: false,
|
||||
disableAdd: false,
|
||||
disableDownload: false,
|
||||
extension: [], //小程序独有 chooseMessageFile
|
||||
selectCount: 1, // 每次打开图片时,可选中的数量 小程序独有
|
||||
sourceType: ['album', 'camera'], // 小程序独有 chooseMedia
|
||||
mediaType: ['image'], // 小程序独有 chooseMedia
|
||||
mode: 'aspectFit', // 图片显示模式
|
||||
size: 3, // 每行可显示的个数 小程序独有
|
||||
showUploadList: true, //web独有
|
||||
showUploadProgress: false, // web独有
|
||||
accept: 'image/*', // web独有
|
||||
disablePreview: false, // 图片是否可预览
|
||||
disableDelete: false, // 图片是否可删除
|
||||
disableAdd: false, // 上传按钮隐藏
|
||||
disableDownload: false, // 下载按钮隐藏
|
||||
type: 'image',
|
||||
origin: 'qiniu',
|
||||
tag1: '',
|
||||
|
|
@ -147,7 +147,7 @@ export default OakComponent({
|
|||
type,
|
||||
tag1,
|
||||
tag2,
|
||||
objectId: generateNewId(),
|
||||
objectId: generateNewId(), // 这个域用来标识唯一性
|
||||
entity,
|
||||
filename,
|
||||
size,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,60 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Switch, Alert, Typography, Form, Input, Radio, Tag } from 'antd';
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { Switch, Alert, Typography, Form, Input, Radio, Tag, Tooltip, Flex } from 'antd';
|
||||
import Styles from './web.module.less';
|
||||
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
|
||||
import { PlusOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
const { TextArea } = Input;
|
||||
const { Text } = Typography;
|
||||
function RenderEmailSuffix(props) {
|
||||
const { emailSuffix, onChange, t } = props;
|
||||
const [inputVisible, setInputVisible] = useState(false);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const inputRef = useRef(null);
|
||||
const tagInputStyle = {
|
||||
width: 100,
|
||||
height: 22,
|
||||
marginInlineEnd: 8,
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
const tagPlusStyle = {
|
||||
height: 22,
|
||||
borderStyle: 'dashed',
|
||||
};
|
||||
const handleInputChange = (e) => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
const handleInputConfirm = () => {
|
||||
if (inputValue && !emailSuffix?.includes(inputValue)) {
|
||||
onChange([...emailSuffix || [], inputValue]);
|
||||
}
|
||||
setInputVisible(false);
|
||||
setInputValue('');
|
||||
};
|
||||
const handleClose = (removedTag) => {
|
||||
const emailSuffix2 = emailSuffix.filter((tag) => tag !== removedTag);
|
||||
onChange(emailSuffix2);
|
||||
};
|
||||
const showInput = () => {
|
||||
setInputVisible(true);
|
||||
};
|
||||
return (<Flex gap="4px 0" wrap="wrap">
|
||||
{(emailSuffix || []).map((tag, index) => {
|
||||
const isLongTag = tag.length > 20;
|
||||
const tagElem = (<Tag closeIcon={<CloseOutlined />} key={tag} onClose={() => handleClose(tag)}>
|
||||
<span>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</span>
|
||||
</Tag>);
|
||||
return isLongTag ? (<Tooltip title={tag} key={tag}>
|
||||
{tagElem}
|
||||
</Tooltip>) : (tagElem);
|
||||
})}
|
||||
{inputVisible ? (<Input ref={inputRef} type="text" size="small" style={tagInputStyle} value={inputValue} onChange={handleInputChange} onBlur={handleInputConfirm} onPressEnter={handleInputConfirm}/>) : (<Tag style={tagPlusStyle} icon={<PlusOutlined />} onClick={showInput}>
|
||||
{t('common::action.add')}
|
||||
</Tag>)}
|
||||
</Flex>);
|
||||
}
|
||||
export default function Email(props) {
|
||||
const { passport, t, changeEnabled, updateConfig } = props;
|
||||
const { id, type, enabled, stateColor } = passport;
|
||||
|
|
@ -16,6 +66,7 @@ export default function Email(props) {
|
|||
const [html, setHtml] = useState(config?.html || '');
|
||||
const [emailCodeDuration, setEmailCodeDuration] = useState(config?.codeDuration || '');
|
||||
const [emailDigit, setEmailDigit] = useState(config?.digit || '');
|
||||
const [emailSuffix, setEmailSuffix] = useState(config?.emailSuffix || []);
|
||||
// editor 实例
|
||||
const [editor, setEditor] = useState(null); // TS 语法
|
||||
// 工具栏配置
|
||||
|
|
@ -40,6 +91,7 @@ export default function Email(props) {
|
|||
setHtml(config?.html || '');
|
||||
setEmailCodeDuration(config?.codeDuration || '');
|
||||
setEmailDigit(config?.digit || '');
|
||||
setEmailSuffix(config?.emailSuffix || []);
|
||||
if (config?.html) {
|
||||
setEContentType('html');
|
||||
}
|
||||
|
|
@ -150,6 +202,13 @@ export default function Email(props) {
|
|||
else {
|
||||
updateConfig(id, config, 'digit', undefined, 'email');
|
||||
}
|
||||
}}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="邮箱后缀" tooltip="允许的邮箱后缀(如: @qq.com),不填则不校验">
|
||||
<RenderEmailSuffix t={t} emailSuffix={emailSuffix} onChange={(v) => {
|
||||
if (v !== config?.emailSuffix) {
|
||||
updateConfig(id, config, 'emailSuffix', v, 'email');
|
||||
}
|
||||
}}/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,40 @@ export default OakComponent({
|
|||
config: 1,
|
||||
description: 1,
|
||||
style: 1,
|
||||
// system$platform: {
|
||||
// $entity: 'system',
|
||||
// data: {
|
||||
// id: 1,
|
||||
// name: 1,
|
||||
// config: 1,
|
||||
// description: 1,
|
||||
// super: 1,
|
||||
// folder: 1,
|
||||
// platformId: 1,
|
||||
// style: 1,
|
||||
// domain$system: {
|
||||
// $entity: 'domain',
|
||||
// data: {
|
||||
// id: 1,
|
||||
// systemId: 1,
|
||||
// url: 1,
|
||||
// },
|
||||
// },
|
||||
// application$system: {
|
||||
// $entity: 'application',
|
||||
// data: {
|
||||
// id: 1,
|
||||
// name: 1,
|
||||
// config: 1,
|
||||
// description: 1,
|
||||
// type: 1,
|
||||
// systemId: 1,
|
||||
// domainId: 1,
|
||||
// style: 1,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
formData({ data }) {
|
||||
return data || {};
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export default function render(props) {
|
|||
{
|
||||
label: <div className={Styles.tabLabel}>{t('system-list')}</div>,
|
||||
key: 'system',
|
||||
children: (<PlatformSystem oakPath={`${oakFullpath}.system$platform`} platformId={id}/>),
|
||||
children: (<PlatformSystem oakPath={`${oakFullpath}-PlatformSystem`} platformId={id}/>),
|
||||
},
|
||||
]}/>
|
||||
</div>);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
.container {
|
||||
padding: 8px;
|
||||
height: 100%;
|
||||
|
||||
.tabLabel {
|
||||
// writing-mode: vertical-rl;
|
||||
letter-spacing: .2rem;
|
||||
}
|
||||
// .tabLabel {
|
||||
// letter-spacing: .2rem;
|
||||
// }
|
||||
}
|
||||
|
|
@ -32,6 +32,13 @@ export default OakComponent({
|
|||
}
|
||||
}
|
||||
},
|
||||
filters: [{
|
||||
filter() {
|
||||
return {
|
||||
platformId: this.props.platformId,
|
||||
};
|
||||
},
|
||||
}],
|
||||
properties: {
|
||||
platformId: '',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ export default function render(props) {
|
|||
</Button>}>
|
||||
{t('confirmToRemove')}
|
||||
</Modal>
|
||||
<Tabs type="editable-card" onEdit={(key, action) => {
|
||||
<Tabs type="editable-card"
|
||||
// destroyInactiveTabPane={true}
|
||||
onEdit={(key, action) => {
|
||||
if (action === 'add') {
|
||||
const id = addItem({ platformId, config: { App: {} } });
|
||||
setCreateId(id);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default OakComponent({
|
|||
properties: {
|
||||
sessionId: '',
|
||||
isEntity: false,
|
||||
entityDisplay: (data) => [],
|
||||
entityDisplay: (data) => [], // user端,指示如何显示entity对象名称
|
||||
entityProjection: null, // user端,指示需要取哪些entity的属性来显示entityDisplay
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -140,12 +140,12 @@ export default OakComponent({
|
|||
unSub: undefined,
|
||||
},
|
||||
properties: {
|
||||
entity: '',
|
||||
entityFilter: null,
|
||||
entity: '', // entity端,指示相应的entity
|
||||
entityFilter: null, // entity端,指示相应的entity查询条件
|
||||
entityFilterSubStr: '',
|
||||
entityDisplay: (data) => [],
|
||||
entityProjection: null,
|
||||
sessionId: '',
|
||||
entityDisplay: (data) => [], // user端,指示如何显示entity对象名称
|
||||
entityProjection: null, // user端,指示需要取哪些entity的属性来显示entityDisplay
|
||||
sessionId: '', // 指示需要打开的默认session
|
||||
dialog: false,
|
||||
onItemClick: null,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export default OakComponent({
|
|||
dialog: false,
|
||||
entity: '',
|
||||
entityId: '',
|
||||
entityDisplay: (data) => [],
|
||||
entityDisplay: (data) => [], // user端,指示如何显示entity对象名称
|
||||
entityProjection: null, // user端,指示需要取哪些entity的属性来显示entityDisplay
|
||||
},
|
||||
filters: [
|
||||
|
|
|
|||
|
|
@ -18,18 +18,6 @@ export default OakComponent({
|
|||
url: 1,
|
||||
},
|
||||
},
|
||||
// application$system: {
|
||||
// $entity: 'application',
|
||||
// data: {
|
||||
// id: 1,
|
||||
// name: 1,
|
||||
// config: 1,
|
||||
// description: 1,
|
||||
// type: 1,
|
||||
// systemId: 1,
|
||||
// domainId: 1,
|
||||
// }
|
||||
// }
|
||||
},
|
||||
formData({ data }) {
|
||||
return data || {};
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export default function Render(props) {
|
|||
</div>),
|
||||
key: 'passport-list',
|
||||
destroyInactiveTabPane: true,
|
||||
children: (<Passport oakPath={`$system-passport`} systemId={id} systemName={name}/>),
|
||||
children: (<Passport oakPath={`$system-passport-${id}`} systemId={id} systemName={name}/>),
|
||||
},
|
||||
]}/>
|
||||
</div>);
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export default OakComponent({
|
|||
},
|
||||
properties: {
|
||||
disabled: '',
|
||||
url: '',
|
||||
callback: undefined,
|
||||
url: '', // 登录系统之后要返回的页面
|
||||
callback: undefined, // 登录成功回调,排除微信登录方式
|
||||
setLoginMode: (value) => undefined,
|
||||
digit: 4, //验证码位数
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@ export default OakComponent({
|
|||
allowPassword: false,
|
||||
allowWechatMp: false,
|
||||
setLoginModeMp(value) { this.setLoginMode(value); },
|
||||
smsDigit: 4,
|
||||
smsDigit: 4, //短信验证码位数
|
||||
emailDigit: 4, //邮箱验证码位数
|
||||
},
|
||||
properties: {
|
||||
onlyCaptcha: false,
|
||||
onlyPassword: false,
|
||||
disabled: '',
|
||||
redirectUri: '',
|
||||
url: '',
|
||||
redirectUri: '', // 微信登录后的redirectUri,要指向wechatUser/login去处理
|
||||
url: '', // 登录系统之后要返回的页面
|
||||
callback: undefined, // 登录成功回调,排除微信登录方式
|
||||
},
|
||||
formData({ features, props }) {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ export default OakComponent({
|
|||
},
|
||||
properties: {
|
||||
disabled: '',
|
||||
redirectUri: '',
|
||||
url: '',
|
||||
callback: undefined,
|
||||
redirectUri: '', // 微信登录后的redirectUri,要指向wechatUser/login去处理
|
||||
url: '', // 登录系统之后要返回的页面
|
||||
callback: undefined, // 登录成功回调,排除微信登录方式
|
||||
allowSms: false,
|
||||
allowEmail: false,
|
||||
allowWechatMp: false,
|
||||
allowWechatMp: false, //小程序切换授权登录
|
||||
setLoginMode: (value) => undefined,
|
||||
},
|
||||
lifetimes: {},
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ export default OakComponent({
|
|||
},
|
||||
properties: {
|
||||
disabled: '',
|
||||
url: '',
|
||||
callback: undefined,
|
||||
allowPassword: false,
|
||||
allowWechatMp: false,
|
||||
url: '', // 登录系统之后要返回的页面
|
||||
callback: undefined, // 登录成功回调,排除微信登录方式
|
||||
allowPassword: false, //小程序切换密码登录
|
||||
allowWechatMp: false, //小程序切换授权登录
|
||||
setLoginMode: (value) => undefined,
|
||||
digit: 4 //验证码位数,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default OakComponent({
|
|||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
type: 1,
|
||||
type: 1, //类型
|
||||
ticket: 1,
|
||||
url: 1,
|
||||
buffer: 1,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export default OakComponent({
|
|||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
type: 1,
|
||||
type: 1, //类型
|
||||
ticket: 1,
|
||||
url: 1,
|
||||
buffer: 1,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default OakComponent({
|
|||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
type: 1,
|
||||
type: 1, //类型
|
||||
ticket: 1,
|
||||
url: 1,
|
||||
expired: 1,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export default OakComponent({
|
|||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
type: 1,
|
||||
type: 1, //类型
|
||||
ticket: 1,
|
||||
url: 1,
|
||||
buffer: 1,
|
||||
|
|
|
|||
|
|
@ -626,7 +626,8 @@ const i18ns = [
|
|||
"loginWayDisabled": "暂不允许该登录方式",
|
||||
"hasToSetPassword": "需要设置密码",
|
||||
"hasToVerifyPassword": "需要验证密码",
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
|
||||
"emailSuffixIsInvalid": "邮箱后缀不合法"
|
||||
},
|
||||
"distinguishUser": "需要鉴别用户身份",
|
||||
"mobileUnset": "需要先登记手机号",
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ export const entityDesc = {
|
|||
zh_CN: {
|
||||
name: '直播流',
|
||||
attr: {
|
||||
title: '名称',
|
||||
title: '名称', // 用户定义直播间名称,
|
||||
streamTitle: '直播流名称',
|
||||
liveonly: '活跃状态',
|
||||
hub: '直播空间名称',
|
||||
hub: '直播空间名称', // 所属直播空间名称
|
||||
entity: '所属实体',
|
||||
entityId: '所属实体id',
|
||||
rtmpPushUrl: '推流地址',
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export type EmailConfig = {
|
|||
html?: string;
|
||||
codeDuration?: number;
|
||||
digit?: number;
|
||||
emailSuffix?: string[];
|
||||
};
|
||||
export type PfwConfig = {
|
||||
appId: string;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
"loginWayDisabled": "暂不允许该登录方式",
|
||||
"hasToSetPassword": "需要设置密码",
|
||||
"hasToVerifyPassword": "需要验证密码",
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
|
||||
"emailSuffixIsInvalid": "邮箱后缀不合法"
|
||||
},
|
||||
"distinguishUser": "需要鉴别用户身份",
|
||||
"mobileUnset": "需要先登记手机号",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export type EmailConfig = {
|
|||
html?: string;
|
||||
codeDuration?: number;
|
||||
digit?: number;
|
||||
emailSuffix?: string[];
|
||||
};
|
||||
export type PfwConfig = {
|
||||
appId: string;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
|
||||
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ async function sendNotification(notification, context) {
|
|||
await instance.sendSubscribedMessage({
|
||||
templateId: templateId,
|
||||
data: data,
|
||||
openId: data1.openId,
|
||||
openId: data1.openId, // 在notification创建时就赋值了
|
||||
page,
|
||||
state: StateDict[process.env.NODE_ENV],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -123,6 +123,15 @@ export type TencentSmsConfig = {
|
|||
defaultSignName: string;
|
||||
endpoint: string;
|
||||
};
|
||||
export type EmailConfig = {
|
||||
host: string;
|
||||
port: number;
|
||||
account: string;
|
||||
password: string;
|
||||
passwordExpiredAt?: number;
|
||||
name?: string;
|
||||
secure?: boolean;
|
||||
};
|
||||
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
|
||||
export type Config = {
|
||||
Account?: {
|
||||
|
|
@ -165,14 +174,7 @@ export type Config = {
|
|||
needUploadIDCardPhoto?: boolean;
|
||||
needManualVerification?: boolean;
|
||||
};
|
||||
Emails?: {
|
||||
host: string;
|
||||
port: number;
|
||||
account: string;
|
||||
password: string;
|
||||
passwordExpiredAt?: number;
|
||||
name?: string;
|
||||
}[];
|
||||
Emails?: EmailConfig[];
|
||||
Security?: {
|
||||
type?: 'password';
|
||||
level?: 'weak' | 'medium' | 'strong';
|
||||
|
|
|
|||
|
|
@ -26,13 +26,14 @@ interface Attachment extends AttachmentLike {
|
|||
raw?: string | Buffer | AttachmentLike | undefined;
|
||||
}
|
||||
export type EmailOptions = {
|
||||
host: string;
|
||||
port: number;
|
||||
account: string;
|
||||
password: string;
|
||||
subject: string;
|
||||
host?: string;
|
||||
port?: number;
|
||||
secure?: boolean;
|
||||
account?: string;
|
||||
password?: string;
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
attachments?: Attachment[];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import { EntityDict } from '../../oak-app-domain';
|
||||
import { BRC } from '../../types/RuntimeCxt';
|
||||
import Email, { EmailOptions } from '../../types/Email';
|
||||
import { BackendRuntimeContext } from '../../context/BackendRuntimeContext';
|
||||
import { EmailConfig } from '../../types/Config';
|
||||
export default class Nodemailer implements Email<EntityDict> {
|
||||
name: string;
|
||||
getConfig(context: BackendRuntimeContext<EntityDict>, systemId?: string): Promise<{
|
||||
config: EmailConfig;
|
||||
}>;
|
||||
sendEmail(options: EmailOptions, context: BRC<EntityDict>): Promise<{
|
||||
success: boolean;
|
||||
error?: undefined;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,50 @@
|
|||
//https://www.nodemailer.com/
|
||||
import nodemailer from 'nodemailer';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
import { get } from 'oak-domain/lib/utils/lodash';
|
||||
export default class Nodemailer {
|
||||
name = 'nodemailer';
|
||||
async getConfig(context, systemId) {
|
||||
let system;
|
||||
if (systemId) {
|
||||
[system] = await context.select('system', {
|
||||
data: {
|
||||
id: 1,
|
||||
config: 1,
|
||||
},
|
||||
filter: {
|
||||
id: systemId,
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
}
|
||||
else {
|
||||
system = context.getApplication().system;
|
||||
}
|
||||
const { config: systemConfig } = system;
|
||||
const emailConfig = get(systemConfig, 'Emails.0', {});
|
||||
const { host, port, password, account } = emailConfig;
|
||||
assert(host, 'host未配置');
|
||||
assert(port, 'port未配置');
|
||||
assert(account, 'account未配置');
|
||||
assert(password, 'password未配置');
|
||||
return {
|
||||
config: emailConfig,
|
||||
};
|
||||
}
|
||||
async sendEmail(options, context) {
|
||||
const { host, port, account, password, subject, from, text, html, to, attachments } = options;
|
||||
const { subject, from, text, html, to, attachments } = options;
|
||||
const { config, } = await this.getConfig(context);
|
||||
const transport = Object.assign(config, options);
|
||||
const _from = from || (config.name ? `"${config.name}" <${config.account}>` : config.account);
|
||||
const transporter = nodemailer.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure: port === 465,
|
||||
host: transport.host,
|
||||
port: transport.port,
|
||||
secure: transport.secure,
|
||||
auth: {
|
||||
user: account,
|
||||
pass: password,
|
||||
user: transport.account,
|
||||
pass: transport.password,
|
||||
},
|
||||
});
|
||||
async function verifyTransporter() {
|
||||
|
|
@ -31,7 +65,7 @@ export default class Nodemailer {
|
|||
console.log('Server is ready to take our messages');
|
||||
}
|
||||
let mailOptions = {
|
||||
from,
|
||||
from: _from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'image',
|
||||
tag1: 'image',
|
||||
objectId: await (0, uuid_1.generateNewIdAsync)(),
|
||||
objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
@ -131,7 +131,7 @@ async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'video',
|
||||
tag1: 'video',
|
||||
objectId: await (0, uuid_1.generateNewIdAsync)(),
|
||||
objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
@ -153,7 +153,7 @@ async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'audio',
|
||||
tag1: 'audio',
|
||||
objectId: await (0, uuid_1.generateNewIdAsync)(),
|
||||
objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
|
|||
|
|
@ -480,6 +480,28 @@ async function loginByMobile(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: 'sms'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
(0, assert_1.assert)(applicationPassport?.passport);
|
||||
if (disableRegister) {
|
||||
const [existMobile] = await context.select('mobile', {
|
||||
data: {
|
||||
|
|
@ -544,6 +566,49 @@ async function loginByAccount(params, context) {
|
|||
(0, assert_1.assert)(account);
|
||||
const accountType = (0, validator_1.isEmail)(account) ? 'email' : ((0, validator_1.isMobile)(account) ? 'mobile' : 'loginName');
|
||||
if (accountType === 'email') {
|
||||
// const application = context.getApplication();
|
||||
// const { system } = application!;
|
||||
// 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: 'email'
|
||||
// },
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// dontCollect: true,
|
||||
// }
|
||||
// );
|
||||
// assert(applicationPassport?.passport);
|
||||
// const config = applicationPassport.passport.config as EmailConfig;
|
||||
// const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
// assert(emailConfig);
|
||||
// const emailSuffix = config.emailSuffix;
|
||||
// // 检查邮箱后缀是否满足配置
|
||||
// if (emailSuffix?.length! > 0) {
|
||||
// let isValid = false;
|
||||
// for (const suffix of emailSuffix!) {
|
||||
// if (account.endsWith(suffix)) {
|
||||
// isValid = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (!isValid) {
|
||||
// throw new OakUserException('error::user.emailSuffixIsInvalid');
|
||||
// }
|
||||
// }
|
||||
const existEmail = await context.select('email', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
@ -828,6 +893,28 @@ 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,
|
||||
});
|
||||
(0, assert_1.assert)(applicationPassport?.passport);
|
||||
const tokenValue = await loginLogic();
|
||||
const [tokenInfo] = await loadTokenInfo(tokenValue, context);
|
||||
const { userId } = tokenInfo;
|
||||
|
|
@ -896,6 +983,46 @@ async function loginByEmail(params, context) {
|
|||
}
|
||||
};
|
||||
const closeRootMode = context.openRootMode();
|
||||
const application = context.getApplication();
|
||||
const { system } = application;
|
||||
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: 'email'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
(0, assert_1.assert)(applicationPassport?.passport);
|
||||
const config = applicationPassport.passport.config;
|
||||
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
(0, assert_1.assert)(emailConfig);
|
||||
const emailSuffix = config.emailSuffix;
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new types_1.OakUserException('邮箱后缀不符合要求');
|
||||
}
|
||||
}
|
||||
if (disableRegister) {
|
||||
const [existEmail] = await context.select('email', {
|
||||
data: {
|
||||
|
|
@ -950,7 +1077,7 @@ async function bindByMobile(params, context) {
|
|||
throw new types_1.OakUserException('验证码已经过期');
|
||||
}
|
||||
// 到这里说明验证码已经通过
|
||||
//检查当前user是否已绑定mobile
|
||||
// 检查当前user是否已绑定mobile
|
||||
const [boundMobile] = await context.select('mobile', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
@ -1111,6 +1238,46 @@ async function bindByEmail(params, context) {
|
|||
}
|
||||
};
|
||||
const closeRootMode = context.openRootMode();
|
||||
const application = context.getApplication();
|
||||
const { system } = application;
|
||||
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: 'email'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
(0, assert_1.assert)(applicationPassport?.passport);
|
||||
const config = applicationPassport.passport.config;
|
||||
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
(0, assert_1.assert)(emailConfig);
|
||||
const emailSuffix = config.emailSuffix;
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new types_1.OakUserException('邮箱后缀不符合要求');
|
||||
}
|
||||
}
|
||||
const [otherUserEmail] = await context.select('email', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
@ -2043,13 +2210,28 @@ async function sendCaptchaByEmail({ email, env, type: captchaType, }, context) {
|
|||
const duration = config.codeDuration || 5;
|
||||
const digit = config.digit || 4;
|
||||
const mockSend = config.mockSend;
|
||||
const emailSuffix = config.emailSuffix;
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new types_1.OakUserException('邮箱后缀不符合要求');
|
||||
}
|
||||
}
|
||||
let emailOptions = {
|
||||
host: emailConfig.host,
|
||||
port: emailConfig.port,
|
||||
account: emailConfig.account,
|
||||
password: emailConfig.password,
|
||||
subject: config.subject,
|
||||
// host: emailConfig.host,
|
||||
// port: emailConfig.port,
|
||||
// secure: emailConfig.secure,
|
||||
// account: emailConfig.account,
|
||||
// password: emailConfig.password,
|
||||
from: emailConfig.name ? `"${emailConfig.name}" <${emailConfig.account}>` : emailConfig.account,
|
||||
subject: config.subject,
|
||||
to: email,
|
||||
text: config.text,
|
||||
html: config.html,
|
||||
|
|
@ -2363,8 +2545,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;
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ async function createWechatQrCode(options, context) {
|
|||
permanent,
|
||||
url,
|
||||
expired: false,
|
||||
expiresAt: Date.now() + 2592000 * 1000,
|
||||
expiresAt: Date.now() + 2592000 * 1000, // wecharQrCode里的过期时间都放到最大,由上层关联对象来主动过期(by Xc, 20230131)
|
||||
props,
|
||||
};
|
||||
// 直接创建
|
||||
|
|
|
|||
|
|
@ -628,7 +628,8 @@ const i18ns = [
|
|||
"loginWayDisabled": "暂不允许该登录方式",
|
||||
"hasToSetPassword": "需要设置密码",
|
||||
"hasToVerifyPassword": "需要验证密码",
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
|
||||
"emailSuffixIsInvalid": "邮箱后缀不合法"
|
||||
},
|
||||
"distinguishUser": "需要鉴别用户身份",
|
||||
"mobileUnset": "需要先登记手机号",
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ exports.entityDesc = {
|
|||
zh_CN: {
|
||||
name: '直播流',
|
||||
attr: {
|
||||
title: '名称',
|
||||
title: '名称', // 用户定义直播间名称,
|
||||
streamTitle: '直播流名称',
|
||||
liveonly: '活跃状态',
|
||||
hub: '直播空间名称',
|
||||
hub: '直播空间名称', // 所属直播空间名称
|
||||
entity: '所属实体',
|
||||
entityId: '所属实体id',
|
||||
rtmpPushUrl: '推流地址',
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export type EmailConfig = {
|
|||
html?: string;
|
||||
codeDuration?: number;
|
||||
digit?: number;
|
||||
emailSuffix?: string[];
|
||||
};
|
||||
export type PfwConfig = {
|
||||
appId: string;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
"loginWayDisabled": "暂不允许该登录方式",
|
||||
"hasToSetPassword": "需要设置密码",
|
||||
"hasToVerifyPassword": "需要验证密码",
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
|
||||
"emailSuffixIsInvalid": "邮箱后缀不合法"
|
||||
},
|
||||
"distinguishUser": "需要鉴别用户身份",
|
||||
"mobileUnset": "需要先登记手机号",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export type EmailConfig = {
|
|||
html?: string;
|
||||
codeDuration?: number;
|
||||
digit?: number;
|
||||
emailSuffix?: string[];
|
||||
};
|
||||
export type PfwConfig = {
|
||||
appId: string;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
|
||||
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ async function sendNotification(notification, context) {
|
|||
await instance.sendSubscribedMessage({
|
||||
templateId: templateId,
|
||||
data: data,
|
||||
openId: data1.openId,
|
||||
openId: data1.openId, // 在notification创建时就赋值了
|
||||
page,
|
||||
state: StateDict[process.env.NODE_ENV],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -123,6 +123,15 @@ export type TencentSmsConfig = {
|
|||
defaultSignName: string;
|
||||
endpoint: string;
|
||||
};
|
||||
export type EmailConfig = {
|
||||
host: string;
|
||||
port: number;
|
||||
account: string;
|
||||
password: string;
|
||||
passwordExpiredAt?: number;
|
||||
name?: string;
|
||||
secure?: boolean;
|
||||
};
|
||||
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
|
||||
export type Config = {
|
||||
Account?: {
|
||||
|
|
@ -165,14 +174,7 @@ export type Config = {
|
|||
needUploadIDCardPhoto?: boolean;
|
||||
needManualVerification?: boolean;
|
||||
};
|
||||
Emails?: {
|
||||
host: string;
|
||||
port: number;
|
||||
account: string;
|
||||
password: string;
|
||||
passwordExpiredAt?: number;
|
||||
name?: string;
|
||||
}[];
|
||||
Emails?: EmailConfig[];
|
||||
Security?: {
|
||||
type?: 'password';
|
||||
level?: 'weak' | 'medium' | 'strong';
|
||||
|
|
|
|||
|
|
@ -26,13 +26,14 @@ interface Attachment extends AttachmentLike {
|
|||
raw?: string | Buffer | AttachmentLike | undefined;
|
||||
}
|
||||
export type EmailOptions = {
|
||||
host: string;
|
||||
port: number;
|
||||
account: string;
|
||||
password: string;
|
||||
subject: string;
|
||||
host?: string;
|
||||
port?: number;
|
||||
secure?: boolean;
|
||||
account?: string;
|
||||
password?: string;
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
attachments?: Attachment[];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import { EntityDict } from '../../oak-app-domain';
|
||||
import { BRC } from '../../types/RuntimeCxt';
|
||||
import Email, { EmailOptions } from '../../types/Email';
|
||||
import { BackendRuntimeContext } from '../../context/BackendRuntimeContext';
|
||||
import { EmailConfig } from '../../types/Config';
|
||||
export default class Nodemailer implements Email<EntityDict> {
|
||||
name: string;
|
||||
getConfig(context: BackendRuntimeContext<EntityDict>, systemId?: string): Promise<{
|
||||
config: EmailConfig;
|
||||
}>;
|
||||
sendEmail(options: EmailOptions, context: BRC<EntityDict>): Promise<{
|
||||
success: boolean;
|
||||
error?: undefined;
|
||||
|
|
|
|||
|
|
@ -3,17 +3,51 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
const tslib_1 = require("tslib");
|
||||
//https://www.nodemailer.com/
|
||||
const nodemailer_1 = tslib_1.__importDefault(require("nodemailer"));
|
||||
const assert_1 = require("oak-domain/lib/utils/assert");
|
||||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||||
class Nodemailer {
|
||||
name = 'nodemailer';
|
||||
async getConfig(context, systemId) {
|
||||
let system;
|
||||
if (systemId) {
|
||||
[system] = await context.select('system', {
|
||||
data: {
|
||||
id: 1,
|
||||
config: 1,
|
||||
},
|
||||
filter: {
|
||||
id: systemId,
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
}
|
||||
else {
|
||||
system = context.getApplication().system;
|
||||
}
|
||||
const { config: systemConfig } = system;
|
||||
const emailConfig = (0, lodash_1.get)(systemConfig, 'Emails.0', {});
|
||||
const { host, port, password, account } = emailConfig;
|
||||
(0, assert_1.assert)(host, 'host未配置');
|
||||
(0, assert_1.assert)(port, 'port未配置');
|
||||
(0, assert_1.assert)(account, 'account未配置');
|
||||
(0, assert_1.assert)(password, 'password未配置');
|
||||
return {
|
||||
config: emailConfig,
|
||||
};
|
||||
}
|
||||
async sendEmail(options, context) {
|
||||
const { host, port, account, password, subject, from, text, html, to, attachments } = options;
|
||||
const { subject, from, text, html, to, attachments } = options;
|
||||
const { config, } = await this.getConfig(context);
|
||||
const transport = Object.assign(config, options);
|
||||
const _from = from || (config.name ? `"${config.name}" <${config.account}>` : config.account);
|
||||
const transporter = nodemailer_1.default.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure: port === 465,
|
||||
host: transport.host,
|
||||
port: transport.port,
|
||||
secure: transport.secure,
|
||||
auth: {
|
||||
user: account,
|
||||
pass: password,
|
||||
user: transport.account,
|
||||
pass: transport.password,
|
||||
},
|
||||
});
|
||||
async function verifyTransporter() {
|
||||
|
|
@ -34,7 +68,7 @@ class Nodemailer {
|
|||
console.log('Server is ready to take our messages');
|
||||
}
|
||||
let mailOptions = {
|
||||
from,
|
||||
from: _from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
|
|
|
|||
|
|
@ -650,6 +650,32 @@ export async function loginByMobile<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: 'sms'
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
dontCollect: true,
|
||||
}
|
||||
);
|
||||
assert(applicationPassport?.passport);
|
||||
if (disableRegister) {
|
||||
const [existMobile] = await context.select(
|
||||
'mobile',
|
||||
|
|
@ -736,6 +762,50 @@ export async function loginByAccount<ED extends EntityDict>(
|
|||
assert(account);
|
||||
const accountType = isEmail(account) ? 'email' : (isMobile(account) ? 'mobile' : 'loginName');
|
||||
if (accountType === 'email') {
|
||||
// const application = context.getApplication();
|
||||
// const { system } = application!;
|
||||
// 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: 'email'
|
||||
// },
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// dontCollect: true,
|
||||
// }
|
||||
// );
|
||||
// assert(applicationPassport?.passport);
|
||||
// const config = applicationPassport.passport.config as EmailConfig;
|
||||
// const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
// assert(emailConfig);
|
||||
// const emailSuffix = config.emailSuffix;
|
||||
|
||||
// // 检查邮箱后缀是否满足配置
|
||||
// if (emailSuffix?.length! > 0) {
|
||||
// let isValid = false;
|
||||
// for (const suffix of emailSuffix!) {
|
||||
// if (account.endsWith(suffix)) {
|
||||
// isValid = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (!isValid) {
|
||||
// throw new OakUserException('error::user.emailSuffixIsInvalid');
|
||||
// }
|
||||
// }
|
||||
const existEmail = await context.select(
|
||||
'email',
|
||||
{
|
||||
|
|
@ -1057,6 +1127,31 @@ 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 tokenValue = await loginLogic();
|
||||
const [ tokenInfo ] = await loadTokenInfo<ED>(tokenValue, context);
|
||||
|
|
@ -1145,6 +1240,51 @@ export async function loginByEmail<ED extends EntityDict>(
|
|||
}
|
||||
};
|
||||
const closeRootMode = context.openRootMode();
|
||||
const application = context.getApplication();
|
||||
const { system } = application!;
|
||||
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: 'email'
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
dontCollect: true,
|
||||
}
|
||||
);
|
||||
assert(applicationPassport?.passport);
|
||||
const config = applicationPassport.passport.config as EmailConfig;
|
||||
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
assert(emailConfig);
|
||||
const emailSuffix = config.emailSuffix;
|
||||
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length! > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix!) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
throw new OakUserException('邮箱后缀不符合要求')
|
||||
}
|
||||
}
|
||||
if (disableRegister) {
|
||||
const [existEmail] = await context.select(
|
||||
'email',
|
||||
|
|
@ -1217,7 +1357,7 @@ export async function bindByMobile<ED extends EntityDict>(
|
|||
}
|
||||
|
||||
// 到这里说明验证码已经通过
|
||||
//检查当前user是否已绑定mobile
|
||||
// 检查当前user是否已绑定mobile
|
||||
const [boundMobile] = await context.select(
|
||||
'mobile',
|
||||
{
|
||||
|
|
@ -1423,6 +1563,52 @@ export async function bindByEmail<ED extends EntityDict>(
|
|||
|
||||
const closeRootMode = context.openRootMode();
|
||||
|
||||
const application = context.getApplication();
|
||||
const { system } = application!;
|
||||
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: 'email'
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
dontCollect: true,
|
||||
}
|
||||
);
|
||||
assert(applicationPassport?.passport);
|
||||
const config = applicationPassport.passport.config as EmailConfig;
|
||||
const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
|
||||
assert(emailConfig);
|
||||
const emailSuffix = config.emailSuffix;
|
||||
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length! > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix!) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
throw new OakUserException('邮箱后缀不符合要求')
|
||||
}
|
||||
}
|
||||
|
||||
const [otherUserEmail] = await context.select(
|
||||
'email',
|
||||
{
|
||||
|
|
@ -2714,14 +2900,31 @@ export async function sendCaptchaByEmail<ED extends EntityDict>(
|
|||
const duration = config.codeDuration || 5;
|
||||
const digit = config.digit || 4;
|
||||
const mockSend = config.mockSend;
|
||||
const emailSuffix = config.emailSuffix;
|
||||
|
||||
// 检查邮箱后缀是否满足配置
|
||||
if (emailSuffix?.length! > 0) {
|
||||
let isValid = false;
|
||||
for (const suffix of emailSuffix!) {
|
||||
if (email.endsWith(suffix)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
throw new OakUserException('邮箱后缀不符合要求')
|
||||
}
|
||||
}
|
||||
|
||||
let emailOptions: EmailOptions = {
|
||||
host: emailConfig.host,
|
||||
port: emailConfig.port,
|
||||
account: emailConfig.account,
|
||||
password: emailConfig.password,
|
||||
subject: config.subject,
|
||||
// host: emailConfig.host,
|
||||
// port: emailConfig.port,
|
||||
// secure: emailConfig.secure,
|
||||
// account: emailConfig.account,
|
||||
// password: emailConfig.password,
|
||||
from: emailConfig.name ? `"${emailConfig.name}" <${emailConfig.account}>` : emailConfig.account,
|
||||
subject: config.subject,
|
||||
to: email,
|
||||
text: config.text,
|
||||
html: config.html,
|
||||
|
|
|
|||
|
|
@ -95,6 +95,21 @@ export default function Email(props: {
|
|||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="启用SSL加密"
|
||||
tooltip="当连接使用的是SSL端口,请启用SSL加密功能以保障数据传输安全"
|
||||
>
|
||||
<>
|
||||
<Switch
|
||||
checkedChildren="是"
|
||||
unCheckedChildren="否"
|
||||
checked={ele?.secure}
|
||||
onChange={(checked) =>
|
||||
setValue(`${idx}.secure`, checked)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="账号"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,101 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { EmailConfig, MfwConfig, PfwConfig, SmsConfig } from "../../../entities/Passport";
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
import { Space, Switch, Alert, Typography, Form, Input, Radio, Tag, Select, Tooltip } from 'antd';
|
||||
import { Space, Switch, Alert, Typography, Form, Input, Radio, Tag, Select, Tooltip, InputRef, Flex } from 'antd';
|
||||
import Styles from './web.module.less';
|
||||
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
|
||||
import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor';
|
||||
import { PlusOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Text } = Typography;
|
||||
|
||||
function RenderEmailSuffix(props: {
|
||||
emailSuffix?: string[];
|
||||
onChange: (v: string[]) => void;
|
||||
t: (k: string) => string;
|
||||
}) {
|
||||
const { emailSuffix, onChange, t } = props;
|
||||
const [inputVisible, setInputVisible] = useState(false);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
const tagInputStyle: React.CSSProperties = {
|
||||
width: 100,
|
||||
height: 22,
|
||||
marginInlineEnd: 8,
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const tagPlusStyle: React.CSSProperties = {
|
||||
height: 22,
|
||||
borderStyle: 'dashed',
|
||||
};
|
||||
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleInputConfirm = () => {
|
||||
if (inputValue && !emailSuffix?.includes(inputValue)) {
|
||||
onChange([...emailSuffix || [], inputValue]);
|
||||
}
|
||||
setInputVisible(false);
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
const handleClose = (removedTag: string) => {
|
||||
const emailSuffix2 = emailSuffix!.filter((tag) => tag !== removedTag);
|
||||
onChange(emailSuffix2);
|
||||
};
|
||||
const showInput = () => {
|
||||
setInputVisible(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex gap="4px 0" wrap="wrap">
|
||||
{(emailSuffix || []).map<React.ReactNode>((tag, index) => {
|
||||
const isLongTag = tag.length > 20;
|
||||
const tagElem = (
|
||||
<Tag
|
||||
closeIcon={<CloseOutlined />}
|
||||
key={tag}
|
||||
onClose={() => handleClose(tag)}
|
||||
>
|
||||
<span>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</span>
|
||||
</Tag>
|
||||
);
|
||||
return isLongTag ? (
|
||||
<Tooltip title={tag} key={tag}>
|
||||
{tagElem}
|
||||
</Tooltip>
|
||||
) : (
|
||||
tagElem
|
||||
);
|
||||
})}
|
||||
{inputVisible ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
size="small"
|
||||
style={tagInputStyle}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputConfirm}
|
||||
onPressEnter={handleInputConfirm}
|
||||
/>
|
||||
) : (
|
||||
<Tag style={tagPlusStyle} icon={<PlusOutlined />} onClick={showInput}>
|
||||
{t('common::action.add')}
|
||||
</Tag>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Email(props: {
|
||||
passport: EntityDict['passport']['OpSchema'] & { stateColor: string };
|
||||
t: (k: string, params?: any) => string;
|
||||
|
|
@ -26,6 +112,7 @@ export default function Email(props: {
|
|||
const [html, setHtml] = useState(config?.html || '');
|
||||
const [emailCodeDuration, setEmailCodeDuration] = useState(config?.codeDuration || '');
|
||||
const [emailDigit, setEmailDigit] = useState(config?.digit || '');
|
||||
const [emailSuffix, setEmailSuffix] = useState(config?.emailSuffix || []);
|
||||
|
||||
// editor 实例
|
||||
const [editor, setEditor] = useState<IDomEditor | null>(null) // TS 语法
|
||||
|
|
@ -54,6 +141,7 @@ export default function Email(props: {
|
|||
setHtml(config?.html || '');
|
||||
setEmailCodeDuration(config?.codeDuration || '');
|
||||
setEmailDigit(config?.digit || '');
|
||||
setEmailSuffix(config?.emailSuffix || []);
|
||||
if (config?.html) {
|
||||
setEContentType('html');
|
||||
} else {
|
||||
|
|
@ -255,6 +343,20 @@ export default function Email(props: {
|
|||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="邮箱后缀"
|
||||
tooltip="允许的邮箱后缀(如: @qq.com),不填则不校验"
|
||||
>
|
||||
<RenderEmailSuffix
|
||||
t={t}
|
||||
emailSuffix={emailSuffix}
|
||||
onChange={(v) => {
|
||||
if (v !== (config as EmailConfig)?.emailSuffix) {
|
||||
updateConfig(id, config!, 'emailSuffix', v, 'email');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,40 @@ export default OakComponent({
|
|||
config: 1,
|
||||
description: 1,
|
||||
style: 1,
|
||||
// system$platform: {
|
||||
// $entity: 'system',
|
||||
// data: {
|
||||
// id: 1,
|
||||
// name: 1,
|
||||
// config: 1,
|
||||
// description: 1,
|
||||
// super: 1,
|
||||
// folder: 1,
|
||||
// platformId: 1,
|
||||
// style: 1,
|
||||
// domain$system: {
|
||||
// $entity: 'domain',
|
||||
// data: {
|
||||
// id: 1,
|
||||
// systemId: 1,
|
||||
// url: 1,
|
||||
// },
|
||||
// },
|
||||
// application$system: {
|
||||
// $entity: 'application',
|
||||
// data: {
|
||||
// id: 1,
|
||||
// name: 1,
|
||||
// config: 1,
|
||||
// description: 1,
|
||||
// type: 1,
|
||||
// systemId: 1,
|
||||
// domainId: 1,
|
||||
// style: 1,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
formData({ data }) {
|
||||
return data || {};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
.container {
|
||||
padding: 8px;
|
||||
height: 100%;
|
||||
|
||||
.tabLabel {
|
||||
// writing-mode: vertical-rl;
|
||||
letter-spacing: .2rem;
|
||||
}
|
||||
// .tabLabel {
|
||||
// letter-spacing: .2rem;
|
||||
// }
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ export default function render(props: WebComponentProps<EntityDict, 'platform',
|
|||
key: 'system',
|
||||
children: (
|
||||
<PlatformSystem
|
||||
oakPath={`${oakFullpath}.system$platform`}
|
||||
oakPath={`${oakFullpath}-PlatformSystem`}
|
||||
platformId={id}
|
||||
/>
|
||||
),
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ export default OakComponent({
|
|||
}
|
||||
}
|
||||
},
|
||||
filters: [{
|
||||
filter() {
|
||||
return {
|
||||
platformId: this.props.platformId,
|
||||
};
|
||||
},
|
||||
}],
|
||||
properties: {
|
||||
platformId: '',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export default function render(props: WebComponentProps<EntityDict, 'system', tr
|
|||
</Modal>
|
||||
<Tabs
|
||||
type="editable-card"
|
||||
// destroyInactiveTabPane={true}
|
||||
onEdit={(key, action) => {
|
||||
if (action === 'add') {
|
||||
const id = addItem({ platformId, config: { App: {} } });
|
||||
|
|
@ -83,6 +84,7 @@ export default function render(props: WebComponentProps<EntityDict, 'system', tr
|
|||
return {
|
||||
label: item.name,
|
||||
key: `${idx}`,
|
||||
|
||||
children: (
|
||||
<SystemPanel
|
||||
oakPath={`${oakFullpath}.${item.id}`}
|
||||
|
|
|
|||
|
|
@ -18,18 +18,6 @@ export default OakComponent({
|
|||
url: 1,
|
||||
},
|
||||
},
|
||||
// application$system: {
|
||||
// $entity: 'application',
|
||||
// data: {
|
||||
// id: 1,
|
||||
// name: 1,
|
||||
// config: 1,
|
||||
// description: 1,
|
||||
// type: 1,
|
||||
// systemId: 1,
|
||||
// domainId: 1,
|
||||
// }
|
||||
// }
|
||||
},
|
||||
formData({ data }) {
|
||||
return data || {};
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'system', fa
|
|||
destroyInactiveTabPane: true,
|
||||
children: (
|
||||
<Passport
|
||||
oakPath={`$system-passport`}
|
||||
oakPath={`$system-passport-${id}`}
|
||||
systemId={id}
|
||||
systemName={name}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -628,7 +628,8 @@ const i18ns: I18n[] = [
|
|||
"loginWayDisabled": "暂不允许该登录方式",
|
||||
"hasToSetPassword": "需要设置密码",
|
||||
"hasToVerifyPassword": "需要验证密码",
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
|
||||
"emailSuffixIsInvalid": "邮箱后缀不合法"
|
||||
},
|
||||
"distinguishUser": "需要鉴别用户身份",
|
||||
"mobileUnset": "需要先登记手机号",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export type EmailConfig = {
|
|||
html?: string;
|
||||
codeDuration?: number; //验证码有效时间 单位分钟, 不填5分钟
|
||||
digit?: number; //验证码位数 4~8,默认为4位
|
||||
emailSuffix?: string[] //邮箱后缀,不填则不校验
|
||||
};
|
||||
|
||||
export type PfwConfig = {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
"loginWayDisabled": "暂不允许该登录方式",
|
||||
"hasToSetPassword": "需要设置密码",
|
||||
"hasToVerifyPassword": "需要验证密码",
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
|
||||
"cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
|
||||
"emailSuffixIsInvalid": "邮箱后缀不合法"
|
||||
},
|
||||
"distinguishUser": "需要鉴别用户身份",
|
||||
"mobileUnset": "需要先登记手机号",
|
||||
|
|
|
|||
|
|
@ -143,6 +143,16 @@ export type TencentSmsConfig = {
|
|||
endpoint: string;
|
||||
};
|
||||
|
||||
export type EmailConfig = {
|
||||
host: string; //主机名
|
||||
port: number; //端口
|
||||
account: string; //账号
|
||||
password: string; //授权码
|
||||
passwordExpiredAt?: number; //授权码过期时间
|
||||
name?: string; //发件人名称
|
||||
secure?: boolean; //是否ssl
|
||||
};
|
||||
|
||||
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
|
||||
|
||||
export type Config = {
|
||||
|
|
@ -186,14 +196,7 @@ export type Config = {
|
|||
needUploadIDCardPhoto?: boolean; // 实名认证是否需要上传身份照片
|
||||
needManualVerification?: boolean; // 实名认证是否需要人工校验
|
||||
};
|
||||
Emails?: {
|
||||
host: string; //主机名
|
||||
port: number; //端口
|
||||
account: string; //账号
|
||||
password: string; //授权码
|
||||
passwordExpiredAt?: number; //授权码过期时间
|
||||
name?: string; //发件人名称
|
||||
}[];
|
||||
Emails?: EmailConfig[];
|
||||
Security?: {
|
||||
type?: 'password', // 采用密码作为第一安全元素
|
||||
level?: 'weak' | 'medium' | 'strong'; // 强度
|
||||
|
|
|
|||
|
|
@ -28,13 +28,14 @@ interface Attachment extends AttachmentLike {
|
|||
}
|
||||
|
||||
export type EmailOptions = {
|
||||
host: string;
|
||||
port: number;
|
||||
account: string;
|
||||
password: string;
|
||||
subject: string;
|
||||
host?: string;
|
||||
port?: number;
|
||||
secure?: boolean;
|
||||
account?: string;
|
||||
password?: string;
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
attachments?: Attachment[]
|
||||
|
|
|
|||
|
|
@ -3,19 +3,64 @@ import nodemailer from 'nodemailer';
|
|||
import { EntityDict } from '../../oak-app-domain';
|
||||
import { BRC } from '../../types/RuntimeCxt';
|
||||
import Email, { EmailOptions } from '../../types/Email';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
import { BackendRuntimeContext } from '../../context/BackendRuntimeContext';
|
||||
import { get } from 'oak-domain/lib/utils/lodash';
|
||||
import { EmailConfig } from '../../types/Config';
|
||||
|
||||
export default class Nodemailer implements Email<EntityDict> {
|
||||
name = 'nodemailer';
|
||||
|
||||
async getConfig(context: BackendRuntimeContext<EntityDict>, systemId?: string) {
|
||||
let system;
|
||||
if (systemId) {
|
||||
[system] = await context.select(
|
||||
'system',
|
||||
{
|
||||
data: {
|
||||
id: 1,
|
||||
config: 1,
|
||||
},
|
||||
filter: {
|
||||
id: systemId,
|
||||
}
|
||||
},
|
||||
{
|
||||
dontCollect: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
system = context.getApplication()!.system;
|
||||
}
|
||||
const { config: systemConfig } = system as EntityDict['system']['Schema'];
|
||||
const emailConfig = get(systemConfig, 'Emails.0', {}) as EmailConfig;
|
||||
const { host, port, password, account } = emailConfig;
|
||||
assert(host, 'host未配置');
|
||||
assert(port, 'port未配置');
|
||||
assert(account, 'account未配置');
|
||||
assert(password, 'password未配置');
|
||||
return {
|
||||
config: emailConfig,
|
||||
};
|
||||
}
|
||||
async sendEmail(options: EmailOptions, context: BRC<EntityDict>) {
|
||||
const { host, port, account, password, subject, from, text, html, to, attachments } =
|
||||
options;
|
||||
const { subject, from, text, html, to, attachments } = options;
|
||||
|
||||
const {
|
||||
config,
|
||||
} = await this.getConfig(context);
|
||||
|
||||
const transport = Object.assign(config, options);
|
||||
|
||||
const _from = from || (config.name ? `"${config.name}" <${config.account}>` : config.account);
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure: port === 465, //true for 465, false for other ports
|
||||
host: transport.host,
|
||||
port: transport.port,
|
||||
secure: transport.secure,
|
||||
auth: {
|
||||
user: account,
|
||||
pass: password,
|
||||
user: transport.account,
|
||||
pass: transport.password,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -37,7 +82,7 @@ export default class Nodemailer implements Email<EntityDict> {
|
|||
console.log('Server is ready to take our messages');
|
||||
}
|
||||
let mailOptions = {
|
||||
from,
|
||||
from: _from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
|
|
|
|||
Loading…
Reference in New Issue