fix: login secret (#6635)

* fix: login secret

* lock

* env template

* fix: ts

* fix: ts

* fix: ts
This commit is contained in:
Archer
2026-03-25 14:45:38 +08:00
committed by GitHub
parent e48a037f2d
commit bd966d479f
54 changed files with 2061 additions and 500 deletions
+2
View File
@@ -16,6 +16,8 @@ export enum LangEnum {
export type localeType = `${LangEnum}`;
export const LocaleList = ['en', 'zh-CN', 'zh-Hant'] as const;
export const LanguageSchema = z.enum(LocaleList).meta({ description: '用户语言偏好' });
export const langMap = {
[LangEnum.en]: {
label: 'English(US)',
@@ -1,6 +1,8 @@
import { AdminInformPath } from './inform';
import { AdminLoginPath } from './login';
import type { OpenAPIPath } from '../../../type';
export const AdminUserPath: OpenAPIPath = {
...AdminInformPath
...AdminInformPath,
...AdminLoginPath
};
@@ -0,0 +1,15 @@
import { z } from 'zod';
// Admin login request body
export const AdminLoginBodySchema = z.object({
username: z.string().meta({ description: '用户名' }),
password: z.string().meta({ description: '密码' })
});
export type AdminLoginBodyType = z.infer<typeof AdminLoginBodySchema>;
// Admin login response
export const AdminLoginResponseSchema = z.object({
user: z.any().meta({ description: '用户信息' }),
token: z.string().meta({ description: '登录令牌' })
});
export type AdminLoginResponseType = z.infer<typeof AdminLoginResponseSchema>;
@@ -0,0 +1,30 @@
import type { OpenAPIPath } from '../../../../type';
import { AdminLoginBodySchema, AdminLoginResponseSchema } from './api';
import { TagsMap } from '../../../../tag';
export const AdminLoginPath: OpenAPIPath = {
'/admin/support/user/login': {
post: {
summary: '管理员登录',
description: '管理员使用用户名和密码登录',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: AdminLoginBodySchema
}
}
},
responses: {
200: {
description: '登录成功',
content: {
'application/json': {
schema: AdminLoginResponseSchema
}
}
}
}
}
}
};
+6 -1
View File
@@ -54,7 +54,12 @@ export const openAPIDocument = createDocument({
},
{
name: '用户体系',
tags: [TagsMap.userInform, TagsMap.walletBill, TagsMap.walletDiscountCoupon]
tags: [
TagsMap.userInform,
TagsMap.walletBill,
TagsMap.walletDiscountCoupon,
TagsMap.userLogin
]
},
{
name: '通用-核心功能',
@@ -0,0 +1,10 @@
import type { OpenAPIPath } from '../../../type';
import { LoginPath } from './login';
import { RegisterPath } from './register';
import { PasswordPath } from './password';
export const UserAccountPath: OpenAPIPath = {
...LoginPath,
...RegisterPath,
...PasswordPath
};
@@ -0,0 +1,110 @@
import { z } from 'zod';
import { OAuthEnum } from '../../../../../support/user/constant';
import { TrackRegisterParamsSchema } from '../../../../../support/marketing/type';
import { LanguageSchema } from '../../../../../common/i18n/type';
export const LoginSuccessResponseSchema = z.object({
user: z.any().meta({
description: '用户详情'
}),
token: z.string().meta({
example: 'eyJhbGciOiJIUzI1NiIs...',
description: '登录令牌'
})
});
export type LoginSuccessResponseType = z.infer<typeof LoginSuccessResponseSchema>;
// ===== Pre login - get login verification code =====
export const PreLoginQuerySchema = z.object({
username: z.string().meta({
example: 'admin',
description: '用户名'
})
});
export type PreLoginQueryType = z.infer<typeof PreLoginQuerySchema>;
export const PreLoginResponseSchema = z
.object({
code: z.string().meta({
example: 'a1b2c3',
description: '预登录验证码'
})
})
.meta({
example: {
code: 'a1b2c3'
}
});
export type PreLoginResponseType = z.infer<typeof PreLoginResponseSchema>;
// ===== Login by password =====
export const LoginByPasswordBodySchema = z
.object({
username: z.string().meta({
example: 'admin',
description: '用户名'
}),
password: z.string().meta({
example: 'hashed_password',
description: '密码'
}),
code: z.string().meta({
example: '123456',
description: '预登录验证码'
}),
language: LanguageSchema.optional().default('zh-CN').meta({
example: 'zh-CN',
description: '用户语言偏好'
})
})
.meta({
example: {
username: 'admin',
password: 'hashed_password',
code: '123456',
language: 'zh-CN'
}
});
export type LoginByPasswordBodyType = z.infer<typeof LoginByPasswordBodySchema>;
/* ===== Wecom Login ===== */
export const WecomGetRedirectURLBodySchema = z.object({
redirectUri: z.string(),
state: z.string(),
isWecomWorkTerminal: z.boolean()
});
export const WecomGetRedirectURLResponseSchema = z.string();
export type WecomGetRedirectURLBodyType = z.infer<typeof WecomGetRedirectURLBodySchema>;
export type WecomGetRedirectURLResponseType = z.infer<typeof WecomGetRedirectURLResponseSchema>;
// ===== OAuth Login =====
export const OauthLoginBodySchema = TrackRegisterParamsSchema.extend({
type: z.enum(OAuthEnum).meta({ description: 'OAuth 登录类型' }),
callbackUrl: z.string().meta({ description: '回调 URL' }),
props: z.record(z.string(), z.string()).meta({ description: '附加属性' }),
language: LanguageSchema.optional().meta({ description: '语言' })
});
export type OauthLoginBodyType = z.infer<typeof OauthLoginBodySchema>;
// ===== Fast Login =====
export const FastLoginBodySchema = z.object({
token: z.string().meta({ description: 'Token' }),
code: z.string().meta({ description: 'Code' })
});
export type FastLoginBodyType = z.infer<typeof FastLoginBodySchema>;
// ===== WeChat Login Result =====
export const WxLoginBodySchema = z.object({
inviterId: z.string().optional().meta({ description: '邀请人 ID' }),
code: z.string().meta({ description: '微信登录 Code' }),
bd_vid: z.string().optional(),
msclkid: z.string().optional(),
fastgpt_sem: z.string().optional(),
sourceDomain: z.string().optional()
});
export type WxLoginBodyType = z.infer<typeof WxLoginBodySchema>;
export const GetWXLoginQRResponseSchema = z.object({
code: z.string().meta({ description: '微信登录 Code' }),
codeUrl: z.string().meta({ description: '微信登录二维码 URL' })
});
export type GetWXLoginQRResponseType = z.infer<typeof GetWXLoginQRResponseSchema>;
@@ -0,0 +1,178 @@
import type { OpenAPIPath } from '../../../../type';
import { TagsMap } from '../../../../tag';
import {
LoginByPasswordBodySchema,
PreLoginQuerySchema,
PreLoginResponseSchema,
OauthLoginBodySchema,
FastLoginBodySchema,
WxLoginBodySchema,
GetWXLoginQRResponseSchema,
LoginSuccessResponseSchema
} from './api';
import { UserSchema } from '../../../../../support/user/type';
export const LoginPath: OpenAPIPath = {
'/support/user/account/tokenLogin': {
get: {
summary: 'Token 登录',
description: '通过已有的登录令牌获取用户信息',
tags: [TagsMap.userLogin],
responses: {
200: {
description: '成功获取用户信息',
content: {
'application/json': {
schema: UserSchema
}
}
}
}
}
},
'/support/user/account/preLogin': {
get: {
summary: '预登录获取验证码',
description: '通过用户名获取预登录验证码,用于密码登录时的验证',
tags: [TagsMap.userLogin],
requestParams: {
query: PreLoginQuerySchema
},
responses: {
200: {
description: '成功获取预登录验证码',
content: {
'application/json': {
schema: PreLoginResponseSchema
}
}
}
}
}
},
'/support/user/account/loginByPassword': {
post: {
summary: '用户密码登录',
description: '通过用户名和密码进行登录,需要先获取预登录验证码',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: LoginByPasswordBodySchema
}
}
},
responses: {
200: {
description: '登录成功,返回用户信息和令牌',
content: {
'application/json': {
schema: LoginSuccessResponseSchema
}
}
}
}
}
},
'/proApi/support/user/account/login/oauth': {
post: {
summary: 'OAuth 登录',
description: '使用第三方 OAuth 授权登录',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: OauthLoginBodySchema
}
}
},
responses: {
200: {
description: '登录成功',
content: {
'application/json': {
schema: LoginSuccessResponseSchema
}
}
}
}
}
},
'/proApi/support/user/account/login/fastLogin': {
post: {
summary: '快捷登录',
description: '使用 Token 和 Code 进行快捷登录',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: FastLoginBodySchema
}
}
},
responses: {
200: {
description: '登录成功',
content: {
'application/json': {
schema: LoginSuccessResponseSchema
}
}
}
}
}
},
'/proApi/support/user/account/login/wx/getQR': {
get: {
summary: '获取微信登录二维码',
description: '获取微信登录二维码',
tags: [TagsMap.userLogin],
responses: {
200: {
description: '获取微信登录二维码成功',
content: {
'application/json': {
schema: GetWXLoginQRResponseSchema
}
}
}
}
}
},
'/proApi/support/user/account/login/wx/getResult': {
post: {
summary: '获取微信登录结果',
description: '提交微信登录 Code 以获取登录结果',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: WxLoginBodySchema
}
}
},
responses: {
200: {
description: '登录成功',
content: {
'application/json': {
schema: LoginSuccessResponseSchema
}
}
}
}
}
},
'/support/user/account/loginout': {
get: {
summary: '退出登录',
description: '退出当前用户的所有会话并清除登录凭证',
tags: [TagsMap.userLogin],
responses: {
200: {
description: '退出登录成功'
}
}
}
}
};
@@ -1,12 +0,0 @@
import { z } from 'zod';
export const WecomGetRedirectURLBodySchema = z.object({
redirectUri: z.string(),
state: z.string(),
isWecomWorkTerminal: z.boolean()
});
export const WecomGetRedirectURLResponseSchema = z.string();
export type WecomGetRedirectURLBodyType = z.infer<typeof WecomGetRedirectURLBodySchema>;
export type WecomGetRedirectURLResponseType = z.infer<typeof WecomGetRedirectURLResponseSchema>;
@@ -0,0 +1,62 @@
import { z } from 'zod';
// ===== Update password by old password =====
export const UpdatePasswordByOldBodySchema = z
.object({
oldPsw: z.string().trim().min(1).meta({
example: 'hashed_old_password',
description: '旧密码(已加密)'
}),
newPsw: z.string().trim().min(1).meta({
example: 'hashed_new_password',
description: '新密码(已加密)'
})
})
.meta({
example: {
oldPsw: 'hashed_old_password',
newPsw: 'hashed_new_password'
}
});
export type UpdatePasswordByOldBodyType = z.infer<typeof UpdatePasswordByOldBodySchema>;
export const UpdatePasswordByOldResponseSchema = z.any().meta({
description: '用户信息'
});
export type UpdatePasswordByOldResponseType = z.infer<typeof UpdatePasswordByOldResponseSchema>;
// ===== Check password expired =====
export const CheckPswExpiredResponseSchema = z.boolean().meta({
example: false,
description: '密码是否已过期'
});
export type CheckPswExpiredResponseType = z.infer<typeof CheckPswExpiredResponseSchema>;
// ===== Reset expired password =====
export const ResetExpiredPswBodySchema = z
.object({
newPsw: z.string().trim().min(1).meta({
example: 'hashed_new_password',
description: '新密码(已加密)'
})
})
.meta({
example: {
newPsw: 'hashed_new_password'
}
});
export type ResetExpiredPswBodyType = z.infer<typeof ResetExpiredPswBodySchema>;
export const ResetExpiredPswResponseSchema = z.object({}).meta({
description: '重置成功'
});
export type ResetExpiredPswResponseType = z.infer<typeof ResetExpiredPswResponseSchema>;
// ===== Find Password (update by code) =====
export const UpdatePasswordByCodeBodySchema = z.object({
username: z.string().trim().min(1).meta({ description: '用户名' }),
code: z.string().meta({ description: '验证码' }),
password: z.string().trim().min(1).meta({ description: '新密码' }),
tmbId: z.string().optional().meta({ description: '团队成员 ID(可选)' })
});
export type UpdatePasswordByCodeBodyType = z.infer<typeof UpdatePasswordByCodeBodySchema>;
@@ -0,0 +1,102 @@
import type { OpenAPIPath } from '../../../../type';
import { TagsMap } from '../../../../tag';
import {
UpdatePasswordByOldBodySchema,
UpdatePasswordByOldResponseSchema,
CheckPswExpiredResponseSchema,
ResetExpiredPswBodySchema,
ResetExpiredPswResponseSchema,
UpdatePasswordByCodeBodySchema
} from './api';
export const PasswordPath: OpenAPIPath = {
'/support/user/account/updatePasswordByOld': {
post: {
summary: '通过旧密码修改密码',
description: '使用旧密码验证后修改为新密码,修改成功后其他会话将被注销',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: UpdatePasswordByOldBodySchema
}
}
},
responses: {
200: {
description: '密码修改成功',
content: {
'application/json': {
schema: UpdatePasswordByOldResponseSchema
}
}
}
}
}
},
'/support/user/account/checkPswExpired': {
get: {
summary: '检查密码是否过期',
description: '检查当前用户的密码是否已过期,需要强制修改',
tags: [TagsMap.userLogin],
responses: {
200: {
description: '返回密码是否过期',
content: {
'application/json': {
schema: CheckPswExpiredResponseSchema
}
}
}
}
}
},
'/support/user/account/resetExpiredPsw': {
post: {
summary: '重置过期密码',
description: '当密码过期时,使用此接口重置密码,重置后其他会话将被注销',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: ResetExpiredPswBodySchema
}
}
},
responses: {
200: {
description: '密码重置成功',
content: {
'application/json': {
schema: ResetExpiredPswResponseSchema
}
}
}
}
}
},
'/support/user/account/password/updateByCode': {
post: {
summary: '通过验证码找回/修改密码',
description: '通过邮箱/手机验证码找回或修改密码',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: UpdatePasswordByCodeBodySchema
}
}
},
responses: {
200: {
description: '修改成功',
content: {
'application/json': {
schema: {}
}
}
}
}
}
}
};
@@ -0,0 +1,13 @@
import { z } from 'zod';
import { TrackRegisterParamsSchema } from '../../../../../support/marketing/type';
import { LanguageSchema } from '../../../../../common/i18n/type';
// ===== Register by email or phone =====
export const AccountRegisterBodySchema = TrackRegisterParamsSchema.extend({
username: z.string().meta({ description: '用户名(邮箱或手机号)' }),
code: z.string().meta({ description: '验证码' }),
password: z.string().meta({ description: '密码(已加密)' }),
language: LanguageSchema.optional().meta({ description: '语言' })
});
export type AccountRegisterBodyType = z.infer<typeof AccountRegisterBodySchema>;
@@ -0,0 +1,30 @@
import type { OpenAPIPath } from '../../../../type';
import { TagsMap } from '../../../../tag';
import { AccountRegisterBodySchema } from './api';
export const RegisterPath: OpenAPIPath = {
'/support/user/account/register/emailAndPhone': {
post: {
summary: '邮箱/手机号注册',
description: '使用邮箱或手机号验证码注册新账号',
tags: [TagsMap.userLogin],
requestBody: {
content: {
'application/json': {
schema: AccountRegisterBodySchema
}
}
},
responses: {
200: {
description: '注册成功',
content: {
'application/json': {
schema: {}
}
}
}
}
}
}
};
@@ -1,6 +1,8 @@
import { UserInformPath } from './inform';
import type { OpenAPIPath } from '../../type';
import { UserAccountPath } from './account';
export const UserPath: OpenAPIPath = {
...UserInformPath
...UserInformPath,
...UserAccountPath
};
+1
View File
@@ -42,6 +42,7 @@ export const TagsMap = {
customDomain: '自定义域名',
// User
userInform: '用户通知',
userLogin: '用户账号',
/* Common */
// APIKey
-33
View File
@@ -1,39 +1,6 @@
import type { MemberGroupSchemaType } from '../permission/memberGroup/type';
import { MemberGroupListItemType } from '../permission/memberGroup/type';
import type { OAuthEnum } from './constant';
import { TeamMemberStatusEnum } from './team/constant';
import type { OrgType } from './team/org/type';
import type { TeamMemberItemType } from './team/type';
import type { LangEnum } from '../../common/i18n/type';
import type { TrackRegisterParams } from '../marketing/type';
export type PostLoginProps = {
username: string;
password: string;
code: string;
language?: `${LangEnum}`;
};
export type OauthLoginProps = {
type: `${OAuthEnum}`;
callbackUrl: string;
props: Record<string, string>;
language?: `${LangEnum}`;
} & TrackRegisterParams;
export type WxLoginProps = {
inviterId?: string;
code: string;
bd_vid?: string;
msclkid?: string;
fastgpt_sem?: string;
sourceDomain?: string;
};
export type FastLoginProps = {
token: string;
code: string;
};
export type SearchResult = {
members: Omit<TeamMemberItemType, 'teamId' | 'permission'>[];
-14
View File
@@ -1,14 +0,0 @@
import type { LangEnum } from '../../../common/i18n/type';
import type { TrackRegisterParams } from '../../marketing/type';
export type GetWXLoginQRResponse = {
code: string;
codeUrl: string;
};
export type AccountRegisterBody = {
username: string;
code: string;
password: string;
language?: `${LangEnum}`;
} & TrackRegisterParams;
+40 -35
View File
@@ -1,13 +1,28 @@
import type { TeamMetaType, UserModelSchema } from '../type';
import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
import { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
import type { GroupMemberRole } from '../../permission/memberGroup/constant';
import type { TeamPermission } from '../../permission/user/controller';
import { TeamPermission } from '../../permission/user/controller';
import { z } from 'zod';
export type ThirdPartyAccountType = {
lafAccount?: LafAccountType;
openaiAccount?: OpenaiAccountType;
externalWorkflowVariables?: Record<string, string>;
};
export const LafAccountSchema = z.object({
appid: z.string(),
token: z.string(),
pat: z.string()
});
export type LafAccountType = z.infer<typeof LafAccountSchema>;
export const OpenaiAccountSchema = z.object({
key: z.string(),
baseUrl: z.string()
});
export type OpenaiAccountType = z.infer<typeof OpenaiAccountSchema>;
export const ThidPartyAccountSchema = z.object({
lafAccount: LafAccountSchema.optional(),
openaiAccount: OpenaiAccountSchema.optional(),
externalWorkflowVariables: z.record(z.string(), z.string()).optional()
});
export type ThirdPartyAccountType = z.infer<typeof ThidPartyAccountSchema>;
export type TeamSchema = {
_id: string;
@@ -45,7 +60,7 @@ export type TeamMemberSchema = {
createTime: Date;
updateTime?: Date;
name: string;
role: `${TeamMemberRoleEnum}`;
role: TeamMemberRoleEnum;
status: TeamMemberStatusEnum;
avatar: string;
};
@@ -55,22 +70,23 @@ export type TeamMemberWithTeamAndUserSchema = TeamMemberSchema & {
user: UserModelSchema;
};
export type TeamTmbItemType = {
userId: string;
teamId: string;
teamAvatar?: string;
teamName: string;
memberName: string;
avatar: string;
balance?: number;
tmbId: string;
teamDomain: string;
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
notificationAccount?: string;
permission: TeamPermission;
isWecomTeam?: boolean;
} & ThirdPartyAccountType;
export const TeamTmbItemSchema = ThidPartyAccountSchema.extend({
userId: z.string(),
teamId: z.string(),
teamAvatar: z.string().optional(),
teamName: z.string(),
memberName: z.string(),
avatar: z.string(),
balance: z.number().optional(),
tmbId: z.string(),
teamDomain: z.string(),
role: z.enum(TeamMemberRoleEnum),
status: z.enum(TeamMemberStatusEnum),
notificationAccount: z.string().optional(),
permission: z.instanceof(TeamPermission),
isWecomTeam: z.boolean().optional()
});
export type TeamTmbItemType = z.infer<typeof TeamTmbItemSchema>;
export type TeamMemberItemType<
Options extends {
@@ -110,17 +126,6 @@ export type TeamTagItemType = {
key: string;
};
export type LafAccountType = {
appid: string;
token: string;
pat: string;
};
export type OpenaiAccountType = {
key: string;
baseUrl: string;
};
export type TeamInvoiceHeaderType = {
teamName: string;
unifiedCreditCode: string;
+16 -15
View File
@@ -1,9 +1,9 @@
import z from 'zod';
import type { LangEnum } from '../../common/i18n/type';
import type { TeamPermission } from '../permission/user/controller';
import { LanguageSchema, type LangEnum } from '../../common/i18n/type';
import { TeamPermission } from '../permission/user/controller';
import type { UserStatusEnum } from './constant';
import { TeamMemberStatusEnum } from './team/constant';
import type { TeamTmbItemType } from './team/type';
import { TeamTmbItemSchema } from './team/type';
export const UserTagsEnum = z.enum(['wecom']);
export type UserTagsEnum = z.infer<typeof UserTagsEnum>;
@@ -33,18 +33,19 @@ export type UserModelSchema = {
meta?: UserMetaType;
};
export type UserType = {
_id: string;
username: string;
avatar: string; // it should be team member's avatar after 4.8.18
timezone: string;
language?: `${LangEnum}`;
promotionRate: UserModelSchema['promotionRate'];
team: TeamTmbItemType;
permission: TeamPermission;
contact?: string;
tags?: UserTagsEnum[];
};
export const UserSchema = z.object({
_id: z.string(),
username: z.string(),
avatar: z.string(),
timezone: z.string(),
language: LanguageSchema.optional(),
promotionRate: z.number(),
team: TeamTmbItemSchema,
permission: z.instanceof(TeamPermission),
contact: z.string().optional(),
tags: z.array(UserTagsEnum).optional()
});
export type UserType = z.infer<typeof UserSchema>;
export const SourceMemberSchema = z.object({
name: z.string().meta({ example: '张三', description: '成员名称' }),