diff --git a/lib/artists/__tests__/buildGetArtistsParams.test.ts b/lib/artists/__tests__/buildGetArtistsParams.test.ts index 8b2d0265b..174150271 100644 --- a/lib/artists/__tests__/buildGetArtistsParams.test.ts +++ b/lib/artists/__tests__/buildGetArtistsParams.test.ts @@ -15,7 +15,6 @@ describe("buildGetArtistsParams", () => { it("returns auth accountId for personal key", async () => { const result = await buildGetArtistsParams({ accountId: "personal-account-123", - orgId: null, }); expect(result).toEqual({ @@ -27,7 +26,6 @@ describe("buildGetArtistsParams", () => { it("returns auth accountId for org key", async () => { const result = await buildGetArtistsParams({ accountId: "org-owner-123", - orgId: "org-123", }); expect(result).toEqual({ @@ -39,7 +37,6 @@ describe("buildGetArtistsParams", () => { it("defaults orgId to null when orgIdFilter is omitted", async () => { const result = await buildGetArtistsParams({ accountId: "account-123", - orgId: null, }); expect(result.params?.orgId).toBeNull(); @@ -48,7 +45,6 @@ describe("buildGetArtistsParams", () => { it("passes orgIdFilter through when provided", async () => { const result = await buildGetArtistsParams({ accountId: "account-123", - orgId: null, orgIdFilter: "filter-org-456", }); @@ -63,7 +59,6 @@ describe("buildGetArtistsParams", () => { const result = await buildGetArtistsParams({ accountId: "org-owner-123", - orgId: "org-123", targetAccountId: "target-456", }); @@ -82,7 +77,6 @@ describe("buildGetArtistsParams", () => { const result = await buildGetArtistsParams({ accountId: "org-owner-123", - orgId: "org-123", targetAccountId: "target-456", orgIdFilter: "filter-org-789", }); @@ -98,7 +92,6 @@ describe("buildGetArtistsParams", () => { const result = await buildGetArtistsParams({ accountId: "personal-123", - orgId: null, targetAccountId: "shared-org-member", }); @@ -117,7 +110,6 @@ describe("buildGetArtistsParams", () => { const result = await buildGetArtistsParams({ accountId: "personal-123", - orgId: null, targetAccountId: "other-account", }); @@ -132,7 +124,6 @@ describe("buildGetArtistsParams", () => { const result = await buildGetArtistsParams({ accountId: "org-owner-123", - orgId: "org-123", targetAccountId: "not-in-org", }); diff --git a/lib/artists/buildGetArtistsParams.ts b/lib/artists/buildGetArtistsParams.ts index 3d25b1a00..f5625e7c6 100644 --- a/lib/artists/buildGetArtistsParams.ts +++ b/lib/artists/buildGetArtistsParams.ts @@ -4,8 +4,6 @@ import type { GetArtistsOptions } from "@/lib/artists/getArtists"; export interface BuildGetArtistsParamsInput { /** The authenticated account ID */ accountId: string; - /** The organization ID from the API key (null for personal keys) */ - orgId: string | null; /** Optional target account ID to filter by */ targetAccountId?: string; /** Optional organization filter for which artists to show */ @@ -31,7 +29,7 @@ export type BuildGetArtistsParamsResult = export async function buildGetArtistsParams( input: BuildGetArtistsParamsInput, ): Promise { - const { accountId, orgId, targetAccountId, orgIdFilter } = input; + const { accountId, targetAccountId, orgIdFilter } = input; let effectiveAccountId = accountId; @@ -50,8 +48,8 @@ export async function buildGetArtistsParams( effectiveAccountId = targetAccountId; } - // When org_id is omitted, default to personal artists (null = personal only) - // When org_id is provided, filter to that organization's artists + // When orgIdFilter is omitted, default to personal artists (null = personal only) + // When orgIdFilter is provided, filter to that organization's artists const effectiveOrgId = orgIdFilter ?? null; return { diff --git a/lib/artists/validateGetArtistsRequest.ts b/lib/artists/validateGetArtistsRequest.ts index 5bde7c381..03d2f71cb 100644 --- a/lib/artists/validateGetArtistsRequest.ts +++ b/lib/artists/validateGetArtistsRequest.ts @@ -58,12 +58,11 @@ export async function validateGetArtistsRequest( return authResult; } - const { accountId, orgId } = authResult; + const { accountId } = authResult; // Use shared function to build params const { params, error } = await buildGetArtistsParams({ accountId, - orgId, targetAccountId, orgIdFilter, }); diff --git a/lib/auth/__tests__/validateAuthContext.test.ts b/lib/auth/__tests__/validateAuthContext.test.ts index bd455025d..31dda345a 100644 --- a/lib/auth/__tests__/validateAuthContext.test.ts +++ b/lib/auth/__tests__/validateAuthContext.test.ts @@ -4,7 +4,6 @@ import { validateAuthContext } from "../validateAuthContext"; import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId"; import { getAuthenticatedAccountId } from "@/lib/auth/getAuthenticatedAccountId"; -import { getApiKeyDetails } from "@/lib/keys/getApiKeyDetails"; import { validateOrganizationAccess } from "@/lib/organizations/validateOrganizationAccess"; import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; @@ -21,10 +20,6 @@ vi.mock("@/lib/auth/getAuthenticatedAccountId", () => ({ getAuthenticatedAccountId: vi.fn(), })); -vi.mock("@/lib/keys/getApiKeyDetails", () => ({ - getApiKeyDetails: vi.fn(), -})); - vi.mock("@/lib/organizations/validateOrganizationAccess", () => ({ validateOrganizationAccess: vi.fn(), })); @@ -35,14 +30,9 @@ vi.mock("@/lib/organizations/canAccessAccount", () => ({ const mockGetApiKeyAccountId = vi.mocked(getApiKeyAccountId); const mockGetAuthenticatedAccountId = vi.mocked(getAuthenticatedAccountId); -const mockGetApiKeyDetails = vi.mocked(getApiKeyDetails); const mockValidateOrganizationAccess = vi.mocked(validateOrganizationAccess); const mockCanAccessAccount = vi.mocked(canAccessAccount); -/** - * - * @param headers - */ function createMockRequest(headers: Record = {}): Request { return { headers: { @@ -87,11 +77,6 @@ describe("validateAuthContext", () => { it("returns accountId from API key when valid", async () => { const request = createMockRequest({ "x-api-key": "valid-api-key" }); mockGetApiKeyAccountId.mockResolvedValue("account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "account-123", - orgId: null, - name: "test-key", - }); const result = await validateAuthContext(request as never); @@ -103,25 +88,6 @@ describe("validateAuthContext", () => { }); }); - it("returns orgId from API key details when present", async () => { - const request = createMockRequest({ "x-api-key": "org-api-key" }); - mockGetApiKeyAccountId.mockResolvedValue("org-account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "org-account-123", - orgId: "org-456", - name: "org-key", - }); - - const result = await validateAuthContext(request as never); - - expect(result).not.toBeInstanceOf(NextResponse); - expect(result).toEqual({ - accountId: "org-account-123", - orgId: "org-456", - authToken: "org-api-key", - }); - }); - it("returns error response when API key is invalid", async () => { const request = createMockRequest({ "x-api-key": "invalid-key" }); mockGetApiKeyAccountId.mockResolvedValue( @@ -137,7 +103,9 @@ describe("validateAuthContext", () => { describe("Bearer token authentication", () => { it("returns accountId from bearer token when valid", async () => { - const request = createMockRequest({ authorization: "Bearer valid-token" }); + const request = createMockRequest({ + authorization: "Bearer valid-token", + }); mockGetAuthenticatedAccountId.mockResolvedValue("bearer-account-123"); const result = await validateAuthContext(request as never); @@ -151,7 +119,9 @@ describe("validateAuthContext", () => { }); it("strips Bearer prefix from auth token", async () => { - const request = createMockRequest({ authorization: "Bearer my-token-123" }); + const request = createMockRequest({ + authorization: "Bearer my-token-123", + }); mockGetAuthenticatedAccountId.mockResolvedValue("account-123"); const result = await validateAuthContext(request as never); @@ -163,17 +133,12 @@ describe("validateAuthContext", () => { }); describe("account_id override", () => { - it("allows personal API key to specify own account_id (self-access)", async () => { + it("allows self-access without calling canAccessAccount", async () => { const request = createMockRequest({ "x-api-key": "personal-key" }); mockGetApiKeyAccountId.mockResolvedValue("account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "account-123", - orgId: null, - name: "personal-key", - }); const result = await validateAuthContext(request as never, { - accountId: "account-123", // Same as the API key's account + accountId: "account-123", }); expect(result).not.toBeInstanceOf(NextResponse); @@ -182,39 +147,12 @@ describe("validateAuthContext", () => { orgId: null, authToken: "personal-key", }); - // Should not call canAccessAccount for self-access expect(mockCanAccessAccount).not.toHaveBeenCalled(); }); - it("denies personal API key accessing different account_id when no shared org", async () => { + it("allows access to different account_id when canAccessAccount returns true", async () => { const request = createMockRequest({ "x-api-key": "personal-key" }); mockGetApiKeyAccountId.mockResolvedValue("account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "account-123", - orgId: null, - name: "personal-key", - }); - mockCanAccessAccount.mockResolvedValue(false); - - const result = await validateAuthContext(request as never, { - accountId: "different-account-456", // Different from API key's account - }); - - expect(result).toBeInstanceOf(NextResponse); - const response = result as NextResponse; - expect(response.status).toBe(403); - const body = await response.json(); - expect(body.error).toBe("Access denied to specified account_id"); - }); - - it("allows personal API key to access account_id in a shared org", async () => { - const request = createMockRequest({ "x-api-key": "personal-key" }); - mockGetApiKeyAccountId.mockResolvedValue("account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "account-123", - orgId: null, - name: "personal-key", - }); mockCanAccessAccount.mockResolvedValue(true); const result = await validateAuthContext(request as never, { @@ -233,61 +171,27 @@ describe("validateAuthContext", () => { }); }); - it("allows org API key to access member account", async () => { - const request = createMockRequest({ "x-api-key": "org-key" }); - mockGetApiKeyAccountId.mockResolvedValue("org-account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "org-account-123", - orgId: "org-456", - name: "org-key", - }); - mockCanAccessAccount.mockResolvedValue(true); - - const result = await validateAuthContext(request as never, { - accountId: "member-account-789", - }); - - expect(result).not.toBeInstanceOf(NextResponse); - expect(result).toEqual({ - accountId: "member-account-789", - orgId: "org-456", - authToken: "org-key", - }); - expect(mockCanAccessAccount).toHaveBeenCalledWith({ - targetAccountId: "member-account-789", - currentAccountId: "org-account-123", - }); - }); - - it("denies org API key accessing non-member account", async () => { - const request = createMockRequest({ "x-api-key": "org-key" }); - mockGetApiKeyAccountId.mockResolvedValue("org-account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "org-account-123", - orgId: "org-456", - name: "org-key", - }); + it("denies access to different account_id when canAccessAccount returns false", async () => { + const request = createMockRequest({ "x-api-key": "personal-key" }); + mockGetApiKeyAccountId.mockResolvedValue("account-123"); mockCanAccessAccount.mockResolvedValue(false); const result = await validateAuthContext(request as never, { - accountId: "non-member-account", + accountId: "different-account-456", }); expect(result).toBeInstanceOf(NextResponse); const response = result as NextResponse; expect(response.status).toBe(403); + const body = await response.json(); + expect(body.error).toBe("Access denied to specified account_id"); }); }); describe("organization_id validation", () => { - it("allows access when account is a member of the organization", async () => { + it("sets orgId from organizationId when account is a member", async () => { const request = createMockRequest({ "x-api-key": "personal-key" }); mockGetApiKeyAccountId.mockResolvedValue("account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "account-123", - orgId: null, - name: "personal-key", - }); mockValidateOrganizationAccess.mockResolvedValue(true); const result = await validateAuthContext(request as never, { @@ -297,23 +201,14 @@ describe("validateAuthContext", () => { expect(result).not.toBeInstanceOf(NextResponse); expect(result).toEqual({ accountId: "account-123", - orgId: "org-789", // orgId is set from organizationId input + orgId: "org-789", authToken: "personal-key", }); - expect(mockValidateOrganizationAccess).toHaveBeenCalledWith({ - accountId: "account-123", - organizationId: "org-789", - }); }); it("denies access when account is NOT a member of the organization", async () => { const request = createMockRequest({ "x-api-key": "personal-key" }); mockGetApiKeyAccountId.mockResolvedValue("account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "account-123", - orgId: null, - name: "personal-key", - }); mockValidateOrganizationAccess.mockResolvedValue(false); const result = await validateAuthContext(request as never, { @@ -330,11 +225,6 @@ describe("validateAuthContext", () => { it("skips organization validation when organizationId is null", async () => { const request = createMockRequest({ "x-api-key": "personal-key" }); mockGetApiKeyAccountId.mockResolvedValue("account-123"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "account-123", - orgId: null, - name: "personal-key", - }); const result = await validateAuthContext(request as never, { organizationId: null, @@ -347,26 +237,25 @@ describe("validateAuthContext", () => { describe("combined account_id and organization_id validation", () => { it("validates organization access using the overridden accountId", async () => { - const request = createMockRequest({ "x-api-key": "org-key" }); - mockGetApiKeyAccountId.mockResolvedValue("org-admin-account"); - mockGetApiKeyDetails.mockResolvedValue({ - accountId: "org-admin-account", - orgId: "org-123", - name: "org-key", - }); + const request = createMockRequest({ "x-api-key": "personal-key" }); + mockGetApiKeyAccountId.mockResolvedValue("account-123"); mockCanAccessAccount.mockResolvedValue(true); mockValidateOrganizationAccess.mockResolvedValue(true); const result = await validateAuthContext(request as never, { accountId: "member-account-456", - organizationId: "different-org-789", + organizationId: "org-789", }); expect(result).not.toBeInstanceOf(NextResponse); - // Verify that organization access was validated with the overridden accountId expect(mockValidateOrganizationAccess).toHaveBeenCalledWith({ - accountId: "member-account-456", // The overridden account - organizationId: "different-org-789", + accountId: "member-account-456", + organizationId: "org-789", + }); + expect(result).toEqual({ + accountId: "member-account-456", + orgId: "org-789", + authToken: "personal-key", }); }); }); diff --git a/lib/auth/validateAuthContext.ts b/lib/auth/validateAuthContext.ts index 1641c33d4..b2e8b4848 100644 --- a/lib/auth/validateAuthContext.ts +++ b/lib/auth/validateAuthContext.ts @@ -3,7 +3,6 @@ import { NextResponse } from "next/server"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId"; import { getAuthenticatedAccountId } from "@/lib/auth/getAuthenticatedAccountId"; -import { getApiKeyDetails } from "@/lib/keys/getApiKeyDetails"; import { validateOrganizationAccess } from "@/lib/organizations/validateOrganizationAccess"; import { validateAccountIdOverride } from "@/lib/auth/validateAccountIdOverride"; @@ -62,7 +61,6 @@ export async function validateAuthContext( } let accountId: string; - let orgId: string | null = null; let authToken: string; if (hasApiKey) { @@ -73,12 +71,6 @@ export async function validateAuthContext( } accountId = accountIdOrError; authToken = apiKey!; - - // Get org context from API key details - const keyDetails = await getApiKeyDetails(apiKey!); - if (keyDetails) { - orgId = keyDetails.orgId; - } } else { // Validate bearer token authentication const accountIdOrError = await getAuthenticatedAccountId(request); @@ -116,14 +108,11 @@ export async function validateAuthContext( { status: 403, headers: getCorsHeaders() }, ); } - - // Use the provided organizationId as the org context - orgId = input.organizationId; } return { accountId, - orgId, + orgId: input.organizationId ?? null, authToken, }; } diff --git a/lib/chat/__tests__/validateChatRequest.test.ts b/lib/chat/__tests__/validateChatRequest.test.ts index 2f7651d08..b420c598d 100644 --- a/lib/chat/__tests__/validateChatRequest.test.ts +++ b/lib/chat/__tests__/validateChatRequest.test.ts @@ -237,11 +237,10 @@ describe("validateChatRequest", () => { expect(json.status).toBe("error"); }); - it("returns orgId for org API key", async () => { + it("returns accountId for org API key", async () => { mockGetApiKeyAccountId.mockResolvedValue("org-account-123"); mockGetApiKeyDetails.mockResolvedValue({ accountId: "org-account-123", - orgId: "org-account-123", }); const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "org-api-key" }); @@ -250,14 +249,12 @@ describe("validateChatRequest", () => { expect(result).not.toBeInstanceOf(NextResponse); expect((result as any).accountId).toBe("org-account-123"); - expect((result as any).orgId).toBe("org-account-123"); }); - it("returns null orgId for personal API key", async () => { + it("returns accountId for personal API key", async () => { mockGetApiKeyAccountId.mockResolvedValue("personal-account-123"); mockGetApiKeyDetails.mockResolvedValue({ accountId: "personal-account-123", - orgId: null, }); const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "personal-api-key" }); @@ -266,10 +263,9 @@ describe("validateChatRequest", () => { expect(result).not.toBeInstanceOf(NextResponse); expect((result as any).accountId).toBe("personal-account-123"); - expect((result as any).orgId).toBeNull(); }); - it("returns null orgId for bearer token auth", async () => { + it("returns accountId for bearer token auth", async () => { mockGetAuthenticatedAccountId.mockResolvedValue("jwt-account-456"); const request = createMockRequest( @@ -281,7 +277,6 @@ describe("validateChatRequest", () => { expect(result).not.toBeInstanceOf(NextResponse); expect((result as any).accountId).toBe("jwt-account-456"); - expect((result as any).orgId).toBeNull(); }); }); @@ -491,7 +486,6 @@ describe("validateChatRequest", () => { const result = await validateChatRequest(request as any); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).orgId).toBe("org-456"); expect(mockValidateOrganizationAccess).toHaveBeenCalledWith({ accountId: "user-account-123", organizationId: "org-456", @@ -502,7 +496,6 @@ describe("validateChatRequest", () => { mockGetApiKeyAccountId.mockResolvedValue("api-key-account-123"); mockGetApiKeyDetails.mockResolvedValue({ accountId: "api-key-account-123", - orgId: null, }); mockValidateOrganizationAccess.mockResolvedValue(true); @@ -512,9 +505,6 @@ describe("validateChatRequest", () => { ); const result = await validateChatRequest(request as any); - - expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).orgId).toBe("org-789"); expect(mockValidateOrganizationAccess).toHaveBeenCalledWith({ accountId: "api-key-account-123", organizationId: "org-789", @@ -525,7 +515,6 @@ describe("validateChatRequest", () => { mockGetApiKeyAccountId.mockResolvedValue("org-account-123"); mockGetApiKeyDetails.mockResolvedValue({ accountId: "org-account-123", - orgId: "original-org-123", }); mockValidateOrganizationAccess.mockResolvedValue(true); @@ -537,7 +526,6 @@ describe("validateChatRequest", () => { const result = await validateChatRequest(request as any); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).orgId).toBe("different-org-456"); }); it("rejects organizationId when user is NOT a member of org", async () => { @@ -561,7 +549,6 @@ describe("validateChatRequest", () => { mockGetApiKeyAccountId.mockResolvedValue("org-account-123"); mockGetApiKeyDetails.mockResolvedValue({ accountId: "org-account-123", - orgId: "api-key-org-123", }); const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "org-api-key" }); @@ -569,7 +556,6 @@ describe("validateChatRequest", () => { const result = await validateChatRequest(request as any); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).orgId).toBe("api-key-org-123"); // Should not validate org access when no organizationId is provided expect(mockValidateOrganizationAccess).not.toHaveBeenCalled(); }); @@ -585,7 +571,6 @@ describe("validateChatRequest", () => { const result = await validateChatRequest(request as any); expect(result).not.toBeInstanceOf(NextResponse); - expect((result as any).orgId).toBeNull(); expect(mockValidateOrganizationAccess).not.toHaveBeenCalled(); }); }); diff --git a/lib/chat/validateChatRequest.ts b/lib/chat/validateChatRequest.ts index 92219e6d5..e0d80adb4 100644 --- a/lib/chat/validateChatRequest.ts +++ b/lib/chat/validateChatRequest.ts @@ -7,7 +7,6 @@ import { getAuthenticatedAccountId } from "@/lib/auth/getAuthenticatedAccountId" import { validateOverrideAccountId } from "@/lib/accounts/validateOverrideAccountId"; import { getMessages } from "@/lib/messages/getMessages"; import convertToUiMessages from "@/lib/messages/convertToUiMessages"; -import { getApiKeyDetails } from "@/lib/keys/getApiKeyDetails"; import { validateOrganizationAccess } from "@/lib/organizations/validateOrganizationAccess"; import { setupConversation } from "@/lib/chat/setupConversation"; import { validateMessages } from "@/lib/chat/validateMessages"; @@ -119,13 +118,7 @@ export async function validateChatRequest( } accountId = accountIdOrError; - // Get org context from API key details - const keyDetails = await getApiKeyDetails(apiKey!); - if (keyDetails) { - orgId = keyDetails.orgId; - } - - // Handle accountId override for org API keys + // Handle accountId override if (validatedBody.accountId) { const overrideResult = await validateOverrideAccountId({ apiKey, diff --git a/lib/chats/__tests__/buildGetChatsParams.test.ts b/lib/chats/__tests__/buildGetChatsParams.test.ts index f62456094..865f38a22 100644 --- a/lib/chats/__tests__/buildGetChatsParams.test.ts +++ b/lib/chats/__tests__/buildGetChatsParams.test.ts @@ -2,215 +2,115 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { buildGetChatsParams } from "../buildGetChatsParams"; import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; vi.mock("@/lib/organizations/canAccessAccount", () => ({ canAccessAccount: vi.fn(), })); -vi.mock("@/lib/supabase/account_organization_ids/getAccountOrganizations", () => ({ - getAccountOrganizations: vi.fn(), -})); - -vi.mock("@/lib/const", () => ({ - RECOUP_ORG_ID: "recoup-org-id", -})); - describe("buildGetChatsParams", () => { beforeEach(() => { vi.clearAllMocks(); }); - describe("personal API key (org_id = null)", () => { - it("returns account_ids with key owner when no target_account_id", async () => { - const result = await buildGetChatsParams({ - account_id: "account-123", - org_id: null, - }); - - expect(result).toEqual({ - params: { account_ids: ["account-123"], artist_id: undefined }, - error: null, - }); - }); - - it("returns error when personal key tries to filter by account_id", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(false); - - const result = await buildGetChatsParams({ - account_id: "account-123", - org_id: null, - target_account_id: "other-account", - }); - - expect(result).toEqual({ - params: null, - error: "Access denied to specified account_id", - }); - }); - - it("allows personal key to access target_account_id via shared org", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(true); - - const result = await buildGetChatsParams({ - account_id: "personal-123", - org_id: null, - target_account_id: "shared-org-member", - }); - - expect(canAccessAccount).toHaveBeenCalledWith({ - targetAccountId: "shared-org-member", - currentAccountId: "personal-123", - }); - expect(result).toEqual({ - params: { account_ids: ["shared-org-member"], artist_id: undefined }, - error: null, - }); - }); - - it("includes artist_id filter for personal key", async () => { - const result = await buildGetChatsParams({ - account_id: "account-123", - org_id: null, - artist_id: "artist-456", - }); - - expect(result).toEqual({ - params: { account_ids: ["account-123"], artist_id: "artist-456" }, - error: null, - }); + it("returns account_ids with key owner when no target_account_id", async () => { + const result = await buildGetChatsParams({ + account_id: "account-123", + }); + + expect(result).toEqual({ + params: { account_ids: ["account-123"], artist_id: undefined }, + error: null, }); }); - describe("organization API key", () => { - it("fetches org member account_ids when no target_account_id", async () => { - vi.mocked(getAccountOrganizations).mockResolvedValue([ - { account_id: "member-1", organization_id: "org-123", organization: null }, - { account_id: "member-2", organization_id: "org-123", organization: null }, - { account_id: "member-3", organization_id: "org-123", organization: null }, - ]); - - const result = await buildGetChatsParams({ - account_id: "org-123", - org_id: "org-123", - }); - - expect(getAccountOrganizations).toHaveBeenCalledWith({ organizationId: "org-123" }); - expect(result).toEqual({ - params: { account_ids: ["member-1", "member-2", "member-3"], artist_id: undefined }, - error: null, - }); - }); - - it("allows filtering by account_id if member of org", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(true); - - const result = await buildGetChatsParams({ - account_id: "account-123", - org_id: "org-123", - target_account_id: "member-account", - }); - - expect(result).toEqual({ - params: { account_ids: ["member-account"], artist_id: undefined }, - error: null, - }); - expect(canAccessAccount).toHaveBeenCalledWith({ - targetAccountId: "member-account", - currentAccountId: "account-123", - }); - }); - - it("returns error when account_id is not member of org", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(false); - - const result = await buildGetChatsParams({ - account_id: "account-123", - org_id: "org-123", - target_account_id: "non-member-account", - }); - - expect(result).toEqual({ - params: null, - error: "Access denied to specified account_id", - }); - }); - - it("includes artist_id filter for org key", async () => { - vi.mocked(getAccountOrganizations).mockResolvedValue([ - { account_id: "member-1", organization_id: "org-123", organization: null }, - ]); - - const result = await buildGetChatsParams({ - account_id: "account-123", - org_id: "org-123", - artist_id: "artist-456", - }); - - expect(result).toEqual({ - params: { account_ids: ["member-1"], artist_id: "artist-456" }, - error: null, - }); - }); - - it("returns empty account_ids when org has no members", async () => { - vi.mocked(getAccountOrganizations).mockResolvedValue([]); - - const result = await buildGetChatsParams({ - account_id: "org-123", - org_id: "org-123", - }); - - expect(result).toEqual({ - params: { account_ids: [], artist_id: undefined }, - error: null, - }); + it("returns error when personal key tries to filter by account_id", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(false); + + const result = await buildGetChatsParams({ + account_id: "account-123", + target_account_id: "other-account", + }); + + expect(result).toEqual({ + params: null, + error: "Access denied to specified account_id", }); }); - describe("Recoup admin key", () => { - const recoupOrgId = "recoup-org-id"; - - it("returns empty params (no filter) to get all records", async () => { - const result = await buildGetChatsParams({ - account_id: "admin-account", - org_id: recoupOrgId, - }); - - expect(result).toEqual({ - params: { artist_id: undefined }, - error: null, - }); - // Should NOT call getAccountOrganizations for admin - expect(getAccountOrganizations).not.toHaveBeenCalled(); - }); - - it("allows filtering by any account_id", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(true); - - const result = await buildGetChatsParams({ - account_id: "admin-account", - org_id: recoupOrgId, - target_account_id: "any-account", - }); - - expect(result).toEqual({ - params: { account_ids: ["any-account"], artist_id: undefined }, - error: null, - }); - }); - - it("includes artist_id filter for admin key", async () => { - const result = await buildGetChatsParams({ - account_id: "admin-account", - org_id: recoupOrgId, - artist_id: "artist-456", - }); - - expect(result).toEqual({ - params: { artist_id: "artist-456" }, - error: null, - }); + it("allows access to target_account_id via shared org", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(true); + + const result = await buildGetChatsParams({ + account_id: "personal-123", + target_account_id: "shared-org-member", + }); + + expect(canAccessAccount).toHaveBeenCalledWith({ + targetAccountId: "shared-org-member", + currentAccountId: "personal-123", + }); + expect(result).toEqual({ + params: { account_ids: ["shared-org-member"], artist_id: undefined }, + error: null, + }); + }); + + it("includes artist_id filter", async () => { + const result = await buildGetChatsParams({ + account_id: "account-123", + artist_id: "artist-456", + }); + + expect(result).toEqual({ + params: { account_ids: ["account-123"], artist_id: "artist-456" }, + error: null, + }); + }); + + it("allows filtering by account_id when access is granted", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(true); + + const result = await buildGetChatsParams({ + account_id: "account-123", + target_account_id: "member-account", + }); + + expect(result).toEqual({ + params: { account_ids: ["member-account"], artist_id: undefined }, + error: null, + }); + expect(canAccessAccount).toHaveBeenCalledWith({ + targetAccountId: "member-account", + currentAccountId: "account-123", + }); + }); + + it("returns error when account_id filter is denied", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(false); + + const result = await buildGetChatsParams({ + account_id: "account-123", + target_account_id: "non-member-account", + }); + + expect(result).toEqual({ + params: null, + error: "Access denied to specified account_id", + }); + }); + + it("includes artist_id with target_account_id", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(true); + + const result = await buildGetChatsParams({ + account_id: "account-123", + target_account_id: "member-account", + artist_id: "artist-456", + }); + + expect(result).toEqual({ + params: { account_ids: ["member-account"], artist_id: "artist-456" }, + error: null, }); }); }); diff --git a/lib/chats/__tests__/getChatsHandler.test.ts b/lib/chats/__tests__/getChatsHandler.test.ts index f832eaf62..5a85935e1 100644 --- a/lib/chats/__tests__/getChatsHandler.test.ts +++ b/lib/chats/__tests__/getChatsHandler.test.ts @@ -124,23 +124,16 @@ describe("getChatsHandler", () => { }); describe("org key behavior", () => { - it("returns chats for all org members without account_id param", async () => { + it("returns chats for org key owner without account_id param", async () => { const orgId = "123e4567-e89b-12d3-a456-426614174000"; const mockChats = [ { id: "chat-1", - account_id: "member-1", + account_id: orgId, artist_id: null, topic: "Chat 1", updated_at: "2024-01-01T00:00:00Z", }, - { - id: "chat-2", - account_id: "member-2", - artist_id: null, - topic: "Chat 2", - updated_at: "2024-01-02T00:00:00Z", - }, ]; vi.mocked(validateAuthContext).mockResolvedValue({ @@ -148,10 +141,6 @@ describe("getChatsHandler", () => { orgId, authToken: "test-token", }); - vi.mocked(getAccountOrganizations).mockResolvedValue([ - { account_id: "member-1", organization_id: orgId, organization: null }, - { account_id: "member-2", organization_id: orgId, organization: null }, - ]); vi.mocked(selectRooms).mockResolvedValue(mockChats); const request = createMockRequest("http://localhost/api/chats"); @@ -162,7 +151,7 @@ describe("getChatsHandler", () => { expect(json.status).toBe("success"); expect(json.chats).toEqual(mockChats); expect(selectRooms).toHaveBeenCalledWith({ - account_ids: ["member-1", "member-2"], + account_ids: [orgId], artist_id: undefined, }); }); @@ -223,12 +212,12 @@ describe("getChatsHandler", () => { }); describe("Recoup admin key behavior", () => { - it("returns all chats without account_id param", async () => { + it("returns chats for admin account without account_id param", async () => { const recoupOrgId = "recoup-org-id"; const mockChats = [ { id: "chat-1", - account_id: "any-account", + account_id: recoupOrgId, artist_id: null, topic: "Chat 1", updated_at: "2024-01-01T00:00:00Z", @@ -249,6 +238,7 @@ describe("getChatsHandler", () => { expect(response.status).toBe(200); expect(json.status).toBe("success"); expect(selectRooms).toHaveBeenCalledWith({ + account_ids: [recoupOrgId], artist_id: undefined, }); }); diff --git a/lib/chats/__tests__/validateGetChatsRequest.test.ts b/lib/chats/__tests__/validateGetChatsRequest.test.ts index 346ba40f0..ea86c5abd 100644 --- a/lib/chats/__tests__/validateGetChatsRequest.test.ts +++ b/lib/chats/__tests__/validateGetChatsRequest.test.ts @@ -65,17 +65,13 @@ describe("validateGetChatsRequest", () => { }); }); - it("should return org member account_ids for org key", async () => { + it("should return account_ids for org key", async () => { const mockOrgId = "org-123"; vi.mocked(validateAuthContext).mockResolvedValue({ accountId: mockOrgId, orgId: mockOrgId, authToken: "test-token", }); - vi.mocked(getAccountOrganizations).mockResolvedValue([ - { account_id: "member-1", organization_id: mockOrgId, organization: null }, - { account_id: "member-2", organization_id: mockOrgId, organization: null }, - ]); const request = new NextRequest("http://localhost/api/chats", { headers: { "x-api-key": "test-api-key" }, @@ -84,12 +80,12 @@ describe("validateGetChatsRequest", () => { expect(result).not.toBeInstanceOf(NextResponse); expect(result).toEqual({ - account_ids: ["member-1", "member-2"], + account_ids: [mockOrgId], artist_id: undefined, }); }); - it("should return undefined account_ids for Recoup admin key", async () => { + it("should return account_ids for Recoup admin key", async () => { const recoupOrgId = "recoup-org-id"; vi.mocked(validateAuthContext).mockResolvedValue({ accountId: recoupOrgId, @@ -104,7 +100,7 @@ describe("validateGetChatsRequest", () => { expect(result).not.toBeInstanceOf(NextResponse); expect(result).toEqual({ - account_ids: undefined, + account_ids: [recoupOrgId], artist_id: undefined, }); }); diff --git a/lib/chats/buildGetChatsParams.ts b/lib/chats/buildGetChatsParams.ts index 8add4c82f..9273bc100 100644 --- a/lib/chats/buildGetChatsParams.ts +++ b/lib/chats/buildGetChatsParams.ts @@ -1,13 +1,9 @@ import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; import type { SelectRoomsParams } from "@/lib/supabase/rooms/selectRooms"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; -import { RECOUP_ORG_ID } from "@/lib/const"; export interface BuildGetChatsParamsInput { /** The authenticated account ID */ account_id: string; - /** The organization ID from the API key (null for personal keys) */ - org_id: string | null; /** Optional target account ID to filter by */ target_account_id?: string; /** Optional artist ID to filter by */ @@ -21,10 +17,7 @@ export type BuildGetChatsParamsResult = /** * Builds the parameters for selectRooms based on auth context. * - * For personal keys: Returns account_ids with the key owner's account - * For org keys: Fetches all org member account_ids and returns them - * For Recoup admin key: Returns empty params to indicate ALL records - * + * Returns account_ids with the key owner's account. * If target_account_id is provided, validates access and returns that account. * * @param input - The auth context and optional filters @@ -33,7 +26,7 @@ export type BuildGetChatsParamsResult = export async function buildGetChatsParams( input: BuildGetChatsParamsInput, ): Promise { - const { account_id, org_id, target_account_id, artist_id } = input; + const { account_id, target_account_id, artist_id } = input; // Handle account_id filter if provided if (target_account_id) { @@ -50,19 +43,6 @@ export async function buildGetChatsParams( return { params: { account_ids: [target_account_id], artist_id }, error: null }; } - // No account_id filter - determine what to return based on key type - if (org_id === RECOUP_ORG_ID) { - // Recoup admin: return undefined to indicate ALL records - return { params: { artist_id }, error: null }; - } - - if (org_id) { - // Org key: fetch all member account IDs for this organization - const orgMembers = await getAccountOrganizations({ organizationId: org_id }); - const memberAccountIds = orgMembers.map(member => member.account_id); - return { params: { account_ids: memberAccountIds, artist_id }, error: null }; - } - - // Personal key: Only return the key owner's account + // Return the key owner's account return { params: { account_ids: [account_id], artist_id }, error: null }; } diff --git a/lib/chats/validateGetChatsRequest.ts b/lib/chats/validateGetChatsRequest.ts index da68c1160..e32fa127e 100644 --- a/lib/chats/validateGetChatsRequest.ts +++ b/lib/chats/validateGetChatsRequest.ts @@ -56,12 +56,11 @@ export async function validateGetChatsRequest( return authResult; } - const { accountId: account_id, orgId: org_id } = authResult; + const { accountId: account_id } = authResult; // Use shared function to build params const { params, error } = await buildGetChatsParams({ account_id, - org_id, target_account_id, artist_id, }); diff --git a/lib/chats/validateUpdateChatBody.ts b/lib/chats/validateUpdateChatBody.ts index 63b48f38f..95e3c61f6 100644 --- a/lib/chats/validateUpdateChatBody.ts +++ b/lib/chats/validateUpdateChatBody.ts @@ -84,7 +84,6 @@ export async function validateUpdateChatBody( // Check access control const { params } = await buildGetChatsParams({ account_id: accountId, - org_id: orgId, }); // If params.account_ids is undefined, it means admin access (all records) diff --git a/lib/keys/__tests__/getApiKeyDetails.test.ts b/lib/keys/__tests__/getApiKeyDetails.test.ts index a15257912..12ef8b3d9 100644 --- a/lib/keys/__tests__/getApiKeyDetails.test.ts +++ b/lib/keys/__tests__/getApiKeyDetails.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { getApiKeyDetails } from "../getApiKeyDetails"; import { selectAccountApiKeys } from "@/lib/supabase/account_api_keys/selectAccountApiKeys"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; // Mock dependencies vi.mock("@/lib/keys/hashApiKey", () => ({ @@ -17,23 +16,19 @@ vi.mock("@/lib/supabase/account_api_keys/selectAccountApiKeys", () => ({ selectAccountApiKeys: vi.fn(), })); -vi.mock("@/lib/supabase/account_organization_ids/getAccountOrganizations", () => ({ - getAccountOrganizations: vi.fn(), -})); - describe("getApiKeyDetails", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("valid API keys", () => { - it("returns accountId and null orgId for personal API key", async () => { - const personalAccountId = "personal-account-123"; + it("returns accountId for a valid API key", async () => { + const accountId = "personal-account-123"; vi.mocked(selectAccountApiKeys).mockResolvedValue([ { id: "key-1", - account: personalAccountId, + account: accountId, name: "My API Key", key_hash: "hashed_test_api_key", created_at: "2024-01-01T00:00:00Z", @@ -41,51 +36,10 @@ describe("getApiKeyDetails", () => { }, ]); - // Mock getAccountOrganizations to return empty array (not an org) - vi.mocked(getAccountOrganizations).mockResolvedValue([]); - const result = await getApiKeyDetails("test_api_key"); expect(result).toEqual({ - accountId: personalAccountId, - orgId: null, - }); - expect(getAccountOrganizations).toHaveBeenCalledWith({ - organizationId: personalAccountId, - }); - }); - - it("returns accountId and orgId for organization API key", async () => { - const orgId = "org-123"; - - vi.mocked(selectAccountApiKeys).mockResolvedValue([ - { - id: "key-1", - account: orgId, - name: "Org API Key", - key_hash: "hashed_org_api_key", - created_at: "2024-01-01T00:00:00Z", - last_used: null, - }, - ]); - - // Mock getAccountOrganizations to return members (is an org) - vi.mocked(getAccountOrganizations).mockResolvedValue([ - { - account_id: "member-1", - organization_id: orgId, - organization: null, - }, - ]); - - const result = await getApiKeyDetails("org_api_key"); - - expect(result).toEqual({ - accountId: orgId, - orgId: orgId, - }); - expect(getAccountOrganizations).toHaveBeenCalledWith({ - organizationId: orgId, + accountId, }); }); }); diff --git a/lib/keys/getApiKeyDetails.ts b/lib/keys/getApiKeyDetails.ts index d4d3a7c50..44ca42ea4 100644 --- a/lib/keys/getApiKeyDetails.ts +++ b/lib/keys/getApiKeyDetails.ts @@ -1,21 +1,19 @@ import { hashApiKey } from "@/lib/keys/hashApiKey"; import { PRIVY_PROJECT_SECRET } from "@/lib/const"; import { selectAccountApiKeys } from "@/lib/supabase/account_api_keys/selectAccountApiKeys"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; export interface ApiKeyDetails { accountId: string; - orgId: string | null; } /** - * Retrieves details for an API key including the account ID and organization context. + * Retrieves the account ID for an API key. * - * For organization API keys, orgId will be set to the organization's account ID. - * For personal API keys, orgId will be null. + * All API keys are personal — they resolve to the account that created them. + * Org access is determined at access-check time via account memberships. * * @param apiKey - The raw API key string - * @returns ApiKeyDetails object with accountId and orgId, or null if key is invalid + * @returns ApiKeyDetails with accountId, or null if key is invalid */ export async function getApiKeyDetails(apiKey: string): Promise { if (!apiKey) { @@ -36,14 +34,7 @@ export async function getApiKeyDetails(apiKey: string): Promise 0 ? accountId : null; - - return { - accountId, - orgId, - }; + return { accountId }; } catch (error) { console.error("[ERROR] getApiKeyDetails:", error); return null; diff --git a/lib/mcp/__tests__/verifyApiKey.test.ts b/lib/mcp/__tests__/verifyApiKey.test.ts index 137250ca5..569368407 100644 --- a/lib/mcp/__tests__/verifyApiKey.test.ts +++ b/lib/mcp/__tests__/verifyApiKey.test.ts @@ -22,7 +22,7 @@ describe("verifyBearerToken", () => { expect(result).toBeUndefined(); }); - it("returns auth info with orgId: null for Privy JWT", async () => { + it("returns auth info for Privy JWT", async () => { vi.mocked(getAccountIdByAuthToken).mockResolvedValue("privy-account-123"); const result = await verifyBearerToken(new Request("http://localhost"), "privy-jwt-token"); @@ -33,12 +33,11 @@ describe("verifyBearerToken", () => { clientId: "privy-account-123", extra: { accountId: "privy-account-123", - orgId: null, }, }); }); - it("returns auth info with orgId for org API key", async () => { + it("returns auth info for org API key", async () => { vi.mocked(getAccountIdByAuthToken).mockRejectedValue(new Error("Invalid JWT")); vi.mocked(getApiKeyDetails).mockResolvedValue({ accountId: "org-account-123", @@ -53,12 +52,11 @@ describe("verifyBearerToken", () => { clientId: "org-account-123", extra: { accountId: "org-account-123", - orgId: "org-account-123", }, }); }); - it("returns auth info with orgId: null for personal API key", async () => { + it("returns auth info for personal API key", async () => { vi.mocked(getAccountIdByAuthToken).mockRejectedValue(new Error("Invalid JWT")); vi.mocked(getApiKeyDetails).mockResolvedValue({ accountId: "personal-account-123", @@ -73,7 +71,6 @@ describe("verifyBearerToken", () => { clientId: "personal-account-123", extra: { accountId: "personal-account-123", - orgId: null, }, }); }); diff --git a/lib/mcp/tools/chats/__tests__/registerGetChatsTool.test.ts b/lib/mcp/tools/chats/__tests__/registerGetChatsTool.test.ts index 3736f7de4..4cceb5de3 100644 --- a/lib/mcp/tools/chats/__tests__/registerGetChatsTool.test.ts +++ b/lib/mcp/tools/chats/__tests__/registerGetChatsTool.test.ts @@ -7,7 +7,6 @@ import { registerGetChatsTool } from "../registerGetChatsTool"; const mockSelectRooms = vi.fn(); const mockCanAccessAccount = vi.fn(); -const mockGetAccountOrganizations = vi.fn(); vi.mock("@/lib/supabase/rooms/selectRooms", () => ({ selectRooms: (...args: unknown[]) => mockSelectRooms(...args), @@ -17,28 +16,16 @@ vi.mock("@/lib/organizations/canAccessAccount", () => ({ canAccessAccount: (...args: unknown[]) => mockCanAccessAccount(...args), })); -vi.mock("@/lib/supabase/account_organization_ids/getAccountOrganizations", () => ({ - getAccountOrganizations: (...args: unknown[]) => mockGetAccountOrganizations(...args), -})); - -vi.mock("@/lib/const", () => ({ - RECOUP_ORG_ID: "recoup-org-id", -})); - type ServerRequestHandlerExtra = RequestHandlerExtra; /** * Creates a mock extra object with optional authInfo. * - * @param authInfo - Optional auth info object containing account and org IDs. + * @param authInfo - Optional auth info object containing account ID. * @param authInfo.accountId - The account ID for authentication. - * @param authInfo.orgId - The organization ID (null for personal keys). * @returns A mock ServerRequestHandlerExtra object. */ -function createMockExtra(authInfo?: { - accountId?: string; - orgId?: string | null; -}): ServerRequestHandlerExtra { +function createMockExtra(authInfo?: { accountId?: string }): ServerRequestHandlerExtra { return { authInfo: authInfo ? { @@ -47,7 +34,6 @@ function createMockExtra(authInfo?: { clientId: authInfo.accountId, extra: { accountId: authInfo.accountId, - orgId: authInfo.orgId ?? null, }, } : undefined, @@ -130,7 +116,7 @@ describe("registerGetChatsTool", () => { }); }); - it("allows account_id override for org auth with access", async () => { + it("allows account_id override with access", async () => { mockCanAccessAccount.mockResolvedValue(true); mockSelectRooms.mockResolvedValue([ { @@ -144,7 +130,7 @@ describe("registerGetChatsTool", () => { await registeredHandler( { account_id: "target-account-789" }, - createMockExtra({ accountId: "org-account-id", orgId: "org-account-id" }), + createMockExtra({ accountId: "org-account-id" }), ); expect(mockCanAccessAccount).toHaveBeenCalledWith({ @@ -157,12 +143,12 @@ describe("registerGetChatsTool", () => { }); }); - it("returns error when org auth lacks access to account_id", async () => { + it("returns error when auth lacks access to account_id", async () => { mockCanAccessAccount.mockResolvedValue(false); const result = await registeredHandler( { account_id: "target-account-789" }, - createMockExtra({ accountId: "org-account-id", orgId: "org-account-id" }), + createMockExtra({ accountId: "org-account-id" }), ); expect(result).toEqual({ @@ -188,62 +174,6 @@ describe("registerGetChatsTool", () => { }); }); - it("returns ALL chats for Recoup Admin key (no filter)", async () => { - const allChats = [ - { id: "chat-1", account_id: "account-1", artist_id: null, topic: "Chat 1" }, - { id: "chat-2", account_id: "account-2", artist_id: null, topic: "Chat 2" }, - { id: "chat-3", account_id: "account-3", artist_id: null, topic: "Chat 3" }, - ]; - mockSelectRooms.mockResolvedValue(allChats); - - const result = await registeredHandler( - {}, - createMockExtra({ accountId: "recoup-org-id", orgId: "recoup-org-id" }), - ); - - // Should call selectRooms with no account_ids filter (no account_ids key) - expect(mockSelectRooms).toHaveBeenCalledWith({ artist_id: undefined }); - expect(result).toEqual({ - content: [ - { - type: "text", - text: expect.stringContaining('"chats":['), - }, - ], - }); - }); - - it("returns chats for org members when using org key", async () => { - mockGetAccountOrganizations.mockResolvedValue([ - { account_id: "member-1", organization_id: "org-123", organization: null }, - { account_id: "member-2", organization_id: "org-123", organization: null }, - ]); - const orgChats = [ - { id: "chat-1", account_id: "member-1", artist_id: null, topic: "Chat 1" }, - { id: "chat-2", account_id: "member-2", artist_id: null, topic: "Chat 2" }, - ]; - mockSelectRooms.mockResolvedValue(orgChats); - - const result = await registeredHandler( - {}, - createMockExtra({ accountId: "org-123", orgId: "org-123" }), - ); - - // Should call selectRooms with member account_ids - expect(mockSelectRooms).toHaveBeenCalledWith({ - account_ids: ["member-1", "member-2"], - artist_id: undefined, - }); - expect(result).toEqual({ - content: [ - { - type: "text", - text: expect.stringContaining('"chats":['), - }, - ], - }); - }); - it("filters by artist_account_id when provided", async () => { const artistChats = [ { id: "chat-1", account_id: "account-123", artist_id: "artist-456", topic: "Artist Chat" }, @@ -281,7 +211,7 @@ describe("registerGetChatsTool", () => { const result = await registeredHandler( { account_id: "other-account" }, - createMockExtra({ accountId: "account-123", orgId: null }), + createMockExtra({ accountId: "account-123" }), ); expect(result).toEqual({ diff --git a/lib/mcp/tools/chats/registerCompactChatsTool.ts b/lib/mcp/tools/chats/registerCompactChatsTool.ts index 13b817ec5..3aef2496b 100644 --- a/lib/mcp/tools/chats/registerCompactChatsTool.ts +++ b/lib/mcp/tools/chats/registerCompactChatsTool.ts @@ -45,7 +45,6 @@ export function registerCompactChatsTool(server: McpServer): void { const authInfo = extra.authInfo as McpAuthInfo | undefined; const accountId = authInfo?.extra?.accountId; - const orgId = authInfo?.extra?.orgId ?? null; if (!accountId) { return getToolResultError( diff --git a/lib/mcp/tools/chats/registerGetChatsTool.ts b/lib/mcp/tools/chats/registerGetChatsTool.ts index e070ea56a..7f999b4eb 100644 --- a/lib/mcp/tools/chats/registerGetChatsTool.ts +++ b/lib/mcp/tools/chats/registerGetChatsTool.ts @@ -37,7 +37,6 @@ export function registerGetChatsTool(server: McpServer): void { const authInfo = extra.authInfo as McpAuthInfo | undefined; const accountId = authInfo?.extra?.accountId; - const orgId = authInfo?.extra?.orgId ?? null; if (!accountId) { return getToolResultError( @@ -47,7 +46,6 @@ export function registerGetChatsTool(server: McpServer): void { const { params, error } = await buildGetChatsParams({ account_id: accountId, - org_id: orgId, target_account_id: account_id, artist_id: artist_account_id, }); diff --git a/lib/mcp/tools/pulse/__tests__/registerGetPulsesTool.test.ts b/lib/mcp/tools/pulse/__tests__/registerGetPulsesTool.test.ts index 75a9e709c..a0c80c8a9 100644 --- a/lib/mcp/tools/pulse/__tests__/registerGetPulsesTool.test.ts +++ b/lib/mcp/tools/pulse/__tests__/registerGetPulsesTool.test.ts @@ -15,10 +15,6 @@ vi.mock("@/lib/organizations/canAccessAccount", () => ({ canAccessAccount: vi.fn(), })); -vi.mock("@/lib/const", () => ({ - RECOUP_ORG_ID: "recoup-org-id", -})); - type ServerRequestHandlerExtra = RequestHandlerExtra; /** @@ -26,12 +22,8 @@ type ServerRequestHandlerExtra = RequestHandlerExtra { }); }); - it("returns ALL pulses for Recoup Admin key (no filter)", async () => { - const allPulses = [ - { id: "pulse-1", account_id: "account-1", active: true }, - { id: "pulse-2", account_id: "account-2", active: false }, - { id: "pulse-3", account_id: "account-3", active: true }, - ]; - mockSelectPulseAccounts.mockResolvedValue(allPulses); - - const result = await registeredHandler( - {}, - createMockExtra({ accountId: "recoup-org-id", orgId: "recoup-org-id" }), - ); - - // Should call selectPulseAccounts with empty object (no filter) - expect(mockSelectPulseAccounts).toHaveBeenCalledWith({}); - expect(result).toEqual({ - content: [ - { - type: "text", - text: expect.stringContaining('"pulses":['), - }, - ], - }); - }); - - it("returns pulses filtered by orgId for org key", async () => { - const orgPulses = [ - { id: "pulse-1", account_id: "member-1", active: true }, - { id: "pulse-2", account_id: "member-2", active: false }, - ]; - mockSelectPulseAccounts.mockResolvedValue(orgPulses); - - const result = await registeredHandler( - {}, - createMockExtra({ accountId: "org-123", orgId: "org-123" }), - ); - - // Should call selectPulseAccounts with orgId filter - expect(mockSelectPulseAccounts).toHaveBeenCalledWith({ orgId: "org-123" }); - expect(result).toEqual({ - content: [ - { - type: "text", - text: expect.stringContaining('"pulses":['), - }, - ], - }); - }); - it("filters by active status when provided", async () => { const activePulses = [{ id: "pulse-1", account_id: "account-123", active: true }]; mockSelectPulseAccounts.mockResolvedValue(activePulses); diff --git a/lib/mcp/tools/pulse/registerGetPulsesTool.ts b/lib/mcp/tools/pulse/registerGetPulsesTool.ts index 8b7050d19..935f4a051 100644 --- a/lib/mcp/tools/pulse/registerGetPulsesTool.ts +++ b/lib/mcp/tools/pulse/registerGetPulsesTool.ts @@ -49,11 +49,8 @@ export function registerGetPulsesTool(server: McpServer): void { return getToolResultError("Failed to resolve account ID"); } - const orgId = authInfo?.extra?.orgId ?? null; - const { params, error } = await buildGetPulsesParams({ accountId, - orgId, targetAccountId: undefined, active, }); diff --git a/lib/mcp/verifyApiKey.ts b/lib/mcp/verifyApiKey.ts index 5d367a4c8..95c7539f0 100644 --- a/lib/mcp/verifyApiKey.ts +++ b/lib/mcp/verifyApiKey.ts @@ -4,7 +4,6 @@ import { getApiKeyDetails } from "@/lib/keys/getApiKeyDetails"; export interface McpAuthInfoExtra extends Record { accountId: string; - orgId: string | null; } export interface McpAuthInfo extends AuthInfo { @@ -18,7 +17,7 @@ export interface McpAuthInfo extends AuthInfo { * * @param _req - The request object (unused). * @param bearerToken - The token from Authorization: Bearer header (Privy JWT or API key). - * @returns AuthInfo with accountId and orgId, or undefined if invalid. + * @returns AuthInfo with accountId, or undefined if invalid. */ export async function verifyBearerToken( _req: Request, @@ -36,16 +35,13 @@ export async function verifyBearerToken( token: bearerToken, scopes: ["mcp:tools"], clientId: accountId, - extra: { - accountId, - orgId: null, - }, + extra: { accountId }, }; } catch { // Privy validation failed, try API key } - // Try API key validation - includes org context + // Try API key validation try { const keyDetails = await getApiKeyDetails(bearerToken); @@ -57,10 +53,7 @@ export async function verifyBearerToken( token: bearerToken, scopes: ["mcp:tools"], clientId: keyDetails.accountId, - extra: { - accountId: keyDetails.accountId, - orgId: keyDetails.orgId, - }, + extra: { accountId: keyDetails.accountId }, }; } catch { return undefined; diff --git a/lib/organizations/__tests__/buildGetOrganizationsParams.test.ts b/lib/organizations/__tests__/buildGetOrganizationsParams.test.ts index 9a022aadd..814d886bb 100644 --- a/lib/organizations/__tests__/buildGetOrganizationsParams.test.ts +++ b/lib/organizations/__tests__/buildGetOrganizationsParams.test.ts @@ -7,10 +7,6 @@ vi.mock("@/lib/organizations/canAccessAccount", () => ({ canAccessAccount: vi.fn(), })); -vi.mock("@/lib/const", () => ({ - RECOUP_ORG_ID: "recoup-org-id", -})); - describe("buildGetOrganizationsParams", () => { beforeEach(() => { vi.clearAllMocks(); @@ -19,7 +15,6 @@ describe("buildGetOrganizationsParams", () => { it("returns accountId for personal key", async () => { const result = await buildGetOrganizationsParams({ accountId: "personal-account-123", - orgId: null, }); expect(result).toEqual({ @@ -28,36 +23,11 @@ describe("buildGetOrganizationsParams", () => { }); }); - it("returns organizationId for org key", async () => { - const result = await buildGetOrganizationsParams({ - accountId: "org-123", - orgId: "org-123", - }); - - expect(result).toEqual({ - params: { organizationId: "org-123" }, - error: null, - }); - }); - - it("returns empty params for Recoup admin key", async () => { - const result = await buildGetOrganizationsParams({ - accountId: "recoup-org-id", - orgId: "recoup-org-id", - }); - - expect(result).toEqual({ - params: {}, - error: null, - }); - }); - it("returns targetAccountId when access is granted", async () => { vi.mocked(canAccessAccount).mockResolvedValue(true); const result = await buildGetOrganizationsParams({ accountId: "org-123", - orgId: "org-123", targetAccountId: "target-456", }); @@ -76,7 +46,6 @@ describe("buildGetOrganizationsParams", () => { const result = await buildGetOrganizationsParams({ accountId: "personal-123", - orgId: null, targetAccountId: "shared-org-member", }); @@ -95,7 +64,6 @@ describe("buildGetOrganizationsParams", () => { const result = await buildGetOrganizationsParams({ accountId: "personal-123", - orgId: null, targetAccountId: "other-account", }); @@ -105,12 +73,11 @@ describe("buildGetOrganizationsParams", () => { }); }); - it("returns error when org key lacks access to targetAccountId", async () => { + it("returns error when access to targetAccountId is denied", async () => { vi.mocked(canAccessAccount).mockResolvedValue(false); const result = await buildGetOrganizationsParams({ accountId: "org-123", - orgId: "org-123", targetAccountId: "not-in-org", }); @@ -119,23 +86,4 @@ describe("buildGetOrganizationsParams", () => { error: "Access denied to specified account_id", }); }); - - it("returns targetAccountId for Recoup admin with filter", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(true); - - const result = await buildGetOrganizationsParams({ - accountId: "recoup-org-id", - orgId: "recoup-org-id", - targetAccountId: "any-account", - }); - - expect(canAccessAccount).toHaveBeenCalledWith({ - targetAccountId: "any-account", - currentAccountId: "recoup-org-id", - }); - expect(result).toEqual({ - params: { accountId: "any-account" }, - error: null, - }); - }); }); diff --git a/lib/organizations/buildGetOrganizationsParams.ts b/lib/organizations/buildGetOrganizationsParams.ts index f89505148..d7db0c873 100644 --- a/lib/organizations/buildGetOrganizationsParams.ts +++ b/lib/organizations/buildGetOrganizationsParams.ts @@ -1,12 +1,9 @@ import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; import type { GetAccountOrganizationsParams } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; -import { RECOUP_ORG_ID } from "@/lib/const"; export interface BuildGetOrganizationsParamsInput { /** The authenticated account ID */ accountId: string; - /** The organization ID from the API key (null for personal keys) */ - orgId: string | null; /** Optional target account ID to filter by */ targetAccountId?: string; } @@ -18,10 +15,7 @@ export type BuildGetOrganizationsParamsResult = /** * Builds the parameters for getAccountOrganizations based on auth context. * - * For personal keys: Returns accountId with the key owner's account - * For org keys: Returns organizationId for filtering by org membership - * For Recoup admin key: Returns empty params to indicate ALL records - * + * Returns accountId with the key owner's account. * If targetAccountId is provided, validates access and returns that account. * * @param input - The auth context and optional filters @@ -30,7 +24,7 @@ export type BuildGetOrganizationsParamsResult = export async function buildGetOrganizationsParams( input: BuildGetOrganizationsParamsInput, ): Promise { - const { accountId, orgId, targetAccountId } = input; + const { accountId, targetAccountId } = input; // Handle account_id filter if provided if (targetAccountId) { @@ -47,17 +41,6 @@ export async function buildGetOrganizationsParams( return { params: { accountId: targetAccountId }, error: null }; } - // No account_id filter - determine what to return based on key type - if (orgId === RECOUP_ORG_ID) { - // Recoup admin: return empty params to indicate ALL records - return { params: {}, error: null }; - } - - if (orgId) { - // Org key: return organizationId for filtering by org membership - return { params: { organizationId: orgId }, error: null }; - } - - // Personal key: Only return the key owner's organizations + // Return the key owner's organizations return { params: { accountId }, error: null }; } diff --git a/lib/organizations/validateGetOrganizationsRequest.ts b/lib/organizations/validateGetOrganizationsRequest.ts index 87a6858c3..65dc7c2f0 100644 --- a/lib/organizations/validateGetOrganizationsRequest.ts +++ b/lib/organizations/validateGetOrganizationsRequest.ts @@ -53,12 +53,11 @@ export async function validateGetOrganizationsRequest( return authResult; } - const { accountId, orgId } = authResult; + const { accountId } = authResult; // Use shared function to build params const { params, error } = await buildGetOrganizationsParams({ accountId, - orgId, targetAccountId, }); diff --git a/lib/pulse/__tests__/buildGetPulsesParams.test.ts b/lib/pulse/__tests__/buildGetPulsesParams.test.ts index 2a9707f4c..bac204202 100644 --- a/lib/pulse/__tests__/buildGetPulsesParams.test.ts +++ b/lib/pulse/__tests__/buildGetPulsesParams.test.ts @@ -7,10 +7,6 @@ vi.mock("@/lib/organizations/canAccessAccount", () => ({ canAccessAccount: vi.fn(), })); -vi.mock("@/lib/const", () => ({ - RECOUP_ORG_ID: "recoup-org-id", -})); - describe("buildGetPulsesParams", () => { beforeEach(() => { vi.clearAllMocks(); @@ -19,7 +15,6 @@ describe("buildGetPulsesParams", () => { it("returns accountIds for personal key", async () => { const result = await buildGetPulsesParams({ accountId: "personal-account-123", - orgId: null, }); expect(result).toEqual({ @@ -28,34 +23,9 @@ describe("buildGetPulsesParams", () => { }); }); - it("returns orgId for org key", async () => { - const result = await buildGetPulsesParams({ - accountId: "org-123", - orgId: "org-123", - }); - - expect(result).toEqual({ - params: { orgId: "org-123", active: undefined }, - error: null, - }); - }); - - it("returns empty params for Recoup admin key", async () => { - const result = await buildGetPulsesParams({ - accountId: "recoup-org-id", - orgId: "recoup-org-id", - }); - - expect(result).toEqual({ - params: { active: undefined }, - error: null, - }); - }); - it("includes active filter when provided", async () => { const result = await buildGetPulsesParams({ accountId: "account-123", - orgId: null, active: true, }); @@ -70,7 +40,6 @@ describe("buildGetPulsesParams", () => { const result = await buildGetPulsesParams({ accountId: "org-123", - orgId: "org-123", targetAccountId: "target-456", }); @@ -89,7 +58,6 @@ describe("buildGetPulsesParams", () => { const result = await buildGetPulsesParams({ accountId: "personal-123", - orgId: null, targetAccountId: "shared-org-member", }); @@ -108,7 +76,6 @@ describe("buildGetPulsesParams", () => { const result = await buildGetPulsesParams({ accountId: "personal-123", - orgId: null, targetAccountId: "other-account", }); @@ -118,12 +85,11 @@ describe("buildGetPulsesParams", () => { }); }); - it("returns error when org key lacks access to targetAccountId", async () => { + it("returns error when access to targetAccountId is denied", async () => { vi.mocked(canAccessAccount).mockResolvedValue(false); const result = await buildGetPulsesParams({ accountId: "org-123", - orgId: "org-123", targetAccountId: "not-in-org", }); diff --git a/lib/pulse/__tests__/validateGetPulsesRequest.test.ts b/lib/pulse/__tests__/validateGetPulsesRequest.test.ts index 205090cfe..788fa1f69 100644 --- a/lib/pulse/__tests__/validateGetPulsesRequest.test.ts +++ b/lib/pulse/__tests__/validateGetPulsesRequest.test.ts @@ -60,7 +60,7 @@ describe("validateGetPulsesRequest", () => { }); }); - it("should return orgId for org key", async () => { + it("should return accountIds for org key", async () => { const mockOrgId = "org-123"; vi.mocked(validateAuthContext).mockResolvedValue({ accountId: mockOrgId, @@ -75,12 +75,12 @@ describe("validateGetPulsesRequest", () => { expect(result).not.toBeInstanceOf(NextResponse); expect(result).toEqual({ - orgId: mockOrgId, + accountIds: [mockOrgId], active: undefined, }); }); - it("should return undefined accountIds for Recoup admin key", async () => { + it("should return accountIds for Recoup admin key", async () => { const recoupOrgId = "recoup-org-id"; vi.mocked(validateAuthContext).mockResolvedValue({ accountId: recoupOrgId, @@ -95,7 +95,7 @@ describe("validateGetPulsesRequest", () => { expect(result).not.toBeInstanceOf(NextResponse); expect(result).toEqual({ - accountIds: undefined, + accountIds: [recoupOrgId], active: undefined, }); }); diff --git a/lib/pulse/buildGetPulsesParams.ts b/lib/pulse/buildGetPulsesParams.ts index e74367747..c9fa49850 100644 --- a/lib/pulse/buildGetPulsesParams.ts +++ b/lib/pulse/buildGetPulsesParams.ts @@ -1,12 +1,9 @@ import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; import type { SelectPulseAccountsParams } from "@/lib/supabase/pulse_accounts/selectPulseAccounts"; -import { RECOUP_ORG_ID } from "@/lib/const"; export interface BuildGetPulsesParamsInput { /** The authenticated account ID */ accountId: string; - /** The organization ID from the API key (null for personal keys) */ - orgId: string | null; /** Optional target account ID to filter by */ targetAccountId?: string; /** Optional active status filter */ @@ -20,10 +17,7 @@ export type BuildGetPulsesParamsResult = /** * Builds the parameters for selectPulseAccounts based on auth context. * - * For personal keys: Returns accountIds with the key owner's account - * For org keys: Returns orgId for filtering by org membership - * For Recoup admin key: Returns empty params to indicate ALL records - * + * Returns accountIds with the key owner's account. * If targetAccountId is provided, validates access and returns that account. * * @param input - The auth context and optional filters @@ -32,7 +26,7 @@ export type BuildGetPulsesParamsResult = export async function buildGetPulsesParams( input: BuildGetPulsesParamsInput, ): Promise { - const { accountId, orgId, targetAccountId, active } = input; + const { accountId, targetAccountId, active } = input; // Handle account_id filter if provided if (targetAccountId) { @@ -49,17 +43,6 @@ export async function buildGetPulsesParams( return { params: { accountIds: [targetAccountId], active }, error: null }; } - // No account_id filter - determine what to return based on key type - if (orgId === RECOUP_ORG_ID) { - // Recoup admin: return undefined to indicate ALL records - return { params: { active }, error: null }; - } - - if (orgId) { - // Org key: return orgId for filtering by org membership in database - return { params: { orgId, active }, error: null }; - } - - // Personal key: Only return the key owner's account + // Return the key owner's account return { params: { accountIds: [accountId], active }, error: null }; } diff --git a/lib/pulse/validateGetPulsesRequest.ts b/lib/pulse/validateGetPulsesRequest.ts index 090f6fb73..40e752971 100644 --- a/lib/pulse/validateGetPulsesRequest.ts +++ b/lib/pulse/validateGetPulsesRequest.ts @@ -59,12 +59,11 @@ export async function validateGetPulsesRequest( return authResult; } - const { accountId, orgId } = authResult; + const { accountId } = authResult; // Use shared function to build params const { params, error } = await buildGetPulsesParams({ accountId, - orgId, targetAccountId, active, }); diff --git a/lib/sandbox/__tests__/buildGetSandboxesParams.test.ts b/lib/sandbox/__tests__/buildGetSandboxesParams.test.ts index c8969464f..f42f488dc 100644 --- a/lib/sandbox/__tests__/buildGetSandboxesParams.test.ts +++ b/lib/sandbox/__tests__/buildGetSandboxesParams.test.ts @@ -2,231 +2,115 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { buildGetSandboxesParams } from "../buildGetSandboxesParams"; import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; vi.mock("@/lib/organizations/canAccessAccount", () => ({ canAccessAccount: vi.fn(), })); -vi.mock("@/lib/supabase/account_organization_ids/getAccountOrganizations", () => ({ - getAccountOrganizations: vi.fn(), -})); - -vi.mock("@/lib/const", () => ({ - RECOUP_ORG_ID: "recoup-org-id", -})); - describe("buildGetSandboxesParams", () => { beforeEach(() => { vi.clearAllMocks(); }); - describe("personal API key (org_id = null)", () => { - it("returns accountIds with key owner when no target_account_id", async () => { - const result = await buildGetSandboxesParams({ - account_id: "account-123", - org_id: null, - }); - - expect(result).toEqual({ - params: { accountIds: ["account-123"], sandboxId: undefined }, - error: null, - }); - }); - - it("returns error when personal key tries to filter by account_id", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(false); - - const result = await buildGetSandboxesParams({ - account_id: "account-123", - org_id: null, - target_account_id: "other-account", - }); - - expect(result).toEqual({ - params: null, - error: "Access denied to specified account_id", - }); - }); - - it("allows personal key to access target_account_id via shared org", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(true); - - const result = await buildGetSandboxesParams({ - account_id: "personal-123", - org_id: null, - target_account_id: "shared-org-member", - }); - - expect(canAccessAccount).toHaveBeenCalledWith({ - targetAccountId: "shared-org-member", - currentAccountId: "personal-123", - }); - expect(result).toEqual({ - params: { accountIds: ["shared-org-member"], sandboxId: undefined }, - error: null, - }); - }); - - it("includes sandbox_id filter for personal key", async () => { - const result = await buildGetSandboxesParams({ - account_id: "account-123", - org_id: null, - sandbox_id: "sbx_abc123", - }); - - expect(result).toEqual({ - params: { accountIds: ["account-123"], sandboxId: "sbx_abc123" }, - error: null, - }); + it("returns accountIds with key owner when no target_account_id", async () => { + const result = await buildGetSandboxesParams({ + account_id: "account-123", + }); + + expect(result).toEqual({ + params: { accountIds: ["account-123"], sandboxId: undefined }, + error: null, }); }); - describe("organization API key", () => { - it("fetches org member accountIds when no target_account_id", async () => { - vi.mocked(getAccountOrganizations).mockResolvedValue([ - { account_id: "member-1", organization_id: "org-123", organization: null }, - { account_id: "member-2", organization_id: "org-123", organization: null }, - { account_id: "member-3", organization_id: "org-123", organization: null }, - ]); - - const result = await buildGetSandboxesParams({ - account_id: "org-123", - org_id: "org-123", - }); - - expect(getAccountOrganizations).toHaveBeenCalledWith({ organizationId: "org-123" }); - expect(result).toEqual({ - params: { accountIds: ["member-1", "member-2", "member-3"], sandboxId: undefined }, - error: null, - }); - }); - - it("allows filtering by account_id if member of org", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(true); - - const result = await buildGetSandboxesParams({ - account_id: "account-123", - org_id: "org-123", - target_account_id: "member-account", - }); - - expect(result).toEqual({ - params: { accountIds: ["member-account"], sandboxId: undefined }, - error: null, - }); - expect(canAccessAccount).toHaveBeenCalledWith({ - targetAccountId: "member-account", - currentAccountId: "account-123", - }); - }); - - it("returns error when account_id is not member of org", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(false); - - const result = await buildGetSandboxesParams({ - account_id: "account-123", - org_id: "org-123", - target_account_id: "non-member-account", - }); - - expect(result).toEqual({ - params: null, - error: "Access denied to specified account_id", - }); - }); - - it("includes sandbox_id filter for org key", async () => { - vi.mocked(getAccountOrganizations).mockResolvedValue([ - { account_id: "member-1", organization_id: "org-123", organization: null }, - ]); - - const result = await buildGetSandboxesParams({ - account_id: "account-123", - org_id: "org-123", - sandbox_id: "sbx_abc123", - }); - - expect(result).toEqual({ - params: { accountIds: ["member-1"], sandboxId: "sbx_abc123" }, - error: null, - }); - }); - - it("returns empty accountIds when org has no members", async () => { - vi.mocked(getAccountOrganizations).mockResolvedValue([]); - - const result = await buildGetSandboxesParams({ - account_id: "org-123", - org_id: "org-123", - }); - - expect(result).toEqual({ - params: { accountIds: [], sandboxId: undefined }, - error: null, - }); - }); - - it("includes both target_account_id and sandbox_id when access granted", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(true); - - const result = await buildGetSandboxesParams({ - account_id: "account-123", - org_id: "org-123", - target_account_id: "member-account", - sandbox_id: "sbx_abc123", - }); - - expect(result).toEqual({ - params: { accountIds: ["member-account"], sandboxId: "sbx_abc123" }, - error: null, - }); + it("returns error when personal key tries to filter by account_id", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(false); + + const result = await buildGetSandboxesParams({ + account_id: "account-123", + target_account_id: "other-account", + }); + + expect(result).toEqual({ + params: null, + error: "Access denied to specified account_id", }); }); - describe("Recoup admin key", () => { - const recoupOrgId = "recoup-org-id"; - - it("returns empty params (no filter) to get all records", async () => { - const result = await buildGetSandboxesParams({ - account_id: "admin-account", - org_id: recoupOrgId, - }); - - expect(result).toEqual({ - params: { sandboxId: undefined }, - error: null, - }); - // Should NOT call getAccountOrganizations for admin - expect(getAccountOrganizations).not.toHaveBeenCalled(); - }); - - it("allows filtering by any account_id", async () => { - vi.mocked(canAccessAccount).mockResolvedValue(true); - - const result = await buildGetSandboxesParams({ - account_id: "admin-account", - org_id: recoupOrgId, - target_account_id: "any-account", - }); - - expect(result).toEqual({ - params: { accountIds: ["any-account"], sandboxId: undefined }, - error: null, - }); - }); - - it("includes sandbox_id filter for admin key", async () => { - const result = await buildGetSandboxesParams({ - account_id: "admin-account", - org_id: recoupOrgId, - sandbox_id: "sbx_abc123", - }); - - expect(result).toEqual({ - params: { sandboxId: "sbx_abc123" }, - error: null, - }); + it("allows access to target_account_id via shared org", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(true); + + const result = await buildGetSandboxesParams({ + account_id: "personal-123", + target_account_id: "shared-org-member", + }); + + expect(canAccessAccount).toHaveBeenCalledWith({ + targetAccountId: "shared-org-member", + currentAccountId: "personal-123", + }); + expect(result).toEqual({ + params: { accountIds: ["shared-org-member"], sandboxId: undefined }, + error: null, + }); + }); + + it("includes sandbox_id filter", async () => { + const result = await buildGetSandboxesParams({ + account_id: "account-123", + sandbox_id: "sbx_abc123", + }); + + expect(result).toEqual({ + params: { accountIds: ["account-123"], sandboxId: "sbx_abc123" }, + error: null, + }); + }); + + it("allows filtering by account_id when access is granted", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(true); + + const result = await buildGetSandboxesParams({ + account_id: "account-123", + target_account_id: "member-account", + }); + + expect(result).toEqual({ + params: { accountIds: ["member-account"], sandboxId: undefined }, + error: null, + }); + expect(canAccessAccount).toHaveBeenCalledWith({ + targetAccountId: "member-account", + currentAccountId: "account-123", + }); + }); + + it("returns error when account_id filter is denied", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(false); + + const result = await buildGetSandboxesParams({ + account_id: "account-123", + target_account_id: "non-member-account", + }); + + expect(result).toEqual({ + params: null, + error: "Access denied to specified account_id", + }); + }); + + it("includes both target_account_id and sandbox_id when access granted", async () => { + vi.mocked(canAccessAccount).mockResolvedValue(true); + + const result = await buildGetSandboxesParams({ + account_id: "account-123", + target_account_id: "member-account", + sandbox_id: "sbx_abc123", + }); + + expect(result).toEqual({ + params: { accountIds: ["member-account"], sandboxId: "sbx_abc123" }, + error: null, }); }); }); diff --git a/lib/sandbox/__tests__/validateGetSandboxesRequest.test.ts b/lib/sandbox/__tests__/validateGetSandboxesRequest.test.ts index 1a79a3253..413325b44 100644 --- a/lib/sandbox/__tests__/validateGetSandboxesRequest.test.ts +++ b/lib/sandbox/__tests__/validateGetSandboxesRequest.test.ts @@ -111,7 +111,6 @@ describe("validateGetSandboxesRequest", () => { expect(buildGetSandboxesParams).toHaveBeenCalledWith({ account_id: "acc_123", - org_id: null, target_account_id: "550e8400-e29b-41d4-a716-446655440000", sandbox_id: undefined, }); @@ -139,7 +138,6 @@ describe("validateGetSandboxesRequest", () => { expect(buildGetSandboxesParams).toHaveBeenCalledWith({ account_id: "org-123", - org_id: "org-123", target_account_id: "550e8400-e29b-41d4-a716-446655440000", sandbox_id: undefined, }); @@ -170,7 +168,6 @@ describe("validateGetSandboxesRequest", () => { expect(buildGetSandboxesParams).toHaveBeenCalledWith({ account_id: "org-123", - org_id: "org-123", target_account_id: "550e8400-e29b-41d4-a716-446655440000", sandbox_id: "sbx_abc123", }); @@ -198,7 +195,6 @@ describe("validateGetSandboxesRequest", () => { expect(buildGetSandboxesParams).toHaveBeenCalledWith({ account_id: "acc_123", - org_id: null, target_account_id: undefined, sandbox_id: undefined, }); diff --git a/lib/sandbox/buildGetSandboxesParams.ts b/lib/sandbox/buildGetSandboxesParams.ts index 40f10e292..d756a5fd0 100644 --- a/lib/sandbox/buildGetSandboxesParams.ts +++ b/lib/sandbox/buildGetSandboxesParams.ts @@ -1,13 +1,9 @@ import type { SelectAccountSandboxesParams } from "@/lib/supabase/account_sandboxes/selectAccountSandboxes"; import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; -import { RECOUP_ORG_ID } from "@/lib/const"; export interface BuildGetSandboxesParamsInput { /** The authenticated account ID */ account_id: string; - /** The organization ID from the API key (null for personal keys) */ - org_id: string | null; /** Optional target account ID to filter by */ target_account_id?: string; /** Optional sandbox ID to filter by */ @@ -21,10 +17,7 @@ export type BuildGetSandboxesParamsResult = /** * Builds the parameters for selectAccountSandboxes based on auth context. * - * For personal keys: Returns accountIds with the key owner's account - * For org keys: Fetches all org member accountIds and returns them - * For Recoup admin key: Returns empty params to indicate ALL records - * + * Returns accountIds with the key owner's account. * If target_account_id is provided, validates access and returns that account. * * @param input - The auth context and optional filters @@ -33,7 +26,7 @@ export type BuildGetSandboxesParamsResult = export async function buildGetSandboxesParams( input: BuildGetSandboxesParamsInput, ): Promise { - const { account_id, org_id, target_account_id, sandbox_id } = input; + const { account_id, target_account_id, sandbox_id } = input; // Handle account_id filter if provided if (target_account_id) { @@ -50,19 +43,6 @@ export async function buildGetSandboxesParams( return { params: { accountIds: [target_account_id], sandboxId: sandbox_id }, error: null }; } - // No account_id filter - determine what to return based on key type - if (org_id === RECOUP_ORG_ID) { - // Recoup admin: return undefined accountIds to indicate ALL records - return { params: { sandboxId: sandbox_id }, error: null }; - } - - if (org_id) { - // Org key: fetch all member account IDs for this organization - const orgMembers = await getAccountOrganizations({ organizationId: org_id }); - const memberAccountIds = orgMembers.map(member => member.account_id); - return { params: { accountIds: memberAccountIds, sandboxId: sandbox_id }, error: null }; - } - - // Personal key: Only return the key owner's account + // Return the key owner's account return { params: { accountIds: [account_id], sandboxId: sandbox_id }, error: null }; } diff --git a/lib/sandbox/validateGetSandboxesFileRequest.ts b/lib/sandbox/validateGetSandboxesFileRequest.ts index fedadc495..bdda238ba 100644 --- a/lib/sandbox/validateGetSandboxesFileRequest.ts +++ b/lib/sandbox/validateGetSandboxesFileRequest.ts @@ -56,7 +56,6 @@ export async function validateGetSandboxesFileRequest( const { params, error } = await buildGetSandboxesParams({ account_id: accountId, - org_id: orgId, }); if (error) { diff --git a/lib/sandbox/validateGetSandboxesRequest.ts b/lib/sandbox/validateGetSandboxesRequest.ts index 7252e33d3..0f53b7209 100644 --- a/lib/sandbox/validateGetSandboxesRequest.ts +++ b/lib/sandbox/validateGetSandboxesRequest.ts @@ -52,12 +52,11 @@ export async function validateGetSandboxesRequest( return authResult; } - const { accountId, orgId } = authResult; + const { accountId } = authResult; // Use shared function to build params const { params, error } = await buildGetSandboxesParams({ account_id: accountId, - org_id: orgId, target_account_id: targetAccountId, sandbox_id: sandboxId, }); diff --git a/lib/slack/chat/handlers/handleSlackChatMessage.ts b/lib/slack/chat/handlers/handleSlackChatMessage.ts index 5838d84a8..c990578cf 100644 --- a/lib/slack/chat/handlers/handleSlackChatMessage.ts +++ b/lib/slack/chat/handlers/handleSlackChatMessage.ts @@ -46,7 +46,7 @@ export async function handleSlackChatMessage( return; } - const { accountId, orgId } = keyDetails; + const { accountId } = keyDetails; await thread.setState({ status: "generating", @@ -99,7 +99,7 @@ export async function handleSlackChatMessage( const body: ChatRequestBody = { messages: allUiMessages, accountId, - orgId, + orgId: null, roomId, authToken, };