|
1 | 1 | import { db } from '@sim/db' |
2 | | -import { permissionGroup, permissionGroupMember } from '@sim/db/schema' |
| 2 | +import { permissionGroup, permissionGroupMember, workspace } from '@sim/db/schema' |
3 | 3 | import { createLogger } from '@sim/logger' |
4 | | -import { and, asc, eq } from 'drizzle-orm' |
| 4 | +import { and, asc, eq, sql } from 'drizzle-orm' |
5 | 5 | import { isWorkspaceOnEnterprisePlan } from '@/lib/billing' |
6 | 6 | import { |
7 | 7 | getAllowedIntegrationsFromEnv, |
@@ -296,32 +296,63 @@ export async function validateSkillsAllowed( |
296 | 296 | } |
297 | 297 |
|
298 | 298 | /** |
299 | | - * Validates if the user is allowed to send invitations for the given workspace. |
300 | | - * Also checks the global feature flag. When `workspaceId` is omitted only the |
301 | | - * feature-flag check runs (no permission-group gate). |
| 299 | + * Validates if the user is allowed to send invitations. Pass one of: |
| 300 | + * - `workspaceId` — workspace-scoped invite: block when the user's group in that workspace has |
| 301 | + * `disableInvitations`. |
| 302 | + * - `organizationId` — organization-level invite (no specific workspace target): block when the |
| 303 | + * user has `disableInvitations` set on their group in any organization-owned workspace. This |
| 304 | + * mirrors the pre-refactor behavior where `disableInvitations` was an organization-level |
| 305 | + * policy. |
| 306 | + * - neither — only the global feature flag is checked. |
302 | 307 | */ |
303 | 308 | export async function validateInvitationsAllowed( |
304 | 309 | userId: string | undefined, |
305 | | - workspaceId?: string |
| 310 | + scope: string | { workspaceId?: string; organizationId?: string } = {} |
306 | 311 | ): Promise<void> { |
307 | 312 | if (isInvitationsDisabled) { |
308 | 313 | logger.warn('Invitations blocked by feature flag') |
309 | 314 | throw new InvitationsNotAllowedError() |
310 | 315 | } |
311 | 316 |
|
312 | | - if (!userId || !workspaceId) { |
| 317 | + if (!userId) { |
313 | 318 | return |
314 | 319 | } |
315 | 320 |
|
316 | | - const config = await getUserPermissionConfig(userId, workspaceId) |
| 321 | + const { workspaceId, organizationId } = |
| 322 | + typeof scope === 'string' ? { workspaceId: scope, organizationId: undefined } : scope |
317 | 323 |
|
318 | | - if (!config) { |
| 324 | + if (workspaceId) { |
| 325 | + const config = await getUserPermissionConfig(userId, workspaceId) |
| 326 | + if (config?.disableInvitations) { |
| 327 | + logger.warn('Invitations blocked by permission group', { userId, workspaceId }) |
| 328 | + throw new InvitationsNotAllowedError() |
| 329 | + } |
319 | 330 | return |
320 | 331 | } |
321 | 332 |
|
322 | | - if (config.disableInvitations) { |
323 | | - logger.warn('Invitations blocked by permission group', { userId, workspaceId }) |
324 | | - throw new InvitationsNotAllowedError() |
| 333 | + if (organizationId) { |
| 334 | + const [row] = await db |
| 335 | + .select({ id: permissionGroup.id }) |
| 336 | + .from(permissionGroupMember) |
| 337 | + .innerJoin(permissionGroup, eq(permissionGroupMember.permissionGroupId, permissionGroup.id)) |
| 338 | + .innerJoin(workspace, eq(permissionGroup.workspaceId, workspace.id)) |
| 339 | + .where( |
| 340 | + and( |
| 341 | + eq(permissionGroupMember.userId, userId), |
| 342 | + eq(workspace.organizationId, organizationId), |
| 343 | + sql`${permissionGroup.config} @> '{"disableInvitations": true}'::jsonb` |
| 344 | + ) |
| 345 | + ) |
| 346 | + .limit(1) |
| 347 | + |
| 348 | + if (row) { |
| 349 | + logger.warn('Invitations blocked by permission group (organization-wide)', { |
| 350 | + userId, |
| 351 | + organizationId, |
| 352 | + permissionGroupId: row.id, |
| 353 | + }) |
| 354 | + throw new InvitationsNotAllowedError() |
| 355 | + } |
325 | 356 | } |
326 | 357 | } |
327 | 358 |
|
|
0 commit comments