Skip to content

Commit df9d992

Browse files
committed
chore: merge main into release for new releases
2 parents 84e259e + 90b0c49 commit df9d992

31 files changed

Lines changed: 2059 additions & 123 deletions

File tree

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,8 @@ export class OAuthController {
403403
};
404404

405405
// Add client credentials based on auth method
406+
// Per OAuth 2.0 RFC 6749 Section 2.3.1, when using HTTP Basic auth (header),
407+
// client credentials should NOT be included in the request body
406408
if (config.clientAuthMethod === 'header') {
407409
const creds = Buffer.from(
408410
`${credentials.clientId}:${credentials.clientSecret}`,
@@ -422,8 +424,10 @@ export class OAuthController {
422424
});
423425

424426
if (!response.ok) {
425-
await response.text(); // consume body
426-
this.logger.error(`Token exchange failed: ${response.status}`);
427+
const errorBody = await response.text();
428+
this.logger.error(
429+
`Token exchange failed: ${response.status} - ${errorBody}`,
430+
);
427431
throw new Error(`Token exchange failed: ${response.status}`);
428432
}
429433

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ export class SyncController {
186186
}
187187
const errorText = await response.text();
188188
this.logger.error(
189-
`Google API error: ${response.status} ${response.statusText}`,
189+
`Google API error: ${response.status} ${response.statusText} - ${errorText}`,
190190
);
191191
throw new HttpException(
192192
'Failed to fetch users from Google Workspace',

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,18 @@ export class VariablesController {
248248

249249
fetch: async <T = unknown>(path: string): Promise<T> => {
250250
const url = new URL(path, baseUrl);
251+
251252
const response = await fetch(url.toString(), {
252253
headers: buildHeaders(),
253254
});
254-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
255-
return response.json();
255+
256+
if (!response.ok) {
257+
const errorText = await response.text();
258+
throw new Error(`HTTP ${response.status}: ${errorText}`);
259+
}
260+
261+
const data = await response.json();
262+
return data as T;
256263
},
257264

258265
fetchAllPages: async <T = unknown>(path: string): Promise<T[]> => {
@@ -268,10 +275,30 @@ export class VariablesController {
268275
const response = await fetch(url.toString(), {
269276
headers: buildHeaders(),
270277
});
271-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
272278

273-
const items: T[] = await response.json();
274-
if (!Array.isArray(items) || items.length === 0) break;
279+
if (!response.ok) {
280+
const errorText = await response.text();
281+
throw new Error(`HTTP ${response.status}: ${errorText}`);
282+
}
283+
284+
const data = await response.json();
285+
286+
// Handle both direct array responses and wrapped responses
287+
// e.g., some APIs return { items: [...] } instead of [...]
288+
let items: T[];
289+
if (Array.isArray(data)) {
290+
items = data;
291+
} else if (data && typeof data === 'object') {
292+
// Find the first array property in the response
293+
const arrayValue = Object.values(data).find((v) =>
294+
Array.isArray(v),
295+
) as T[] | undefined;
296+
items = arrayValue ?? [];
297+
} else {
298+
items = [];
299+
}
300+
301+
if (items.length === 0) break;
275302

276303
allItems.push(...items);
277304
if (items.length < perPage) break;

apps/api/src/integration-platform/services/credential-vault.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ export class CredentialVaultService {
304304
};
305305

306306
// Add client credentials based on auth method
307+
// Per OAuth 2.0 RFC 6749 Section 2.3.1, when using HTTP Basic auth (header),
308+
// client credentials should NOT be included in the request body
307309
if (config.clientAuthMethod === 'header') {
308310
const credentials = Buffer.from(
309311
`${config.clientId}:${config.clientSecret}`,

apps/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"@types/canvas-confetti": "^1.9.0",
6262
"@types/react-syntax-highlighter": "^15.5.13",
6363
"@types/three": "^0.180.0",
64+
"@uiw/react-json-view": "^2.0.0-alpha.40",
6465
"@uploadthing/react": "^7.3.0",
6566
"@upstash/ratelimit": "^2.0.5",
6667
"@vercel/analytics": "^1.5.0",

apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PolicyPage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default function PolicyPage({
1515
policyId,
1616
organizationId,
1717
logs,
18+
showAiAssistant,
1819
}: {
1920
policy: (Policy & { approver: (Member & { user: User }) | null }) | null;
2021
assignees: (Member & { user: User })[];
@@ -25,6 +26,8 @@ export default function PolicyPage({
2526
/** Organization ID - required for correct org context in comments */
2627
organizationId: string;
2728
logs: AuditLogWithRelations[];
29+
/** Whether the AI assistant feature is enabled */
30+
showAiAssistant: boolean;
2831
}) {
2932
return (
3033
<>
@@ -41,6 +44,7 @@ export default function PolicyPage({
4144
policyContent={policy?.content ? (policy.content as JSONContent[]) : []}
4245
displayFormat={policy?.displayFormat}
4346
pdfUrl={policy?.pdfUrl}
47+
aiAssistantEnabled={showAiAssistant}
4448
/>
4549

4650
<RecentAuditLogs logs={logs} />

apps/app/src/app/(app)/[orgId]/policies/[policyId]/editor/components/PolicyDetails.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ interface PolicyContentManagerProps {
8181
isPendingApproval: boolean;
8282
displayFormat?: PolicyDisplayFormat;
8383
pdfUrl?: string | null;
84+
/** Whether the AI assistant feature is enabled (behind feature flag) */
85+
aiAssistantEnabled?: boolean;
8486
}
8587

8688
export function PolicyContentManager({
@@ -89,8 +91,9 @@ export function PolicyContentManager({
8991
isPendingApproval,
9092
displayFormat = 'EDITOR',
9193
pdfUrl,
94+
aiAssistantEnabled = false,
9295
}: PolicyContentManagerProps) {
93-
const [showAiAssistant, setShowAiAssistant] = useState(true);
96+
const [showAiAssistant, setShowAiAssistant] = useState(aiAssistantEnabled);
9497
const [editorKey, setEditorKey] = useState(0);
9598
const [currentContent, setCurrentContent] = useState<Array<JSONContent>>(() => {
9699
const formattedContent = Array.isArray(policyContent)
@@ -205,7 +208,7 @@ export function PolicyContentManager({
205208
PDF View
206209
</TabsTrigger>
207210
</TabsList>
208-
{!isPendingApproval && (
211+
{!isPendingApproval && aiAssistantEnabled && (
209212
<Button
210213
variant={showAiAssistant ? 'default' : 'outline'}
211214
size="sm"
@@ -236,7 +239,7 @@ export function PolicyContentManager({
236239
</Tabs>
237240
</div>
238241

239-
{showAiAssistant && (
242+
{aiAssistantEnabled && showAiAssistant && (
240243
<div className="w-80 shrink-0 self-stretch flex flex-col overflow-hidden">
241244
<PolicyAiAssistant
242245
messages={messages}

apps/app/src/app/(app)/[orgId]/policies/[policyId]/page.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { getFeatureFlags } from '@/app/posthog';
12
import PageWithBreadcrumb from '@/components/pages/PageWithBreadcrumb';
3+
import { auth } from '@/utils/auth';
24
import type { Metadata } from 'next';
5+
import { headers } from 'next/headers';
36
import { PolicyHeaderActions } from './components/PolicyHeaderActions';
47
import PolicyPage from './components/PolicyPage';
58
import { getAssignees, getLogsForPolicy, getPolicy, getPolicyControlMappingInfo } from './data';
@@ -18,6 +21,15 @@ export default async function PolicyDetails({
1821

1922
const isPendingApproval = !!policy?.approverId;
2023

24+
// Check feature flag for AI policy editor
25+
const session = await auth.api.getSession({
26+
headers: await headers(),
27+
});
28+
const flags = session?.user?.id ? await getFeatureFlags(session.user.id) : {};
29+
const isAiPolicyEditorEnabled =
30+
flags['is-ai-policy-assistant-enabled'] === true ||
31+
flags['is-ai-policy-assistant-enabled'] === 'true';
32+
2133
return (
2234
<PageWithBreadcrumb
2335
breadcrumbs={[
@@ -35,6 +47,7 @@ export default async function PolicyDetails({
3547
allControls={allControls}
3648
isPendingApproval={isPendingApproval}
3749
logs={logs}
50+
showAiAssistant={isAiPolicyEditorEnabled}
3851
/>
3952
</PageWithBreadcrumb>
4053
);

0 commit comments

Comments
 (0)