Skip to content

Commit 6d91987

Browse files
committed
Merge branch 'main' of https://github.com/trycompai/comp into feat/rbac-v1
2 parents 582714d + 410288e commit 6d91987

48 files changed

Lines changed: 3142 additions & 602 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1+
## [1.85.1](https://github.com/trycompai/comp/compare/v1.85.0...v1.85.1) (2026-02-23)
2+
3+
4+
### Bug Fixes
5+
6+
* **sync:** prevent privileged member auto-deactivation in GW and JumpCloud ([#2184](https://github.com/trycompai/comp/issues/2184)) ([00d26f6](https://github.com/trycompai/comp/commit/00d26f6b9a6054ac495f49048b197cb66a92894e))
7+
8+
# [1.85.0](https://github.com/trycompai/comp/compare/v1.84.0...v1.85.0) (2026-02-23)
9+
10+
11+
### Features
12+
13+
* **sync:** add Google Workspace inbox filtering for employee sync ([#2180](https://github.com/trycompai/comp/issues/2180)) ([dc484d8](https://github.com/trycompai/comp/commit/dc484d8ffeb80362b381f88cf38f87e1d8d62232))
14+
15+
# [1.84.0](https://github.com/trycompai/comp/compare/v1.83.7...v1.84.0) (2026-02-20)
16+
17+
18+
### Bug Fixes
19+
20+
* resolve device agent sign-in loop and improve auth robustness ([#2177](https://github.com/trycompai/comp/issues/2177)) ([7de133f](https://github.com/trycompai/comp/commit/7de133f9feb862e03563c520f76e5ad6ed04dca4))
21+
22+
23+
### Features
24+
25+
* **cloud-security:** add endpoints to trigger scans and get run status ([#2176](https://github.com/trycompai/comp/issues/2176)) ([4f1e87a](https://github.com/trycompai/comp/commit/4f1e87a4fb01af415b76daabd96732691dbebfb2))
26+
27+
## [1.83.7](https://github.com/trycompai/comp/compare/v1.83.6...v1.83.7) (2026-02-19)
28+
29+
30+
### Bug Fixes
31+
32+
* **app:** resolve 504 timeout on cloud security scans for new platform connections ([#2168](https://github.com/trycompai/comp/issues/2168)) ([82ccec8](https://github.com/trycompai/comp/commit/82ccec8b48d60f05ba8410108e217129ca0f1752))
33+
134
## [1.83.6](https://github.com/trycompai/comp/compare/v1.83.5...v1.83.6) (2026-02-18)
235

336

apps/api/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"prettier": "^3.5.3",
8484
"source-map-support": "^0.5.21",
8585
"supertest": "^7.0.0",
86+
"trigger.dev": "4.0.6",
8687
"ts-jest": "^29.2.5",
8788
"ts-loader": "^9.5.2",
8889
"ts-node": "^10.9.2",
@@ -120,9 +121,9 @@
120121
"db:getschema": "node ../../packages/db/scripts/combine-schemas.js && cp ../../packages/db/dist/schema.prisma prisma/schema.prisma",
121122
"db:migrate": "cd ../../packages/db && bunx prisma migrate dev && cd ../../apps/api",
122123
"deploy:trigger-prod": "npx trigger.dev@4.0.6 deploy",
123-
"dev": "bunx concurrently --kill-others --names \"nest,trigger\" --prefix-colors \"green,blue\" \"nest start --watch\" \"bunx trigger.dev@4.0.6 dev\"",
124+
"dev": "bunx concurrently --kill-others --names \"nest,trigger\" --prefix-colors \"green,blue\" \"nest start --watch\" \"trigger dev\"",
124125
"dev:nest": "nest start --watch",
125-
"dev:trigger": "bunx trigger.dev@4.0.6 dev",
126+
"dev:trigger": "trigger dev",
126127
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
127128
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
128129
"prebuild": "bun run db:generate",
Lines changed: 58 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,40 @@
11
import {
22
Controller,
3-
Get,
43
Post,
5-
Delete,
4+
Get,
65
Param,
7-
Body,
6+
Query,
7+
Headers,
88
Logger,
99
HttpException,
1010
HttpStatus,
1111
UseGuards,
1212
} from '@nestjs/common';
13-
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
14-
import { CloudSecurityService } from './cloud-security.service';
15-
import { CloudSecurityQueryService } from './cloud-security-query.service';
16-
import { CloudSecurityLegacyService } from './cloud-security-legacy.service';
1713
import { HybridAuthGuard } from '../auth/hybrid-auth.guard';
18-
import { PermissionGuard } from '../auth/permission.guard';
19-
import { RequirePermission } from '../auth/require-permission.decorator';
2014
import { OrganizationId } from '../auth/auth-context.decorator';
15+
import {
16+
CloudSecurityService,
17+
ConnectionNotFoundError,
18+
} from './cloud-security.service';
2119

2220
@Controller({ path: 'cloud-security', version: '1' })
23-
@UseGuards(HybridAuthGuard, PermissionGuard)
24-
@ApiTags('Cloud Security')
25-
@ApiSecurity('apikey')
2621
export class CloudSecurityController {
2722
private readonly logger = new Logger(CloudSecurityController.name);
2823

29-
constructor(
30-
private readonly cloudSecurityService: CloudSecurityService,
31-
private readonly queryService: CloudSecurityQueryService,
32-
private readonly legacyService: CloudSecurityLegacyService,
33-
) {}
34-
35-
// ============================================================
36-
// Read endpoints
37-
// ============================================================
38-
39-
@Get('providers')
40-
@RequirePermission('cloud-security', 'read')
41-
async getProviders(@OrganizationId() organizationId: string) {
42-
const data = await this.queryService.getProviders(organizationId);
43-
return { data, count: data.length };
44-
}
45-
46-
@Get('findings')
47-
@RequirePermission('cloud-security', 'read')
48-
async getFindings(@OrganizationId() organizationId: string) {
49-
const data = await this.queryService.getFindings(organizationId);
50-
return { data, count: data.length };
51-
}
52-
53-
// ============================================================
54-
// Scan endpoint (existing)
55-
// ============================================================
24+
constructor(private readonly cloudSecurityService: CloudSecurityService) {}
5625

5726
@Post('scan/:connectionId')
58-
@RequirePermission('cloud-security', 'update')
5927
async scan(
6028
@Param('connectionId') connectionId: string,
61-
@OrganizationId() organizationId: string,
29+
@Headers('x-organization-id') organizationId: string,
6230
) {
31+
if (!organizationId) {
32+
throw new HttpException(
33+
'Organization ID required',
34+
HttpStatus.BAD_REQUEST,
35+
);
36+
}
37+
6338
this.logger.log(
6439
`Cloud security scan requested for connection ${connectionId}`,
6540
);
@@ -87,57 +62,56 @@ export class CloudSecurityController {
8762
};
8863
}
8964

90-
// ============================================================
91-
// Legacy integration endpoints
92-
// ============================================================
93-
94-
@Post('legacy/connect')
95-
@RequirePermission('cloud-security', 'create')
96-
async connectLegacy(
65+
@Post('trigger/:connectionId')
66+
@UseGuards(HybridAuthGuard)
67+
async triggerScan(
68+
@Param('connectionId') connectionId: string,
9769
@OrganizationId() organizationId: string,
98-
@Body()
99-
body: {
100-
provider: 'aws' | 'gcp' | 'azure';
101-
credentials: Record<string, string | string[]>;
102-
},
10370
) {
104-
if (!['aws', 'gcp', 'azure'].includes(body.provider)) {
105-
throw new HttpException('Invalid provider', HttpStatus.BAD_REQUEST);
106-
}
107-
108-
const result = await this.legacyService.connectLegacy(
109-
organizationId,
110-
body.provider,
111-
body.credentials,
71+
this.logger.log(
72+
`Cloud security scan trigger requested for connection ${connectionId}`,
11273
);
11374

114-
return { success: true, integrationId: result.integrationId };
75+
try {
76+
const result = await this.cloudSecurityService.triggerScan(
77+
connectionId,
78+
organizationId,
79+
);
80+
return result;
81+
} catch (error) {
82+
const message =
83+
error instanceof Error ? error.message : 'Failed to trigger scan';
84+
throw new HttpException(message, HttpStatus.BAD_REQUEST);
85+
}
11586
}
11687

117-
@Delete('legacy/:id')
118-
@RequirePermission('cloud-security', 'delete')
119-
async disconnectLegacy(
120-
@Param('id') id: string,
88+
@Get('runs/:runId')
89+
@UseGuards(HybridAuthGuard)
90+
async getRunStatus(
91+
@Param('runId') runId: string,
92+
@Query('connectionId') connectionId: string,
12193
@OrganizationId() organizationId: string,
12294
) {
123-
await this.legacyService.disconnectLegacy(id, organizationId);
124-
return { success: true };
125-
}
126-
127-
@Post('legacy/validate-aws')
128-
@RequirePermission('cloud-security', 'read')
129-
async validateAwsCredentials(
130-
@Body() body: { accessKeyId: string; secretAccessKey: string },
131-
) {
132-
const result = await this.legacyService.validateAwsAccessKeys(
133-
body.accessKeyId,
134-
body.secretAccessKey,
135-
);
95+
if (!connectionId) {
96+
throw new HttpException(
97+
'connectionId query parameter is required',
98+
HttpStatus.BAD_REQUEST,
99+
);
100+
}
136101

137-
return {
138-
success: true,
139-
accountId: result.accountId,
140-
regions: result.regions,
141-
};
102+
try {
103+
return await this.cloudSecurityService.getRunStatus(
104+
runId,
105+
connectionId,
106+
organizationId,
107+
);
108+
} catch (error) {
109+
if (error instanceof ConnectionNotFoundError) {
110+
throw new HttpException('Connection not found', HttpStatus.NOT_FOUND);
111+
}
112+
const message =
113+
error instanceof Error ? error.message : 'Failed to get run status';
114+
throw new HttpException(message, HttpStatus.INTERNAL_SERVER_ERROR);
115+
}
142116
}
143117
}

apps/api/src/cloud-security/cloud-security.service.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Injectable, Logger } from '@nestjs/common';
22
import { db } from '@db';
33
import { getManifest } from '@comp/integration-platform';
4+
import { runs, tasks } from '@trigger.dev/sdk';
45
import { CredentialVaultService } from '../integration-platform/services/credential-vault.service';
56
import { OAuthCredentialsService } from '../integration-platform/services/oauth-credentials.service';
67
import { GCPSecurityService } from './providers/gcp-security.service';
@@ -28,6 +29,12 @@ export interface ScanResult {
2829
error?: string;
2930
}
3031

32+
export class ConnectionNotFoundError extends Error {
33+
constructor() {
34+
super('Connection not found');
35+
}
36+
}
37+
3138
@Injectable()
3239
export class CloudSecurityService {
3340
private readonly logger = new Logger(CloudSecurityService.name);
@@ -220,6 +227,65 @@ export class CloudSecurityService {
220227
}
221228
}
222229

230+
async triggerScan(
231+
connectionId: string,
232+
organizationId: string,
233+
): Promise<{ runId: string }> {
234+
// Validate connection exists and is active
235+
const connection = await db.integrationConnection.findFirst({
236+
where: {
237+
id: connectionId,
238+
organizationId,
239+
status: 'active',
240+
},
241+
});
242+
243+
if (!connection) {
244+
throw new Error('Connection not found or inactive');
245+
}
246+
247+
const handle = await tasks.trigger('run-cloud-security-scan', {
248+
connectionId,
249+
organizationId,
250+
providerSlug: 'platform',
251+
connectionName: connectionId,
252+
});
253+
254+
this.logger.log(`Triggered cloud security scan task`, {
255+
connectionId,
256+
runId: handle.id,
257+
});
258+
259+
return { runId: handle.id };
260+
}
261+
262+
async getRunStatus(
263+
runId: string,
264+
connectionId: string,
265+
organizationId: string,
266+
): Promise<{ completed: boolean; success: boolean; output: unknown }> {
267+
// Verify the connection belongs to the caller's organization
268+
const connection = await db.integrationConnection.findFirst({
269+
where: {
270+
id: connectionId,
271+
organizationId,
272+
},
273+
select: { id: true },
274+
});
275+
276+
if (!connection) {
277+
throw new ConnectionNotFoundError();
278+
}
279+
280+
const run = await runs.retrieve(runId);
281+
282+
return {
283+
completed: run.isCompleted,
284+
success: run.isCompleted ? run.isSuccess : false,
285+
output: run.isCompleted ? run.output : null,
286+
};
287+
}
288+
223289
private async storeFindings(
224290
connectionId: string,
225291
provider: string,

0 commit comments

Comments
 (0)