Skip to content

Commit 6dd28fb

Browse files
fix:prevent other team members from resuming enterprise auth tasks
1 parent 5e93cb1 commit 6dd28fb

24 files changed

Lines changed: 445 additions & 93 deletions

File tree

packages/global/support/user/team/enterpriseAuth/type.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { TeamEnterpriseAuthTaskStatusEnum } from './constant';
55
export const EnterpriseAuthTaskSchema = z.object({
66
_id: ObjectIdSchema,
77
teamId: ObjectIdSchema,
8+
userId: ObjectIdSchema.optional(),
9+
tmbId: ObjectIdSchema.optional(),
810
taskId: z.string(),
911
status: z.enum(TeamEnterpriseAuthTaskStatusEnum),
1012
enterpriseName: z.string(),

packages/service/support/user/team/enterpriseAuth/common.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,29 @@ export const isEnterpriseAuthTimesExhausted = (usedTimes?: number) =>
7474

7575
export const buildVerifiedEnterpriseName = (auth?: TeamEnterpriseAuthType | null) =>
7676
auth?.enterpriseName;
77+
78+
/**
79+
* 判断当前操作者是否为企业认证任务发起人。
80+
*
81+
* 历史任务可能没有操作者字段,为避免已在流程中的团队被升级阻断,缺失 tmbId 时保留旧任务可继续处理。
82+
* 新任务都会写入 tmbId,其他团队成员访问时会被拦截为 processing。
83+
*/
84+
export const isEnterpriseAuthTaskOperator = ({
85+
task,
86+
operator
87+
}: {
88+
task?: EnterpriseAuthTaskType | null;
89+
operator: AuthOperator;
90+
}) => !task?.tmbId || task.tmbId.toString() === operator.tmbId;
91+
92+
export const assertEnterpriseAuthTaskOperator = ({
93+
task,
94+
operator
95+
}: {
96+
task?: EnterpriseAuthTaskType | null;
97+
operator: AuthOperator;
98+
}) => {
99+
if (!isEnterpriseAuthTaskOperator({ task, operator })) {
100+
throw new Error(EnterpriseAuthErrEnum.processing);
101+
}
102+
};

packages/service/support/user/team/enterpriseAuth/readModel.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import type {
88
} from '@fastgpt/global/support/user/team/enterpriseAuth/type';
99
import { serviceEnv } from '../../../../env';
1010
import {
11+
assertEnterpriseAuthTaskOperator,
12+
type AuthOperator,
1113
buildVerifiedEnterpriseName,
1214
enabledGuard,
1315
getDefaultAuthStatusRecord,
16+
isEnterpriseAuthTaskOperator,
1417
pendingTaskStatuses,
1518
type EnterpriseAuthReadonlyStatusRecord
1619
} from './common';
@@ -86,9 +89,11 @@ const buildReadonlyAuthStatusRecord = (
8689
* 若认证服务 URL 未配置则直接返回 disabled;否则查询当前认证记录并推导展示态,不触发写库。
8790
*/
8891
export const getEnterpriseAuthStatus = async ({
92+
operator,
8993
teamId,
9094
canManage
9195
}: {
96+
operator?: AuthOperator;
9297
teamId: string;
9398
canManage: boolean;
9499
}) => {
@@ -108,7 +113,11 @@ export const getEnterpriseAuthStatus = async ({
108113
usedTimes: record.usedTimes,
109114
canManage,
110115
verifiedEnterpriseName: record.verifiedEnterpriseName,
111-
currentTask: buildLightTask(record),
116+
currentTask:
117+
!record.currentTask ||
118+
(operator && isEnterpriseAuthTaskOperator({ task: record.currentTask, operator }))
119+
? buildLightTask(record)
120+
: undefined,
112121
lastErrorCode: record.lastErrorCode,
113122
lastErrorMessage: record.lastErrorMessage
114123
};
@@ -118,13 +127,14 @@ export const getEnterpriseAuthStatus = async ({
118127
* 获取当前对公打款任务的敏感详情。
119128
* 仅在对公打款待确认阶段可调用,会先尝试过期超时任务;无有效任务时抛出 taskNotFound。
120129
*/
121-
export const getEnterpriseAuthCurrentTaskDetail = async (teamId: string) => {
130+
export const getEnterpriseAuthCurrentTaskDetail = async (operator: AuthOperator) => {
122131
enabledGuard();
123132

124133
const task =
125-
(await expireCurrentTaskIfNeeded(teamId)) ||
126-
(await MongoTeamEnterpriseAuthTask.findOne({ teamId }).lean());
134+
(await expireCurrentTaskIfNeeded(operator.teamId)) ||
135+
(await MongoTeamEnterpriseAuthTask.findOne({ teamId: operator.teamId }).lean());
127136
if (!task) throw new Error(EnterpriseAuthErrEnum.taskNotFound);
137+
assertEnterpriseAuthTaskOperator({ task, operator });
128138

129139
const terminalError = toTerminalTaskError(task);
130140
if (terminalError) throw new Error(terminalError);

packages/service/support/user/team/enterpriseAuth/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ const TeamEnterpriseAuthTaskSchema = new Schema({
4747
ref: TeamCollectionName,
4848
required: true
4949
},
50+
userId: { type: Schema.Types.ObjectId },
51+
tmbId: { type: Schema.Types.ObjectId },
5052
taskId: { type: String, required: true },
5153
status: {
5254
type: String,

packages/service/support/user/team/enterpriseAuth/startTask.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
TeamEnterpriseAuthTaskStatusEnum
99
} from '@fastgpt/global/support/user/team/enterpriseAuth/constant';
1010
import {
11+
assertEnterpriseAuthTaskOperator,
12+
type AuthOperator,
1113
createEnterpriseAuthTaskId,
1214
enabledGuard,
1315
isEnterpriseAuthTimesExhausted,
@@ -39,9 +41,11 @@ const getCurrentEnterpriseAuthTask = async (teamId: string) => {
3941

4042
const checkStartPreconditions = async ({
4143
teamId,
44+
operator,
4245
normalizedUnifiedCreditCode
4346
}: {
4447
teamId: string;
48+
operator: AuthOperator;
4549
normalizedUnifiedCreditCode: string;
4650
}) => {
4751
await expireCurrentTaskIfNeeded(teamId);
@@ -57,6 +61,7 @@ const checkStartPreconditions = async ({
5761

5862
const usedTimes = teamTask?.usedTimes ?? 0;
5963
if (currentTask) {
64+
assertEnterpriseAuthTaskOperator({ task: currentTask, operator });
6065
return {
6166
restore: true as const,
6267
task: currentTask
@@ -98,9 +103,11 @@ const startingTaskCleanupUnset = {
98103

99104
const buildStartConflictResponse = async ({
100105
teamId,
106+
operator,
101107
normalizedUnifiedCreditCode
102108
}: {
103109
teamId: string;
110+
operator: AuthOperator;
104111
normalizedUnifiedCreditCode: string;
105112
}) => {
106113
const [verifiedAfterPrecheck, currentTask, teamTask] = await Promise.all([
@@ -118,6 +125,7 @@ const buildStartConflictResponse = async ({
118125
throw new Error(EnterpriseAuthErrEnum.enterpriseOccupied);
119126
}
120127
if (currentTask) {
128+
assertEnterpriseAuthTaskOperator({ task: currentTask, operator });
121129
return {
122130
status: TeamEnterpriseAuthStatusEnum.verifying,
123131
currentTask: buildLightTask({ currentTask }),
@@ -179,18 +187,23 @@ const markStartAsFailed = async ({
179187
* 发起企业认证。外部打款调用不放进 Mongo 事务,抢到本地 starting 任务后再调用服务。
180188
*/
181189
export const startEnterpriseAuth = async ({
182-
teamId,
190+
operator,
183191
data
184192
}: {
185-
teamId: string;
193+
operator: AuthOperator;
186194
data: StartEnterpriseAuthBodyType;
187195
}) => {
188196
enabledGuard();
189197
serviceConfigGuard();
190198

199+
const { teamId } = operator;
191200
const normalizedUnifiedCreditCode = normalizeUnifiedCreditCode(data.unifiedCreditCode);
192201
const bankAccount = normalizeBankAccount(data.bankAccount);
193-
const precheck = await checkStartPreconditions({ teamId, normalizedUnifiedCreditCode });
202+
const precheck = await checkStartPreconditions({
203+
teamId,
204+
operator,
205+
normalizedUnifiedCreditCode
206+
});
194207
if (precheck.restore) {
195208
return {
196209
status: TeamEnterpriseAuthStatusEnum.verifying,
@@ -205,6 +218,8 @@ export const startEnterpriseAuth = async ({
205218
const taskId = createEnterpriseAuthTaskId();
206219
const startingTask = {
207220
teamId: toObjectId(teamId),
221+
userId: toObjectId(operator.userId),
222+
tmbId: toObjectId(operator.tmbId),
208223
taskId,
209224
status: TeamEnterpriseAuthTaskStatusEnum.starting,
210225
enterpriseName: data.enterpriseName.trim(),
@@ -239,6 +254,7 @@ export const startEnterpriseAuth = async ({
239254
}
240255
const currentTask = await getCurrentEnterpriseAuthTask(teamId);
241256
if (currentTask) {
257+
assertEnterpriseAuthTaskOperator({ task: currentTask, operator });
242258
return {
243259
status: TeamEnterpriseAuthStatusEnum.verifying,
244260
currentTask: buildLightTask({ currentTask }),
@@ -276,11 +292,11 @@ export const startEnterpriseAuth = async ({
276292
}
277293
).lean();
278294
if (!claimedTask || claimedTask.taskId !== taskId) {
279-
return await buildStartConflictResponse({ teamId, normalizedUnifiedCreditCode });
295+
return await buildStartConflictResponse({ teamId, operator, normalizedUnifiedCreditCode });
280296
}
281297
} catch (error) {
282298
if (isMongoDuplicateKeyError(error)) {
283-
return await buildStartConflictResponse({ teamId, normalizedUnifiedCreditCode });
299+
return await buildStartConflictResponse({ teamId, operator, normalizedUnifiedCreditCode });
284300
}
285301
throw error;
286302
}

packages/service/support/user/team/enterpriseAuth/taskExpire.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import {
33
TeamEnterpriseAuthTaskStatusEnum
44
} from '@fastgpt/global/support/user/team/enterpriseAuth/constant';
55
import { serviceEnv } from '../../../../env';
6-
import { enabledGuard, pendingTaskStatuses } from './common';
6+
import {
7+
assertEnterpriseAuthTaskOperator,
8+
type AuthOperator,
9+
enabledGuard,
10+
pendingTaskStatuses
11+
} from './common';
712
import { MongoTeamEnterpriseAuthTask } from './schema';
813
import { deriveExpiredTaskPatch, isPendingAmountTask } from './status';
914

@@ -168,17 +173,18 @@ export const expireCurrentTaskIfNeeded = async (teamId: string) => {
168173
return MongoTeamEnterpriseAuthTask.findOne({ teamId, taskId: task.taskId }).lean();
169174
};
170175

171-
export const resetEnterpriseAuthTask = async (teamId: string) => {
176+
export const resetEnterpriseAuthTask = async (operator: AuthOperator) => {
172177
enabledGuard();
173-
const task = await expireCurrentTaskIfNeeded(teamId);
178+
const task = await expireCurrentTaskIfNeeded(operator.teamId);
174179
if (!task || !isPendingAmountTask(task)) {
175180
return;
176181
}
182+
assertEnterpriseAuthTaskOperator({ task, operator });
177183

178184
const now = new Date();
179185
await MongoTeamEnterpriseAuthTask.updateOne(
180186
{
181-
teamId,
187+
teamId: operator.teamId,
182188
taskId: task.taskId,
183189
status: {
184190
$in: [

packages/service/support/user/team/enterpriseAuth/transferClient.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ const parseFenAmount = (amount: string | number | undefined) => {
7474
return num;
7575
};
7676

77+
const parseFailedTransferResult = (
78+
result: z.infer<typeof EnterpriseAuthTransferResponseSchema>['data'],
79+
fallbackMessage?: string
80+
): EnterpriseAuthTransferResult => {
81+
// 没有上游响应码时无法证明是企业信息校验失败,应按认证服务异常处理,避免误导用户反复修改表单。
82+
if (!result?.respCode) {
83+
return {
84+
type: 'service_failed',
85+
message: result?.respMsg || fallbackMessage
86+
};
87+
}
88+
89+
return {
90+
type: 'info_failed',
91+
message: result.respMsg || fallbackMessage,
92+
transferRespCode: result.respCode,
93+
transferRespMsg: result.respMsg
94+
};
95+
};
96+
7797
export const createEnterpriseAuthTransfer = async (
7898
data: Pick<
7999
StartEnterpriseAuthBodyType,
@@ -105,12 +125,7 @@ export const createEnterpriseAuthTransfer = async (
105125

106126
const result = parsed.data.data;
107127
if (!result.isTransactionSuccess) {
108-
return {
109-
type: 'info_failed',
110-
message: result.respMsg || parsed.data.message,
111-
transferRespCode: result.respCode,
112-
transferRespMsg: result.respMsg
113-
};
128+
return parseFailedTransferResult(result, parsed.data.message);
114129
}
115130

116131
const amountFen = parseFenAmount(result.transAmt);

packages/service/support/user/team/enterpriseAuth/verifyAmount.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { mongoSessionRun } from '../../../../common/mongo/sessionRun';
1111
import { getLogger, LogCategories } from '../../../../common/logger';
1212
import { clearTeamPlanCache } from '../../../wallet/sub/utils';
13-
import { enabledGuard, type AuthOperator } from './common';
13+
import { assertEnterpriseAuthTaskOperator, enabledGuard, type AuthOperator } from './common';
1414
import { MongoTeamEnterpriseAuth, MongoTeamEnterpriseAuthTask } from './schema';
1515
import { expireCurrentTaskIfNeeded } from './taskExpire';
1616
import { isPendingAmountTask, toTerminalTaskError } from './status';
@@ -198,6 +198,8 @@ export const verifyEnterpriseAuthAmount = async ({
198198
throw new Error(EnterpriseAuthErrEnum.taskNotFound);
199199
}
200200
if (task.taskId === data.taskId) {
201+
assertEnterpriseAuthTaskOperator({ task, operator });
202+
201203
const terminalError = toTerminalTaskError(task);
202204
if (terminalError) throw new Error(terminalError);
203205

0 commit comments

Comments
 (0)