From 489454daa024b4bb6656e603e990912198c16c0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:30:04 +0000 Subject: [PATCH] Add user-scoped data filtering for cron operations Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/api/cron.ts | 63 ++++++++++++++++++++++++------------------- back/services/cron.ts | 55 ++++++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/back/api/cron.ts b/back/api/cron.ts index 1bc99062..f443ac72 100644 --- a/back/api/cron.ts +++ b/back/api/cron.ts @@ -145,7 +145,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.crontabs(req.query as any); + const data = await cronService.crontabs({ + ...req.query as any, + userId: req.user?.userId + }); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -177,7 +180,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.create(req.body); + const data = await cronService.create({ + ...req.body, + userId: req.user?.userId + }); return res.send({ code: 200, data }); } catch (e) { return next(e); @@ -194,10 +200,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.run(req.body); + const data = await cronService.run(req.body, req.user?.userId); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); @@ -211,10 +217,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.stop(req.body); + const data = await cronService.stop(req.body, req.user?.userId); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); @@ -234,10 +240,11 @@ export default (app: Router) => { const data = await cronService.removeLabels( req.body.ids, req.body.labels, + req.user?.userId, ); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); @@ -254,10 +261,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.addLabels(req.body.ids, req.body.labels); + const data = await cronService.addLabels(req.body.ids, req.body.labels, req.user?.userId); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); @@ -271,10 +278,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.disabled(req.body); + const data = await cronService.disabled(req.body, req.user?.userId); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); @@ -288,10 +295,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.enabled(req.body); + const data = await cronService.enabled(req.body, req.user?.userId); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); @@ -344,10 +351,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.remove(req.body); + const data = await cronService.remove(req.body, req.user?.userId); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); @@ -361,10 +368,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.pin(req.body); + const data = await cronService.pin(req.body, req.user?.userId); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); @@ -378,10 +385,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const cronService = Container.get(CronService); - const data = await cronService.unPin(req.body); + const data = await cronService.unPin(req.body, req.user?.userId); return res.send({ code: 200, data }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); diff --git a/back/services/cron.ts b/back/services/cron.ts index a838ce9e..8616619b 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -29,6 +29,30 @@ import { ScheduleType } from '../interface/schedule'; export default class CronService { constructor(@Inject('logger') private logger: winston.Logger) {} + private addUserIdFilter(query: any, userId?: number) { + if (userId !== undefined) { + query.userId = userId; + } + return query; + } + + private async checkOwnership(ids: number[], userId?: number): Promise { + if (userId === undefined) { + // Admin can access all crons + return; + } + const crons = await CrontabModel.findAll({ + where: { id: ids }, + attributes: ['id', 'userId'], + }); + const unauthorized = crons.filter( + (cron) => cron.userId !== userId && cron.userId !== undefined + ); + if (unauthorized.length > 0) { + throw new Error('无权限操作该定时任务'); + } + } + private isNodeCron(cron: Crontab) { const { schedule, extra_schedules } = cron; if (Number(schedule?.split(/ +/).length) > 5 || extra_schedules?.length) { @@ -156,21 +180,26 @@ export default class CronService { } } - public async remove(ids: number[]) { + public async remove(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await CrontabModel.destroy({ where: { id: ids } }); await cronClient.delCron(ids.map(String)); await this.setCrontab(); } - public async pin(ids: number[]) { + public async pin(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await CrontabModel.update({ isPinned: 1 }, { where: { id: ids } }); } - public async unPin(ids: number[]) { + public async unPin(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await CrontabModel.update({ isPinned: 0 }, { where: { id: ids } }); } - public async addLabels(ids: string[], labels: string[]) { + public async addLabels(ids: string[], labels: string[], userId?: number) { + const numIds = ids.map(Number); + await this.checkOwnership(numIds, userId); const docs = await CrontabModel.findAll({ where: { id: ids } }); for (const doc of docs) { await CrontabModel.update( @@ -182,7 +211,9 @@ export default class CronService { } } - public async removeLabels(ids: string[], labels: string[]) { + public async removeLabels(ids: string[], labels: string[], userId?: number) { + const numIds = ids.map(Number); + await this.checkOwnership(numIds, userId); const docs = await CrontabModel.findAll({ where: { id: ids } }); for (const doc of docs) { await CrontabModel.update( @@ -377,6 +408,7 @@ export default class CronService { sorter: string; filters: string; queryString: string; + userId?: number; }): Promise<{ data: Crontab[]; total: number }> { const searchText = params?.searchValue; const page = Number(params?.page || '0'); @@ -397,6 +429,7 @@ export default class CronService { this.formatSearchText(query, searchText); this.formatFilterQuery(query, filterQuery); this.formatViewSort(order, viewQuery); + this.addUserIdFilter(query, params?.userId); if (sorterQuery) { const { field, type } = sorterQuery; @@ -429,7 +462,8 @@ export default class CronService { return doc.get({ plain: true }); } - public async run(ids: number[]) { + public async run(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await CrontabModel.update( { status: CrontabStatus.queued }, { where: { id: ids } }, @@ -439,7 +473,8 @@ export default class CronService { }); } - public async stop(ids: number[]) { + public async stop(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); const docs = await CrontabModel.findAll({ where: { id: ids } }); for (const doc of docs) { if (doc.pid) { @@ -533,13 +568,15 @@ export default class CronService { }); } - public async disabled(ids: number[]) { + public async disabled(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } }); await cronClient.delCron(ids.map(String)); await this.setCrontab(); } - public async enabled(ids: number[]) { + public async enabled(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } }); const docs = await CrontabModel.findAll({ where: { id: ids } }); const sixCron = docs