diff --git a/client/web/src/components/modals/SettingsView/Account.tsx b/client/web/src/components/modals/SettingsView/Account.tsx index 63a38af3..126f9708 100644 --- a/client/web/src/components/modals/SettingsView/Account.tsx +++ b/client/web/src/components/modals/SettingsView/Account.tsx @@ -24,11 +24,11 @@ import { } from 'tailchat-shared'; import { EmailVerify } from '../EmailVerify'; import { ModifyPassword } from '../ModifyPassword'; +import { isBuiltinEmail } from '@/utils/user-helper'; export const SettingsAccount: React.FC = React.memo(() => { const userInfo = useUserInfo(); const dispatch = useAppDispatch(); - const navigate = useNavigate(); const userExtra = userInfo?.extra ?? {}; const [, handleUserAvatarChange] = useAsyncRequest( @@ -113,7 +113,11 @@ export const SettingsAccount: React.FC = React.memo(() => { content={
{userInfo.email} - {userInfo.emailVerified ? ( + {isBuiltinEmail(userInfo.email) ? ( + + {t('内置邮箱')} + + ) : userInfo.emailVerified ? ( {t('已认证')} diff --git a/client/web/src/utils/user-helper.ts b/client/web/src/utils/user-helper.ts index 7895ad50..139ff797 100644 --- a/client/web/src/utils/user-helper.ts +++ b/client/web/src/utils/user-helper.ts @@ -39,3 +39,28 @@ export async function tryAutoLogin(): Promise { return userLoginInfo; } + +/** + * 是否为内置邮箱 + * + * 内置邮箱则为非用户输入,通过其他途径进来但是没有邮箱属于自动补全的邮箱类型 + * 比如临时用户,比如iam + */ +export function isBuiltinEmail(email: string): boolean { + if (typeof email !== 'string') { + // 一般来说内置邮箱都是表示不可用状态,因此如果不是合法输入直接视为true + return true; + } + + const domain = email.split('@')[1]; + + if (!domain) { + return true; + } + + if (domain.endsWith('.msgbyte.com')) { + return true; + } + + return false; +} diff --git a/server/plugins/com.msgbyte.iam/services/iam.service.ts b/server/plugins/com.msgbyte.iam/services/iam.service.ts index ceb534f0..d05b9453 100644 --- a/server/plugins/com.msgbyte.iam/services/iam.service.ts +++ b/server/plugins/com.msgbyte.iam/services/iam.service.ts @@ -84,9 +84,14 @@ class IAMService extends TcService { }); } + const username = providerUserInfo.username; + const email = + providerUserInfo.email || + `${username}.${strategyName}@iam.msgbyte.com`; + // 不存在记录,查找是否已经注册过,如果已经注册过需要绑定,如果没有注册过则创建账号并绑定用户关系 const userInfo = await ctx.call('user.findUserByEmail', { - email: providerUserInfo.email, + email, }); if (!!userInfo) { // 用户已存在,需要登录后才能确定绑定关系 @@ -114,7 +119,7 @@ class IAMService extends TcService { const newUserInfo: UserStructWithToken = await ctx.call( 'user.register', { - email: providerUserInfo.email, + email, nickname: providerUserInfo.nickname, password: String(new db.Types.ObjectId()), // random password avatar, diff --git a/server/plugins/com.msgbyte.iam/strategies/github.ts b/server/plugins/com.msgbyte.iam/strategies/github.ts index 9fbc80a5..96cf3a30 100644 --- a/server/plugins/com.msgbyte.iam/strategies/github.ts +++ b/server/plugins/com.msgbyte.iam/strategies/github.ts @@ -46,13 +46,20 @@ export const GithubStrategy: StrategyType = { Authorization: `token ${accessToken}`, }, }) - .json<{ id: number; name: string; email: string; avatar_url: string }>(); + .json<{ + id: number; + login: string; + name: string; + email: string; + avatar_url: string; + }>(); console.log(`[github oauth] user info:`, result); return { id: String(result.id), nickname: result.name, + username: result.login, email: result.email, avatar: result.avatar_url, }; diff --git a/server/plugins/com.msgbyte.iam/strategies/types.ts b/server/plugins/com.msgbyte.iam/strategies/types.ts index 687823f7..66d09b0f 100644 --- a/server/plugins/com.msgbyte.iam/strategies/types.ts +++ b/server/plugins/com.msgbyte.iam/strategies/types.ts @@ -7,6 +7,7 @@ export interface StrategyType { getUserInfo: (code: string) => Promise<{ id: string; nickname: string; + username: string; email: string; avatar: string; }>; diff --git a/server/plugins/com.msgbyte.iam/web/plugins/com.msgbyte.iam/src/IAMAction.tsx b/server/plugins/com.msgbyte.iam/web/plugins/com.msgbyte.iam/src/IAMAction.tsx index ff53b6e0..41aec224 100644 --- a/server/plugins/com.msgbyte.iam/web/plugins/com.msgbyte.iam/src/IAMAction.tsx +++ b/server/plugins/com.msgbyte.iam/web/plugins/com.msgbyte.iam/src/IAMAction.tsx @@ -30,6 +30,8 @@ export const IAMAction: React.FC = React.memo(() => { if (payload.type === 'existed') { showToasts(Translate.accountExistedTip, 'warning'); + } else if (payload.type === 'infoDeviant') { + showToasts(Translate.infoDeviantTip, 'error'); } else if (payload.type === 'token') { const token = payload.token; setUserJWT(token) diff --git a/server/plugins/com.msgbyte.iam/web/plugins/com.msgbyte.iam/src/translate.ts b/server/plugins/com.msgbyte.iam/web/plugins/com.msgbyte.iam/src/translate.ts index 6a708363..56617b3b 100644 --- a/server/plugins/com.msgbyte.iam/web/plugins/com.msgbyte.iam/src/translate.ts +++ b/server/plugins/com.msgbyte.iam/web/plugins/com.msgbyte.iam/src/translate.ts @@ -15,6 +15,12 @@ export const Translate = { 'zh-CN': '账号已存在,请使用账号密码登录', 'en-US': 'Account Existed, please log in with account password', }), + infoDeviantTip: localTrans({ + // 'zh-CN': '账号已存在,你应该在登录后绑定账号', + // 'en-US': 'Account Existed, You should bind provider account after login', + 'zh-CN': '账号信息异常,请使用账号密码登录', + 'en-US': 'Account Info Deviant, please log in with account password', + }), notSupportMobile: localTrans({ 'zh-CN': '第三方登录功能暂不支持移动端使用', 'en-US': 'The third-party login function does not support mobile use', diff --git a/server/services/core/user/user.service.ts b/server/services/core/user/user.service.ts index 62be7bb8..e9b5b16d 100644 --- a/server/services/core/user/user.service.ts +++ b/server/services/core/user/user.service.ts @@ -200,6 +200,12 @@ class UserService extends TcService { email: 'string', }, }); + this.registerAction('findUserByUsername', this.findUserByUsername, { + visibility: 'public', + params: { + username: 'string', + }, + }); this.registerAction('updateUserField', this.updateUserField, { params: { fieldName: 'string', @@ -537,7 +543,7 @@ class UserService extends TcService { const password = await this.hashPassword(generateRandomStr()); const doc = await this.adapter.insert({ - email: `${generateRandomStr()}.temporary@msgbyte.com`, + email: `${generateRandomStr()}@temporary.msgbyte.com`, password, nickname, discriminator, @@ -861,6 +867,29 @@ class UserService extends TcService { return user; } + /** + * 通过用户邮箱查找用户 + */ + async findUserByUsername( + ctx: TcContext<{ + username: string; + }> + ): Promise { + const username = ctx.params.username; + + const doc = await this.adapter.model.findOne({ + username, + }); + + if (!doc) { + return null; + } + + const user = await this.transformDocuments(ctx, {}, doc); + + return user; + } + /** * 修改用户字段 */ @@ -1073,7 +1102,7 @@ class UserService extends TcService { } /** - * 根据用户邮件获取开放平台机器人id + * 根据用户邮箱获取开放平台机器人id */ findOpenapiBotId(ctx: TcContext<{ email: string }>): string { return this.parseOpenapiBotEmail(ctx.params.email); @@ -1216,18 +1245,23 @@ class UserService extends TcService { } private buildPluginBotEmail(botId: string) { - return `${botId}@tailchat-plugin.com`; + return `${botId}@plugin.msgbyte.com`; } private buildOpenapiBotEmail(botId: string) { - return `${botId}@tailchat-openapi.com`; + return `${botId}@openapi.msgbyte.com`; } private parseOpenapiBotEmail(email: string): string | null { if (email.endsWith('@tailchat-openapi.com')) { + // 旧的实现,兼容代码 return email.replace('@tailchat-openapi.com', ''); } + if (email.endsWith('@openapi.msgbyte.com')) { + return email.replace('@openapi.msgbyte.com', ''); + } + return null; }