Skip to content
Merged
2 changes: 1 addition & 1 deletion lib/accounts/__tests__/validateOverrideAccountId.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe("validateOverrideAccountId", () => {
expect(result).toEqual({ accountId: targetAccountId });
expect(getApiKeyDetails).toHaveBeenCalledWith("valid_api_key");
expect(canAccessAccount).toHaveBeenCalledWith({
orgId,
currentAccountId: orgId,
targetAccountId,
});
});
Expand Down
2 changes: 1 addition & 1 deletion lib/accounts/validateOverrideAccountId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function validateOverrideAccountId({
}

const hasAccess = await canAccessAccount({
orgId: keyDetails.orgId,
currentAccountId: keyDetails.accountId,
targetAccountId,
});

Expand Down
10 changes: 7 additions & 3 deletions lib/admins/privy/__tests__/getPrivyLoginsHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { NextRequest, NextResponse } from "next/server";
import { getPrivyLoginsHandler } from "../getPrivyLoginsHandler";

import { validateGetPrivyLoginsQuery } from "../validateGetPrivyLoginsQuery";
import { fetchPrivyLogins } from "../fetchPrivyLogins";

vi.mock("@/lib/networking/getCorsHeaders", () => ({
getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })),
}));
Expand All @@ -14,9 +17,6 @@ vi.mock("../fetchPrivyLogins", () => ({
fetchPrivyLogins: vi.fn(),
}));

import { validateGetPrivyLoginsQuery } from "../validateGetPrivyLoginsQuery";
import { fetchPrivyLogins } from "../fetchPrivyLogins";

const now = Date.now();
const ONE_HOUR_AGO = Math.floor((now - 60 * 60 * 1000) / 1000);
const TWO_HOURS_AGO = Math.floor((now - 2 * 60 * 60 * 1000) / 1000);
Expand All @@ -42,6 +42,10 @@ const mockLogins = [
},
];

/**
*
* @param period
*/
function makeRequest(period = "daily") {
return new NextRequest(`https://example.com/api/admins/privy?period=${period}`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { NextRequest, NextResponse } from "next/server";
import { validateGetPrivyLoginsQuery } from "../validateGetPrivyLoginsQuery";

import { validateAdminAuth } from "@/lib/admins/validateAdminAuth";

vi.mock("@/lib/networking/getCorsHeaders", () => ({
getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })),
}));
Expand All @@ -10,10 +12,12 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({
validateAdminAuth: vi.fn(),
}));

import { validateAdminAuth } from "@/lib/admins/validateAdminAuth";

const mockAuth = { accountId: "test-account", orgId: null, authToken: "token" };

/**
*
* @param period
*/
function makeRequest(period?: string) {
const url = period
? `https://example.com/api/admins/privy?period=${period}`
Expand Down
6 changes: 3 additions & 3 deletions lib/ai/__tests__/getAvailableModels.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

import { gateway } from "@ai-sdk/gateway";
import { getAvailableModels } from "../getAvailableModels";

vi.mock("@ai-sdk/gateway", () => ({
gateway: {
getAvailableModels: vi.fn(),
},
}));

import { gateway } from "@ai-sdk/gateway";
import { getAvailableModels } from "../getAvailableModels";

const mockGatewayGetAvailableModels = vi.mocked(gateway.getAvailableModels);

describe("getAvailableModels", () => {
Expand Down
6 changes: 3 additions & 3 deletions lib/ai/__tests__/getModel.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

import { getAvailableModels } from "@/lib/ai/getAvailableModels";
import { getModel } from "../getModel";

vi.mock("@/lib/ai/getAvailableModels", () => ({
getAvailableModels: vi.fn(),
}));

import { getAvailableModels } from "@/lib/ai/getAvailableModels";
import { getModel } from "../getModel";

const mockGetAvailableModels = vi.mocked(getAvailableModels);

describe("getModel", () => {
Expand Down
31 changes: 25 additions & 6 deletions lib/artists/__tests__/buildGetArtistsParams.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { buildGetArtistsParams } from "../buildGetArtistsParams";

import { canAccessAccount } from "@/lib/organizations/canAccessAccount";

vi.mock("@/lib/organizations/canAccessAccount", () => ({
canAccessAccount: vi.fn(),
}));

import { canAccessAccount } from "@/lib/organizations/canAccessAccount";

describe("buildGetArtistsParams", () => {
beforeEach(() => {
vi.clearAllMocks();
Expand Down Expand Up @@ -68,8 +68,8 @@ describe("buildGetArtistsParams", () => {
});

expect(canAccessAccount).toHaveBeenCalledWith({
orgId: "org-123",
targetAccountId: "target-456",
currentAccountId: "org-owner-123",
});
expect(result).toEqual({
params: { accountId: "target-456", orgId: null },
Expand All @@ -93,7 +93,26 @@ describe("buildGetArtistsParams", () => {
});
});

it("returns error when personal key tries to filter by targetAccountId", async () => {
it("allows personal key to access targetAccountId via shared org", async () => {
vi.mocked(canAccessAccount).mockResolvedValue(true);

const result = await buildGetArtistsParams({
accountId: "personal-123",
orgId: null,
targetAccountId: "shared-org-member",
});

expect(canAccessAccount).toHaveBeenCalledWith({
targetAccountId: "shared-org-member",
currentAccountId: "personal-123",
});
expect(result).toEqual({
params: { accountId: "shared-org-member", orgId: null },
error: null,
});
});

it("returns error when personal key has no shared org with targetAccountId", async () => {
vi.mocked(canAccessAccount).mockResolvedValue(false);

const result = await buildGetArtistsParams({
Expand All @@ -104,7 +123,7 @@ describe("buildGetArtistsParams", () => {

expect(result).toEqual({
params: null,
error: "Personal API keys cannot filter by account_id",
error: "Access denied to specified account_id",
});
});

Expand All @@ -119,7 +138,7 @@ describe("buildGetArtistsParams", () => {

expect(result).toEqual({
params: null,
error: "account_id is not a member of this organization",
error: "Access denied to specified account_id",
});
});
});
8 changes: 4 additions & 4 deletions lib/artists/__tests__/checkAccountArtistAccess.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { checkAccountArtistAccess } from "../checkAccountArtistAccess";

import { selectAccountArtistId } from "@/lib/supabase/account_artist_ids/selectAccountArtistId";
import { selectArtistOrganizationIds } from "@/lib/supabase/artist_organization_ids/selectArtistOrganizationIds";
import { selectAccountOrganizationIds } from "@/lib/supabase/account_organization_ids/selectAccountOrganizationIds";

vi.mock("@/lib/supabase/account_artist_ids/selectAccountArtistId", () => ({
selectAccountArtistId: vi.fn(),
}));
Expand All @@ -13,10 +17,6 @@ vi.mock("@/lib/supabase/account_organization_ids/selectAccountOrganizationIds",
selectAccountOrganizationIds: vi.fn(),
}));

import { selectAccountArtistId } from "@/lib/supabase/account_artist_ids/selectAccountArtistId";
import { selectArtistOrganizationIds } from "@/lib/supabase/artist_organization_ids/selectArtistOrganizationIds";
import { selectAccountOrganizationIds } from "@/lib/supabase/account_organization_ids/selectAccountOrganizationIds";

describe("checkAccountArtistAccess", () => {
beforeEach(() => {
vi.clearAllMocks();
Expand Down
4 changes: 2 additions & 2 deletions lib/artists/__tests__/createArtistInDb.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest";

import { createArtistInDb } from "../createArtistInDb";

const mockInsertAccount = vi.fn();
const mockInsertAccountInfo = vi.fn();
const mockSelectAccountWithSocials = vi.fn();
Expand All @@ -26,8 +28,6 @@ vi.mock("@/lib/supabase/artist_organization_ids/addArtistToOrganization", () =>
addArtistToOrganization: (...args: unknown[]) => mockAddArtistToOrganization(...args),
}));

import { createArtistInDb } from "../createArtistInDb";

describe("createArtistInDb", () => {
const mockAccount = {
id: "artist-123",
Expand Down
10 changes: 5 additions & 5 deletions lib/artists/__tests__/validateGetArtistsRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { NextRequest, NextResponse } from "next/server";
import { validateGetArtistsRequest } from "../validateGetArtistsRequest";

import { validateAuthContext } from "@/lib/auth/validateAuthContext";
import { canAccessAccount } from "@/lib/organizations/canAccessAccount";

vi.mock("@/lib/auth/validateAuthContext", () => ({
validateAuthContext: vi.fn(),
}));
Expand All @@ -14,9 +17,6 @@ vi.mock("@/lib/networking/getCorsHeaders", () => ({
getCorsHeaders: vi.fn(() => new Headers()),
}));

import { validateAuthContext } from "@/lib/auth/validateAuthContext";
import { canAccessAccount } from "@/lib/organizations/canAccessAccount";

describe("validateGetArtistsRequest", () => {
beforeEach(() => {
vi.clearAllMocks();
Expand Down Expand Up @@ -128,8 +128,8 @@ describe("validateGetArtistsRequest", () => {
const result = await validateGetArtistsRequest(request);

expect(canAccessAccount).toHaveBeenCalledWith({
orgId: mockOrgId,
targetAccountId,
currentAccountId: "org-owner",
});
expect(result).not.toBeInstanceOf(NextResponse);
expect(result).toEqual({
Expand Down Expand Up @@ -168,8 +168,8 @@ describe("validateGetArtistsRequest", () => {
const result = await validateGetArtistsRequest(request);

expect(canAccessAccount).toHaveBeenCalledWith({
orgId: mockOrgId,
targetAccountId: notInOrgId,
currentAccountId: "org-owner",
});
expect(result).toBeInstanceOf(NextResponse);
expect((result as NextResponse).status).toBe(403);
Expand Down
9 changes: 5 additions & 4 deletions lib/artists/buildGetArtistsParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ export async function buildGetArtistsParams(

// Handle account_id filter if provided
if (targetAccountId) {
const hasAccess = await canAccessAccount({ orgId, targetAccountId });
const hasAccess = await canAccessAccount({
targetAccountId,
currentAccountId: accountId,
});
if (!hasAccess) {
return {
params: null,
error: orgId
? "account_id is not a member of this organization"
: "Personal API keys cannot filter by account_id",
error: "Access denied to specified account_id",
};
}
effectiveAccountId = targetAccountId;
Expand Down
35 changes: 33 additions & 2 deletions lib/auth/__tests__/validateAuthContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const mockGetApiKeyDetails = vi.mocked(getApiKeyDetails);
const mockValidateOrganizationAccess = vi.mocked(validateOrganizationAccess);
const mockCanAccessAccount = vi.mocked(canAccessAccount);

/**
*
* @param headers
*/
function createMockRequest(headers: Record<string, string> = {}): Request {
return {
headers: {
Expand Down Expand Up @@ -182,14 +186,15 @@ describe("validateAuthContext", () => {
expect(mockCanAccessAccount).not.toHaveBeenCalled();
});

it("denies personal API key accessing different account_id", async () => {
it("denies personal API key accessing different account_id when no 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(false);

const result = await validateAuthContext(request as never, {
accountId: "different-account-456", // Different from API key's account
Expand All @@ -202,6 +207,32 @@ describe("validateAuthContext", () => {
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, {
accountId: "member-account-456",
});

expect(result).not.toBeInstanceOf(NextResponse);
expect(result).toEqual({
accountId: "member-account-456",
orgId: null,
authToken: "personal-key",
});
expect(mockCanAccessAccount).toHaveBeenCalledWith({
targetAccountId: "member-account-456",
currentAccountId: "account-123",
});
});

it("allows org API key to access member account", async () => {
const request = createMockRequest({ "x-api-key": "org-key" });
mockGetApiKeyAccountId.mockResolvedValue("org-account-123");
Expand All @@ -223,8 +254,8 @@ describe("validateAuthContext", () => {
authToken: "org-key",
});
expect(mockCanAccessAccount).toHaveBeenCalledWith({
orgId: "org-456",
targetAccountId: "member-account-789",
currentAccountId: "org-account-123",
});
});

Expand Down
Loading
Loading