Skip to content

Commit

Permalink
feat: 增加邮箱认证功能
Browse files Browse the repository at this point in the history
  • Loading branch information
moonrailgun committed Jan 21, 2023
1 parent ba7399f commit 2e774d1
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 26 deletions.
7 changes: 7 additions & 0 deletions client/shared/i18n/langs/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"k1cbe2507": "Confirm",
"k1d8e2fac": "View group details",
"k1ea177bd": "Group privacy control to prevent malicious access to member information through groups",
"k1ea9f5ff": "Verified",
"k206eff71": "Nickname can not be blank",
"k21ee7a1f": "Roles",
"k22856100": "Not found",
Expand All @@ -53,6 +54,7 @@
"k323b5cc7": "Recall",
"k3279c602": "Add now",
"k32905632": "Unlimited invitation link",
"k335c71bf": "OTP code cannot be empty",
"k34b5e3ab": "Send Message",
"k34e357ee": "Group Summary",
"k35abe359": "Lobby",
Expand All @@ -61,6 +63,7 @@
"k3662c0d4": "Please select a panel",
"k378f66fc": "Unmute",
"k393892b6": "Upload original image",
"k3a31dae3": "Verification email sent",
"k3ac17670": "An exception occurred, store create failed",
"k3b4b656d": "About",
"k3bbf3bbd": "Register Account",
Expand Down Expand Up @@ -99,6 +102,7 @@
"k547a7a99": "This record was not found",
"k551b0348": "Password",
"k56f9469b": "No friends yet",
"k570b61fc": "Send verification email to {{email}}",
"k57ab4d97": "Please select user",
"k58a85592": "Is not a valid plugin configuration",
"k5a0084e7": "Modify group configuration",
Expand Down Expand Up @@ -186,6 +190,7 @@
"k9b91079c": "All readed",
"k9bb01902": "Show Detail",
"k9d5a843a": "Mail Service",
"k9d80acdf": "Verify email",
"k9d901c20": "Meeting room",
"k9dfa2c97": "never expires",
"k9f3089ce": "Create",
Expand Down Expand Up @@ -239,6 +244,7 @@
"kb76d94e0": "Refresh",
"kb7a57f24": "Plugin Registry Service",
"kb8185132": "Or",
"kb8ec7062": "Email verification passed",
"kb96b79c5": "Allow management of invitation links",
"kbc76781d": "No permission to send messages, please contact the group owner",
"kbcacf812": "Are you sure to clear the inbox?",
Expand Down Expand Up @@ -274,6 +280,7 @@
"kd455acf4": "Sent successfully, please check your email.",
"kd4ff36fa": "Search Friends",
"kd637a30": "Group Invite Service",
"kd7096593": "Unverified",
"kd8d2b865": "Group Configuration",
"kd955767f": "This invitation code never expires",
"kd983a61a": "{{nickname}} recall a message",
Expand Down
7 changes: 7 additions & 0 deletions client/shared/i18n/langs/zh-CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"k1cbe2507": "确认",
"k1d8e2fac": "查看群组详情",
"k1ea177bd": "群组隐私控制,防止通过群组恶意获取成员信息",
"k1ea9f5ff": "已认证",
"k206eff71": "昵称不能为空",
"k21ee7a1f": "身份组",
"k22856100": "未找到",
Expand All @@ -53,6 +54,7 @@
"k323b5cc7": "撤回",
"k3279c602": "立即添加",
"k32905632": "不限时邀请链接",
"k335c71bf": "校验码不能为空",
"k34b5e3ab": "发送消息",
"k34e357ee": "群组概述",
"k35abe359": "大厅",
Expand All @@ -61,6 +63,7 @@
"k3662c0d4": "请选择面板",
"k378f66fc": "解除禁言",
"k393892b6": "上传原图",
"k3a31dae3": "已发送认证邮件",
"k3ac17670": "出现异常, Store 创建失败",
"k3b4b656d": "关于",
"k3bbf3bbd": "注册账号",
Expand Down Expand Up @@ -99,6 +102,7 @@
"k547a7a99": "没有找到该记录",
"k551b0348": "密码",
"k56f9469b": "暂无好友",
"k570b61fc": "向 {{email}} 发送认证邮件",
"k57ab4d97": "请选择用户",
"k58a85592": "不是一个合法的插件配置",
"k5a0084e7": "修改群组配置",
Expand Down Expand Up @@ -186,6 +190,7 @@
"k9b91079c": "所有已读",
"k9bb01902": "显示详情",
"k9d5a843a": "邮件服务",
"k9d80acdf": "认证邮箱",
"k9d901c20": "会议室",
"k9dfa2c97": "永不过期",
"k9f3089ce": "创建",
Expand Down Expand Up @@ -239,6 +244,7 @@
"kb76d94e0": "刷新",
"kb7a57f24": "插件中心服务",
"kb8185132": "",
"kb8ec7062": "邮箱验证通过",
"kb96b79c5": "允许管理邀请链接",
"kbc76781d": "没有发送消息的权限, 请联系群组所有者",
"kbcacf812": "确认清空收件箱么?",
Expand Down Expand Up @@ -274,6 +280,7 @@
"kd455acf4": "发送成功, 请检查你的邮箱。",
"kd4ff36fa": "查找好友",
"kd637a30": "群组邀请服务",
"kd7096593": "未认证",
"kd8d2b865": "群组配置",
"kd955767f": "该邀请码永不过期",
"kd983a61a": "{{nickname}} 撤回了一条消息",
Expand Down
15 changes: 15 additions & 0 deletions client/shared/model/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface UserBaseInfo {
discriminator: string;
avatar: string | null;
temporary: boolean;
emailVerified: boolean;
extra?: Record<string, unknown>;
}

Expand Down Expand Up @@ -110,6 +111,20 @@ export async function verifyEmail(email: string): Promise<UserLoginInfo> {
return data;
}

/**
* 检查邮箱校验码并更新用户字段
* @param email 邮箱
*/
export async function verifyEmailWithOTP(
emailOTP: string
): Promise<UserLoginInfo> {
const { data } = await request.post('/api/user/verifyEmailWithOTP', {
emailOTP,
});

return data;
}

/**
* 邮箱注册账号
* @param email 邮箱
Expand Down
120 changes: 120 additions & 0 deletions client/web/src/components/modals/EmailVerify.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { setUserJWT } from '@/utils/jwt-helper';
import { setGlobalUserLoginInfo } from '@/utils/user-helper';
import React, { useMemo, useState } from 'react';
import {
model,
showErrorToasts,
showSuccessToasts,
t,
useAppDispatch,
useAsyncRequest,
userActions,
useUserInfo,
} from 'tailchat-shared';
import {
createMetaFormSchema,
MetaFormFieldMeta,
metaFormFieldSchema,
WebMetaForm,
FastifyFormFieldProps,
useFastifyFormContext,
} from 'tailchat-design';
import { ModalWrapper } from '../Modal';
import { Button, Input } from 'antd';
import _compact from 'lodash/compact';
import { getGlobalConfig } from 'tailchat-shared/model/config';
import { Problem } from '../Problem';

interface Values {
emailOTP: string;
[key: string]: unknown;
}

const fields: MetaFormFieldMeta[] = [
{
type: 'text',
name: 'emailOTP',
placeholder: t('6位校验码'),
label: t('邮箱校验码'),
},
];

const schema = createMetaFormSchema({
emailOTP: metaFormFieldSchema
.string()
.length(6, t('校验码为6位'))
.required(t('校验码不能为空')),
});

export const EmailVerify: React.FC<{
onSuccess?: () => void;
}> = React.memo((props) => {
const dispatch = useAppDispatch();
const [sended, setSended] = useState(false);
const userInfo = useUserInfo();

const [{ loading }, handleSendEmail] = useAsyncRequest(async () => {
if (!userInfo) {
return;
}

await model.user.verifyEmail(userInfo.email);
setSended(true);
}, [userInfo?.email]);

const [, handleVerifyEmail] = useAsyncRequest(
async (values: Values) => {
const data = await model.user.verifyEmailWithOTP(values.emailOTP);

setGlobalUserLoginInfo(data);
dispatch(userActions.setUserInfo(data));

showSuccessToasts(t('邮箱验证通过'));

if (typeof props.onSuccess === 'function') {
props.onSuccess();
}
},
[userInfo?.email, props.onSuccess]
);

if (!userInfo) {
return <Problem />;
}

return (
<ModalWrapper title={t('认证邮箱')}>
{!sended ? (
<>
<Button
className="mb-2"
type="primary"
block={true}
size="large"
loading={loading}
onClick={handleSendEmail}
>
{t('向 {{email}} 发送认证邮件', {
email: userInfo.email,
})}
</Button>
<Button
type="text"
block={true}
size="large"
onClick={() => setSended(true)}
>
{t('已发送认证邮件')}
</Button>
</>
) : (
<WebMetaForm
schema={schema}
fields={fields}
onSubmit={handleVerifyEmail}
/>
)}
</ModalWrapper>
);
});
EmailVerify.displayName = 'EmailVerify';
33 changes: 32 additions & 1 deletion client/web/src/components/modals/SettingsView/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { closeModal, pluginUserExtraInfo } from '@/plugin/common';
import { getGlobalSocket } from '@/utils/global-state-helper';
import { setUserJWT } from '@/utils/jwt-helper';
import { setGlobalUserLoginInfo } from '@/utils/user-helper';
import { Button, Divider, Typography } from 'antd';
import { Button, Divider, Tag, Typography } from 'antd';
import React, { useCallback } from 'react';
import { useNavigate } from 'react-router';
import { Avatar } from 'tailchat-design';
Expand All @@ -24,6 +24,7 @@ import {
userActions,
useUserInfo,
} from 'tailchat-shared';
import { EmailVerify } from '../EmailVerify';
import { ModifyPassword } from '../ModifyPassword';

export const SettingsAccount: React.FC = React.memo(() => {
Expand Down Expand Up @@ -111,6 +112,36 @@ export const SettingsAccount: React.FC = React.memo(() => {
onSave={handleUpdateNickName}
/>

<FullModalField
title={t('邮箱')}
content={
<div>
<span className="mr-1">{userInfo.email}</span>
{userInfo.emailVerified ? (
<Tag color="success" className="select-none">
{t('已认证')}
</Tag>
) : (
<Tag
color="warning"
className="cursor-pointer"
onClick={() => {
const key = openModal(
<EmailVerify
onSuccess={() => {
closeModal(key);
}}
/>
);
}}
>
{t('未认证')}
</Tag>
)}
</div>
}
/>

{pluginUserExtraInfo.map((item, i) => {
if (item.component && item.component.editor) {
const Component = item.component.editor;
Expand Down
5 changes: 5 additions & 0 deletions client/web/src/styles/antd/dark.less
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
border-color: #434343;
background: transparent;


&:hover,
&:focus {
color: var(--antd-primary-border);
Expand Down Expand Up @@ -58,6 +59,10 @@
}
}
}

&.ant-btn-text{
border-color: transparent;
}
}

.ant-switch {
Expand Down
3 changes: 3 additions & 0 deletions server/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"k17f8532": "No message found",
"k1b3d8c72": "Group not found",
"k1bdc50f": "Please enter a username with a unique identifier such as: Nickname#0000",
"k206592b2": "Email has been verified",
"k21e507de": "Unable to delete private message",
"k236bb718": "Email sending failed",
"k2bb4fb6d": "OTP incorrect",
"k313eb9b3": "User does not exist, please check your username",
"k3b35c0b0": "No permission to create invitation codes",
Expand Down Expand Up @@ -33,6 +35,7 @@
"kb5971793": "Username or email is empty",
"kb8be9969": "Recall failed, no permission",
"kba207c17": "No permission to view",
"kbb1ef795": "Verification failed, OTP has expired",
"kbb96754b": "Group OP not allowed to be kicked out",
"kc1e668f5": "Not allowed to kick yourself out",
"kc4b77045": "{{nickname}} join this group with invite code from {{creator}}",
Expand Down
3 changes: 3 additions & 0 deletions server/locales/zh-CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"k17f8532": "没有找到消息",
"k1b3d8c72": "群组未找到",
"k1bdc50f": "请输入带唯一标识的用户名 如: Nickname#0000",
"k206592b2": "邮箱已认证",
"k21e507de": "无法删除私人信息",
"k236bb718": "邮件发送失败",
"k2bb4fb6d": "OTP 不正确",
"k313eb9b3": "用户不存在, 请检查您的用户名",
"k3b35c0b0": "没有创建邀请码权限",
Expand Down Expand Up @@ -33,6 +35,7 @@
"kb5971793": "用户名或邮箱为空",
"kb8be9969": "撤回失败, 没有权限",
"kba207c17": "没有查看权限",
"kbb1ef795": "校验失败, OTP已过期",
"kbb96754b": "不允许踢出群组OP",
"kc1e668f5": "不允许踢出自己",
"kc4b77045": "{{nickname}} 通过 {{creator}} 的邀请码加入群组",
Expand Down
2 changes: 2 additions & 0 deletions server/packages/sdk/src/structs/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ export interface UserStruct {
avatar?: string;

type: UserType[];

emailVerified: boolean;
}
Loading

0 comments on commit 2e774d1

Please sign in to comment.