From 777fd3fb239800b782daebc3e99ee9f8e0fa4c01 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 16:37:37 +0000 Subject: [PATCH] Add user-scoped data filtering for subscription and dependence operations Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/api/dependence.ts | 28 +++++++++++----------- back/api/subscription.ts | 36 +++++++++++++++------------- back/services/dependence.ts | 39 ++++++++++++++++++++++++++---- back/services/subscription.ts | 45 +++++++++++++++++++++++++++++------ 4 files changed, 106 insertions(+), 42 deletions(-) diff --git a/back/api/dependence.ts b/back/api/dependence.ts index 2d9dc465..5ec48fe6 100644 --- a/back/api/dependence.ts +++ b/back/api/dependence.ts @@ -22,7 +22,7 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const dependenceService = Container.get(DependenceService); - const data = await dependenceService.dependencies(req.query as any); + const data = await dependenceService.dependencies(req.query as any, [], {}, req.user?.userId); return res.send({ code: 200, data }); } catch (e) { logger.error('🔥 error: %o', e); @@ -45,7 +45,7 @@ export default (app: Router) => { async (req: Request, res: Response, next: NextFunction) => { try { const dependenceService = Container.get(DependenceService); - const data = await dependenceService.create(req.body); + const data = await dependenceService.create(req.body, req.user?.userId); return res.send({ code: 200, data }); } catch (e) { return next(e); @@ -82,10 +82,10 @@ export default (app: Router) => { async (req: Request, res: Response, next: NextFunction) => { try { const dependenceService = Container.get(DependenceService); - const data = await dependenceService.remove(req.body); + const data = await dependenceService.remove(req.body, false, 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 }); } }, ); @@ -98,10 +98,10 @@ export default (app: Router) => { async (req: Request, res: Response, next: NextFunction) => { try { const dependenceService = Container.get(DependenceService); - const data = await dependenceService.remove(req.body, true); + const data = await dependenceService.remove(req.body, true, 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 }); } }, ); @@ -132,10 +132,10 @@ export default (app: Router) => { async (req: Request, res: Response, next: NextFunction) => { try { const dependenceService = Container.get(DependenceService); - const data = await dependenceService.reInstall(req.body); + const data = await dependenceService.reInstall(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 }); } }, ); @@ -148,10 +148,10 @@ export default (app: Router) => { async (req: Request, res: Response, next: NextFunction) => { try { const dependenceService = Container.get(DependenceService); - await dependenceService.cancel(req.body); + await dependenceService.cancel(req.body, req.user?.userId); return res.send({ code: 200 }); - } catch (e) { - return next(e); + } catch (e: any) { + return res.send({ code: 400, message: e.message }); } }, ); diff --git a/back/api/subscription.ts b/back/api/subscription.ts index 85587a16..3cfc4b43 100644 --- a/back/api/subscription.ts +++ b/back/api/subscription.ts @@ -16,6 +16,7 @@ export default (app: Router) => { const data = await subscriptionService.list( req.query.searchValue as string, req.query.ids as string, + req.user?.userId, ); return res.send({ code: 200, data }); } catch (e) { @@ -63,7 +64,10 @@ export default (app: Router) => { cron_parser.parseExpression(req.body.schedule).hasNext() ) { const subscriptionService = Container.get(SubscriptionService); - const data = await subscriptionService.create(req.body); + const data = await subscriptionService.create({ + ...req.body, + userId: req.user?.userId, + }); return res.send({ code: 200, data }); } else { return res.send({ code: 400, message: 'param schedule error' }); @@ -83,10 +87,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const subscriptionService = Container.get(SubscriptionService); - const data = await subscriptionService.run(req.body); + const data = await subscriptionService.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 }); } }, ); @@ -100,10 +104,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const subscriptionService = Container.get(SubscriptionService); - const data = await subscriptionService.stop(req.body); + const data = await subscriptionService.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 }); } }, ); @@ -117,10 +121,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const subscriptionService = Container.get(SubscriptionService); - const data = await subscriptionService.disabled(req.body); + const data = await subscriptionService.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 }); } }, ); @@ -134,10 +138,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const subscriptionService = Container.get(SubscriptionService); - const data = await subscriptionService.enabled(req.body); + const data = await subscriptionService.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 }); } }, ); @@ -220,10 +224,10 @@ export default (app: Router) => { const logger: Logger = Container.get('logger'); try { const subscriptionService = Container.get(SubscriptionService); - const data = await subscriptionService.remove(req.body, req.query); + const data = await subscriptionService.remove(req.body, req.query, 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/dependence.ts b/back/services/dependence.ts index 1cb7e559..7e90113a 100644 --- a/back/services/dependence.ts +++ b/back/services/dependence.ts @@ -30,9 +30,33 @@ export default class DependenceService { private sockService: SockService, ) { } - public async create(payloads: Dependence[]): Promise { + 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 dependencies + return; + } + const dependencies = await DependenceModel.findAll({ + where: { id: ids }, + attributes: ['id', 'userId'], + }); + const unauthorized = dependencies.filter( + (dep) => dep.userId !== undefined && dep.userId !== userId + ); + if (unauthorized.length > 0) { + throw new Error('无权限操作该依赖'); + } + } + + public async create(payloads: Dependence[], userId?: number): Promise { const tabs = payloads.map((x) => { - const tab = new Dependence({ ...x, status: DependenceStatus.queued }); + const tab = new Dependence({ ...x, status: DependenceStatus.queued, userId }); return tab; }); const docs = await this.insert(tabs); @@ -65,7 +89,8 @@ export default class DependenceService { return await this.getDb({ id: payload.id }); } - public async remove(ids: number[], force = false): Promise { + public async remove(ids: number[], force = false, userId?: number): Promise { + await this.checkOwnership(ids, userId); const docs = await DependenceModel.findAll({ where: { id: ids } }); for (const doc of docs) { taskLimit.removeQueuedDependency(doc); @@ -105,8 +130,10 @@ export default class DependenceService { }, sort: any = [], query: any = {}, + userId?: number, ): Promise { let condition = query; + this.addUserIdFilter(condition, userId); if (DependenceTypes[type]) { condition.type = DependenceTypes[type]; } @@ -141,7 +168,8 @@ export default class DependenceService { return taskLimit.waitDependencyQueueDone(); } - public async reInstall(ids: number[]): Promise { + public async reInstall(ids: number[], userId?: number): Promise { + await this.checkOwnership(ids, userId); await DependenceModel.update( { status: DependenceStatus.queued, log: [] }, { where: { id: ids } }, @@ -155,7 +183,8 @@ export default class DependenceService { return docs; } - public async cancel(ids: number[]) { + public async cancel(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); const docs = await DependenceModel.findAll({ where: { id: ids } }); for (const doc of docs) { taskLimit.removeQueuedDependency(doc); diff --git a/back/services/subscription.ts b/back/services/subscription.ts index 5b13161b..b30a4333 100644 --- a/back/services/subscription.ts +++ b/back/services/subscription.ts @@ -42,11 +42,37 @@ export default class SubscriptionService { private crontabService: CrontabService, ) {} + 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 subscriptions + return; + } + const subscriptions = await SubscriptionModel.findAll({ + where: { id: ids }, + attributes: ['id', 'userId'], + }); + const unauthorized = subscriptions.filter( + (sub) => sub.userId !== undefined && sub.userId !== userId + ); + if (unauthorized.length > 0) { + throw new Error('无权限操作该订阅'); + } + } + public async list( searchText?: string, ids?: string, + userId?: number, ): Promise { - let query = {}; + let query: any = {}; + this.addUserIdFilter(query, userId); const subIds = JSON.parse(ids || '[]'); if (searchText) { const reg = { @@ -262,7 +288,8 @@ export default class SubscriptionService { ); } - public async remove(ids: number[], query: { force?: boolean }) { + public async remove(ids: number[], query: { force?: boolean }, userId?: number) { + await this.checkOwnership(ids, userId); const docs = await SubscriptionModel.findAll({ where: { id: ids } }); for (const doc of docs) { await this.handleTask(doc.get({ plain: true }), false); @@ -273,7 +300,7 @@ export default class SubscriptionService { if (query?.force === true) { const crons = await CrontabModel.findAll({ where: { sub_id: ids } }); if (crons?.length) { - await this.crontabService.remove(crons.map((x) => x.id!)); + await this.crontabService.remove(crons.map((x) => x.id!), userId); } for (const doc of docs) { const filePath = join(config.scriptPath, doc.alias); @@ -294,7 +321,8 @@ export default class SubscriptionService { return doc.get({ plain: true }); } - public async run(ids: number[]) { + public async run(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await SubscriptionModel.update( { status: SubscriptionStatus.queued }, { where: { id: ids } }, @@ -304,7 +332,8 @@ export default class SubscriptionService { }); } - public async stop(ids: number[]) { + public async stop(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); const docs = await SubscriptionModel.findAll({ where: { id: ids } }); for (const doc of docs) { if (doc.pid) { @@ -339,7 +368,8 @@ export default class SubscriptionService { }); } - public async disabled(ids: number[]) { + public async disabled(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await SubscriptionModel.update({ is_disabled: 1 }, { where: { id: ids } }); const docs = await SubscriptionModel.findAll({ where: { id: ids } }); await this.setSshConfig(); @@ -348,7 +378,8 @@ export default class SubscriptionService { } } - public async enabled(ids: number[]) { + public async enabled(ids: number[], userId?: number) { + await this.checkOwnership(ids, userId); await SubscriptionModel.update({ is_disabled: 0 }, { where: { id: ids } }); const docs = await SubscriptionModel.findAll({ where: { id: ids } }); await this.setSshConfig();