Skip to content

Commit e954209

Browse files
authored
Merge branch 'main' into chas/comment-disabled-error
2 parents ae973c3 + 70398e4 commit e954209

52 files changed

Lines changed: 2796 additions & 397 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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,70 @@
1+
# [3.13.0](https://github.com/trycompai/comp/compare/v3.12.2...v3.13.0) (2026-03-30)
2+
3+
4+
### Bug Fixes
5+
6+
* fix error with policy rendering ([e9cd567](https://github.com/trycompai/comp/commit/e9cd5673a2605ab09182a76d1d4217d8b6b4a4dd))
7+
* **integration-platform:** move buildHeaders inside request lambda for token refresh ([4349f50](https://github.com/trycompai/comp/commit/4349f5069d6bb55a44eba5791336abcc26ccf002))
8+
* **policy-editor:** enhance permission handling during loading state ([089ceb7](https://github.com/trycompai/comp/commit/089ceb715f59f9c04e3e206d8e587b6b408f1918))
9+
* **policy-editor:** simplify permission handling in PolicyEditorWrapper ([#2400](https://github.com/trycompai/comp/issues/2400)) ([e4fb01a](https://github.com/trycompai/comp/commit/e4fb01a1be682e8f47ca9fafcf8c42594455af5c))
10+
11+
12+
### Features
13+
14+
* **integration-platform:** add bodyEncoding option to fetch step ([22759b8](https://github.com/trycompai/comp/commit/22759b8d3243d915eb9629e0747a749eca4eebf7))
15+
16+
## [3.12.2](https://github.com/trycompai/comp/compare/v3.12.1...v3.12.2) (2026-03-30)
17+
18+
19+
### Bug Fixes
20+
21+
* **auth:** use better-auth APIs instead of direct DB session operations ([17378d9](https://github.com/trycompai/comp/commit/17378d95e7e9c5b71e011dac4d681e2442196cf2))
22+
* skip audit log for chat history and expose task templates to ui ([9baf09c](https://github.com/trycompai/comp/commit/9baf09c1e648960f7c38495a751aab07436ae4d7))
23+
24+
## [3.12.1](https://github.com/trycompai/comp/compare/v3.12.0...v3.12.1) (2026-03-30)
25+
26+
27+
### Bug Fixes
28+
29+
* upload attachments stuck loading ([7dc6bf9](https://github.com/trycompai/comp/commit/7dc6bf9a9da1cc231e59001a4569dff81f1f7b10))
30+
31+
# [3.12.0](https://github.com/trycompai/comp/compare/v3.11.5...v3.12.0) (2026-03-30)
32+
33+
34+
### Bug Fixes
35+
36+
* **integration-platform:** clear stale syncDefinition on upsert, deduplicate VariableSchema ([ebfa25a](https://github.com/trycompai/comp/commit/ebfa25a08be22b5a71576b78ec4a787d62e583b6))
37+
* **integration-platform:** persist syncDefinition to database ([6f08b6b](https://github.com/trycompai/comp/commit/6f08b6b5242e0eadbd5e6422f2dc3cf48d2a6c3a))
38+
* **integration-platform:** remove redundant per-check re-validation in validate endpoint ([b116616](https://github.com/trycompai/comp/commit/b116616e8f8e72aefd6b7ddcd940e1dba7aca0b4))
39+
* **integration-platform:** validate syncDefinition before storing, fix success flag ([4b007ff](https://github.com/trycompai/comp/commit/4b007ff4dfb5a578720daab1d6e492479b828646))
40+
* use value import for Prisma (DbNull requires runtime access) ([73d0fa6](https://github.com/trycompai/comp/commit/73d0fa6fd72f0a486d48a6af7ca598a016f6ccff))
41+
42+
43+
### Features
44+
45+
* **integration-platform:** add code step and dynamic employee sync ([3ddfaef](https://github.com/trycompai/comp/commit/3ddfaef188eb84de70fb1e193432f2aa9ba45366))
46+
47+
## [3.11.5](https://github.com/trycompai/comp/compare/v3.11.4...v3.11.5) (2026-03-25)
48+
49+
50+
### Bug Fixes
51+
52+
* **vendors:** skip assignee validation when assignee hasn't changed ([9e508c2](https://github.com/trycompai/comp/commit/9e508c2c4b2a7728c4f8d4778323f31729a82a15))
53+
54+
## [3.11.4](https://github.com/trycompai/comp/compare/v3.11.3...v3.11.4) (2026-03-25)
55+
56+
57+
### Bug Fixes
58+
59+
* **vendors:** fix PATCH 400 error for vendors with empty descriptions ([cdc71c7](https://github.com/trycompai/comp/commit/cdc71c7f69b59090a8407f3bd10c1adb98defce5))
60+
61+
## [3.11.3](https://github.com/trycompai/comp/compare/v3.11.2...v3.11.3) (2026-03-25)
62+
63+
64+
### Bug Fixes
65+
66+
* **trust-portal:** fix CORS for custom domain trust portals ([#2371](https://github.com/trycompai/comp/issues/2371)) ([0d54899](https://github.com/trycompai/comp/commit/0d5489959d3675d3de7f1bc36c34b3c52c876f10))
67+
168
## [3.11.2](https://github.com/trycompai/comp/compare/v3.11.1...v3.11.2) (2026-03-25)
269

370

apps/api/src/assistant-chat/assistant-chat.controller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ Important:
194194
}
195195

196196
@Put('history')
197+
@SkipAuditLog()
197198
@ApiOperation({
198199
summary: 'Save assistant chat history',
199200
description:
@@ -214,6 +215,7 @@ Important:
214215
}
215216

216217
@Delete('history')
218+
@SkipAuditLog()
217219
@ApiOperation({
218220
summary: 'Clear assistant chat history',
219221
description: 'Deletes the current user-scoped assistant chat history.',

apps/api/src/auth/auth-server-origins.spec.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,64 @@ describe('isStaticTrustedOrigin', () => {
115115
expect(mainTs).toContain("import { isTrustedOrigin } from './auth/auth.server'");
116116
});
117117
});
118+
119+
describe('getCustomDomains (structural)', () => {
120+
it('auth.server.ts should NOT filter by domainVerified in CORS domain query', () => {
121+
// Custom domains should be allowed for CORS as soon as they are configured
122+
// by an admin, not only after DNS verification completes. Vercel can serve
123+
// the trust portal before our domainVerified flag is set, causing CORS
124+
// failures on client-side API calls.
125+
const fs = require('fs');
126+
const path = require('path');
127+
const authServer = fs.readFileSync(
128+
path.join(__dirname, 'auth.server.ts'),
129+
'utf-8',
130+
) as string;
131+
132+
// Extract the getCustomDomains function body
133+
const fnMatch = authServer.match(
134+
/async function getCustomDomains[\s\S]*?^}/m,
135+
);
136+
expect(fnMatch).toBeTruthy();
137+
const fnBody = fnMatch![0];
138+
139+
// Must NOT require domainVerified — that flag lags behind Vercel's own verification
140+
expect(fnBody).not.toContain('domainVerified');
141+
142+
// Must still filter by published status
143+
expect(fnBody).toContain("status: 'published'");
144+
});
145+
146+
it('auth.server.ts getCustomDomains should have independent error handling for Redis and DB', () => {
147+
const fs = require('fs');
148+
const path = require('path');
149+
const authServer = fs.readFileSync(
150+
path.join(__dirname, 'auth.server.ts'),
151+
'utf-8',
152+
) as string;
153+
154+
const fnMatch = authServer.match(
155+
/async function getCustomDomains[\s\S]*?^}/m,
156+
);
157+
expect(fnMatch).toBeTruthy();
158+
const fnBody = fnMatch![0];
159+
160+
// Should have multiple try/catch blocks (Redis read, DB query, Redis write)
161+
const tryCatchCount = (fnBody.match(/\btry\s*\{/g) || []).length;
162+
expect(tryCatchCount).toBeGreaterThanOrEqual(3);
163+
});
164+
});
165+
166+
describe('originCheckMiddleware (structural)', () => {
167+
it('should exempt trust-access paths from origin validation', () => {
168+
const fs = require('fs');
169+
const path = require('path');
170+
const middleware = fs.readFileSync(
171+
path.join(__dirname, 'origin-check.middleware.ts'),
172+
'utf-8',
173+
) as string;
174+
175+
// Trust-access endpoints are public (no auth, no cookies) — no CSRF risk
176+
expect(middleware).toContain('/v1/trust-access');
177+
});
178+
});

apps/api/src/auth/auth.server.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,21 @@ const corsRedisClient = new Redis({
9494
});
9595

9696
async function getCustomDomains(): Promise<Set<string>> {
97+
// Try Redis cache first (non-fatal if Redis is unavailable)
9798
try {
98-
// Try Redis cache first
9999
const cached = await corsRedisClient.get<string[]>(CORS_DOMAINS_CACHE_KEY);
100100
if (cached) {
101101
return new Set(cached);
102102
}
103+
} catch (error) {
104+
console.error('[CORS] Redis cache read failed, falling back to DB:', error);
105+
}
103106

104-
// Cache miss — query DB and store in Redis
107+
// Cache miss or Redis unavailable — query DB
108+
try {
105109
const trusts = await db.trust.findMany({
106110
where: {
107111
domain: { not: null },
108-
domainVerified: true,
109112
status: 'published',
110113
},
111114
select: { domain: true },
@@ -115,13 +118,18 @@ async function getCustomDomains(): Promise<Set<string>> {
115118
.map((t) => t.domain)
116119
.filter((d): d is string => d !== null);
117120

118-
await corsRedisClient.set(CORS_DOMAINS_CACHE_KEY, domains, {
119-
ex: CORS_DOMAINS_CACHE_TTL_SECONDS,
120-
});
121+
// Best-effort cache update (don't lose DB results if Redis SET fails)
122+
try {
123+
await corsRedisClient.set(CORS_DOMAINS_CACHE_KEY, domains, {
124+
ex: CORS_DOMAINS_CACHE_TTL_SECONDS,
125+
});
126+
} catch {
127+
// Redis unavailable — continue without caching
128+
}
121129

122130
return new Set(domains);
123131
} catch (error) {
124-
console.error('[CORS] Failed to fetch custom domains:', error);
132+
console.error('[CORS] Failed to fetch custom domains from DB:', error);
125133
return new Set();
126134
}
127135
}
@@ -130,7 +138,7 @@ async function getCustomDomains(): Promise<Set<string>> {
130138
* Check if an origin is trusted. Checks (in order):
131139
* 1. Static trusted origins list
132140
* 2. *.trycomp.ai / *.trust.inc subdomains
133-
* 3. Verified custom domains from the DB (cached in Redis, TTL 5 min)
141+
* 3. Published custom domains from the DB (cached in Redis, TTL 5 min)
134142
*/
135143
export async function isTrustedOrigin(origin: string): Promise<boolean> {
136144
if (isStaticTrustedOrigin(origin)) {

apps/api/src/auth/origin-check.middleware.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
88
* These are called by external services that don't send browser Origin headers.
99
*/
1010
const EXEMPT_PATH_PREFIXES = [
11-
'/api/auth', // better-auth handles its own CSRF
12-
'/v1/health', // health check
13-
'/api/docs', // swagger
11+
'/api/auth', // better-auth handles its own CSRF
12+
'/v1/health', // health check
13+
'/api/docs', // swagger
14+
'/v1/trust-access', // public trust portal endpoints (no auth, no cookies)
1415
];
1516

1617
/**

apps/api/src/integration-platform/controllers/checks.controller.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
UseGuards,
1111
} from '@nestjs/common';
1212
import { ApiTags, ApiSecurity } from '@nestjs/swagger';
13+
import type { Prisma } from '@prisma/client';
1314
import { HybridAuthGuard } from '../../auth/hybrid-auth.guard';
1415
import { PermissionGuard } from '../../auth/permission.guard';
1516
import { RequirePermission } from '../../auth/require-permission.decorator';
@@ -271,14 +272,28 @@ export class ChecksController {
271272
await this.checkRunRepository.addResults(resultsToStore);
272273
}
273274

274-
// Update the check run status
275+
// Collect execution logs from all check results
276+
const allLogs = result.results.flatMap((checkResult) =>
277+
checkResult.result.logs.map((log) => ({
278+
check: checkResult.checkName,
279+
level: log.level,
280+
message: log.message,
281+
...(log.data ? { data: log.data } : {}),
282+
timestamp: log.timestamp.toISOString(),
283+
})),
284+
);
285+
286+
// Update the check run status with logs
275287
const startTime = checkRun.startedAt?.getTime() || Date.now();
276288
await this.checkRunRepository.complete(checkRun.id, {
277289
status: result.totalFindings > 0 ? 'failed' : 'success',
278290
durationMs: Date.now() - startTime,
279291
totalChecked: result.results.length,
280292
passedCount: result.totalPassing,
281293
failedCount: result.totalFindings,
294+
logs: allLogs.length > 0
295+
? (allLogs as unknown as Prisma.InputJsonValue)
296+
: undefined,
282297
});
283298

284299
return {
@@ -288,20 +303,29 @@ export class ChecksController {
288303
...result,
289304
};
290305
} catch (error) {
291-
// Mark the check run as failed
306+
// Mark the check run as failed with error details
292307
const startTime = checkRun.startedAt?.getTime() || Date.now();
308+
const errorMessage = error instanceof Error ? error.message : String(error);
309+
const errorStack = error instanceof Error ? error.stack : undefined;
293310
await this.checkRunRepository.complete(checkRun.id, {
294311
status: 'failed',
295312
durationMs: Date.now() - startTime,
296313
totalChecked: 0,
297314
passedCount: 0,
298315
failedCount: 0,
299-
errorMessage: error instanceof Error ? error.message : String(error),
316+
errorMessage,
317+
logs: [{
318+
check: body.checkId || 'all',
319+
level: 'error',
320+
message: errorMessage,
321+
...(errorStack ? { data: { stack: errorStack } } : {}),
322+
timestamp: new Date().toISOString(),
323+
}] as unknown as Prisma.InputJsonValue,
300324
});
301325

302326
this.logger.error(`Check execution failed: ${error}`);
303327
throw new HttpException(
304-
`Check execution failed: ${error instanceof Error ? error.message : String(error)}`,
328+
`Check execution failed: ${errorMessage}`,
305329
HttpStatus.INTERNAL_SERVER_ERROR,
306330
);
307331
}

0 commit comments

Comments
 (0)