Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/api/accounts/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export async function OPTIONS() {
* - id (required): The unique identifier of the account (UUID)
*
* @param request - The request object
* @param params - Route params containing the account ID
* @param root1 - Destructured route context
* @param root1.params - Promise resolving to route params containing the account ID
* @returns A NextResponse with account data
*/
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
Expand Down
9 changes: 7 additions & 2 deletions app/api/admins/coding/pr/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ import { getPrStatusHandler } from "@/lib/admins/pr/getPrStatusHandler";
* Uses the GitHub REST API to check each PR's state.
* Requires admin authentication.
*
* @param request
* @param request - The incoming request with pull_requests query params
* @returns A NextResponse with the PR status for each provided URL
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
return getPrStatusHandler(request);
}

/** CORS preflight handler. */
/**
* CORS preflight handler.
*
* @returns A NextResponse with CORS headers allowing cross-origin access
*/
export async function OPTIONS(): Promise<NextResponse> {
return new NextResponse(null, { status: 204, headers: getCorsHeaders() });
}
9 changes: 8 additions & 1 deletion app/api/admins/coding/slack/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ import { getSlackTagsHandler } from "@/lib/admins/slack/getSlackTagsHandler";
* Pulls directly from the Slack API as the source of truth.
* Supports period filtering: all (default), daily, weekly, monthly.
* Requires admin authentication.
*
* @param request - The incoming request with optional period query param
* @returns A NextResponse with Slack tagging analytics data
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
return getSlackTagsHandler(request);
}

/** CORS preflight handler. */
/**
* CORS preflight handler.
*
* @returns A NextResponse with CORS headers allowing cross-origin access
*/
export async function OPTIONS(): Promise<NextResponse> {
return new NextResponse(null, { status: 204, headers: getCorsHeaders() });
}
9 changes: 7 additions & 2 deletions app/api/admins/content/slack/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ import { getContentSlackTagsHandler } from "@/lib/admins/content/getContentSlack
* Supports period filtering: all (default), daily, weekly, monthly.
* Requires admin authentication.
*
* @param request
* @param request - The incoming request with optional period query param
* @returns A NextResponse with Slack tagging analytics data
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
return getContentSlackTagsHandler(request);
}

/** CORS preflight handler. */
/**
* CORS preflight handler.
*
* @returns A NextResponse with CORS headers allowing cross-origin access
*/
export async function OPTIONS(): Promise<NextResponse> {
return new NextResponse(null, { status: 204, headers: getCorsHeaders() });
}
8 changes: 8 additions & 0 deletions app/api/admins/privy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ import { getPrivyLoginsHandler } from "@/lib/admins/privy/getPrivyLoginsHandler"
* Returns Privy login statistics for the requested time period.
* Supports daily (last 24h), weekly (last 7 days), and monthly (last 30 days) periods.
* Requires admin authentication.
*
* @param request - The incoming request with optional period query param
* @returns A NextResponse with Privy login statistics
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
return getPrivyLoginsHandler(request);
}

/**
* CORS preflight handler.
*
* @returns A NextResponse with CORS headers allowing cross-origin access
*/
export async function OPTIONS(): Promise<NextResponse> {
return new NextResponse(null, { status: 204, headers: getCorsHeaders() });
}
1 change: 1 addition & 0 deletions app/api/coding-agent/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { handleCodingAgentCallback } from "@/lib/coding-agent/handleCodingAgentC
* Receives task results and posts them back to the Slack thread.
*
* @param request - The incoming callback request
* @returns A NextResponse indicating success or failure of the callback processing
*/
export async function POST(request: NextRequest) {
await codingAgentBot.initialize();
Expand Down
1 change: 1 addition & 0 deletions app/api/coding-agent/github/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { handleGitHubWebhook } from "@/lib/coding-agent/handleGitHubWebhook";
* Receives issue_comment events and triggers update-pr when the bot is mentioned.
*
* @param request - The incoming GitHub webhook request
* @returns A NextResponse indicating whether the webhook was processed
*/
export async function POST(request: NextRequest) {
return handleGitHubWebhook(request);
Expand Down
9 changes: 6 additions & 3 deletions app/api/connectors/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { disconnectConnectorHandler } from "@/lib/composio/connectors/disconnect

/**
* OPTIONS handler for CORS preflight requests.
*
* @returns A NextResponse with CORS headers allowing cross-origin access
*/
export async function OPTIONS() {
return new NextResponse(null, {
Expand All @@ -25,7 +27,7 @@ export async function OPTIONS() {
*
* Authentication: x-api-key OR Authorization Bearer token required.
*
* @param request
* @param request - The incoming request with optional account_id query param
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate connector handlers:"
fd -i 'getConnectorsHandler.ts|authorizeConnectorHandler.ts|disconnectConnectorHandler.ts' lib

echo
echo "Check for account_id parsing/usages and auth context validation in those handlers:"
rg -n -C3 --type=ts '\baccount_id\b|searchParams\.get\("account_id"\)|validateAuthContext\(' lib/composio/connectors

Repository: recoupable/api

Length of output: 47897


Remove account_id parameter support across all connector endpoints (GET, POST, DELETE).

The current API design accepts optional account_id in query params (GET) and request bodies (POST, DELETE), which violates the authentication model. Per coding guidelines, account identity must derive solely from authentication context—never from client input. This applies to:

  • GET /api/connectors: account_id query param (documented in line 30)
  • POST /api/connectors/authorize: account_id body field (documented in line 49)
  • DELETE /api/connectors: account_id body field (documented in line 67–68)

The handlers currently accept account_id as optional with access checks, but the entire pattern conflicts with the guideline. Refactor to derive the account ID from validateAuthContext() alone. If multi-tenant access is needed, implement it through a separate, explicit authorization mechanism—not through optional client parameters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/connectors/route.ts` at line 30, Remove all client-supplied
account_id handling in the connectors endpoints: stop reading account_id from
query or request bodies and derive the account ID exclusively from
validateAuthContext() for the GET /api/connectors handler, the POST
/api/connectors/authorize handler, and the DELETE /api/connectors handler.
Update the handlers (functions/methods that parse request.query.account_id,
request.body.account_id or similar) to use the accountId returned by
validateAuthContext(), remove any conditional access checks based on an optional
account_id input, and adjust related TypeScript types and JSDoc comments (the
param lines in the file) to reflect that account_id is not accepted from the
client. Ensure all authorization/enforcement still runs but now uses the
accountId from validateAuthContext() only.

* @returns List of connectors with connection status
*/
export async function GET(request: NextRequest) {
Expand All @@ -44,7 +46,7 @@ export async function GET(request: NextRequest) {
* - callback_url: Optional custom callback URL after OAuth
* - account_id: Optional account ID for account-specific connections
*
* @param request
* @param request - The incoming request with connector slug and optional callback_url in the body
* @returns The redirect URL for OAuth authorization
*/
export async function POST(request: NextRequest) {
Expand All @@ -62,7 +64,8 @@ export async function POST(request: NextRequest) {
*
* Authentication: x-api-key OR Authorization Bearer token required.
*
* @param request
* @param request - The incoming request with connected_account_id in the body
* @returns A NextResponse confirming the connector was disconnected
*/
export async function DELETE(request: NextRequest) {
return disconnectConnectorHandler(request);
Expand Down
1 change: 1 addition & 0 deletions app/api/songs/analyze/presets/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export async function OPTIONS() {
* - status: "success"
* - presets: Array of { name, label, description, requiresAudio, responseFormat }
*
* @param request - The incoming request with authentication headers
* @returns A NextResponse with the list of available presets
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
Expand Down
9 changes: 9 additions & 0 deletions app/api/transcribe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import { NextRequest, NextResponse } from "next/server";
import { processAudioTranscription } from "@/lib/transcribe/processAudioTranscription";
import { formatTranscriptionError } from "@/lib/transcribe/types";

/**
* POST /api/transcribe
*
* Fetches an audio file from the provided URL, transcribes it using OpenAI Whisper,
* and saves both the audio and transcript as file records linked to the artist account.
*
* @param req - The incoming request containing audio_url, account_id, artist_account_id, title, and include_timestamps
* @returns A NextResponse with the created audio file record, transcript file record, and transcription text
*/
export async function POST(req: NextRequest) {
try {
const body = await req.json();
Expand Down
8 changes: 3 additions & 5 deletions lib/accounts/validateOverrideAccountId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ export type ValidateOverrideAccountIdResult = {
* Used when an org API key wants to create resources on behalf of another account.
* Checks that the API key belongs to an org with access to the target account.
*
* @param params.apiKey - The x-api-key header value
* @param params.targetAccountId - The accountId to override to
* @param root0
* @param root0.apiKey
* @param root0.targetAccountId
* @param root0 - The validation parameters
* @param root0.apiKey - The x-api-key header value
* @param root0.targetAccountId - The accountId to override to
* @returns The validated accountId or a NextResponse error
*/
export async function validateOverrideAccountId({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ const mockTags = [
];

/**
* Creates a mock NextRequest for the content Slack tags endpoint with the given period.
*
* @param period
* @param period - The time period query parameter (e.g., "all", "daily", "weekly", "monthly").
* @returns A NextRequest instance targeting the content Slack tags endpoint.
*/
function makeRequest(period = "all") {
return new NextRequest(`https://example.com/api/admins/content/slack?period=${period}`);
Expand Down
7 changes: 4 additions & 3 deletions lib/admins/content/extractVideoLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ const VIDEO_URL_PATTERN = /https?:\/\/[^\s>|]+/g;
* Extracts video/media URLs from a Slack bot reply message's text, attachments, and blocks.
* Filters for common video hosting patterns.
*
* @param text
* @param attachments
* @param blocks
* @param text - The plain text body of the Slack message to scan for URLs.
* @param attachments - Optional Slack message attachments whose action button URLs are also scanned.
* @param blocks - Optional Slack Block Kit blocks whose element URLs are also scanned.
* @returns A deduplicated array of video/media URLs found across all message surfaces.
*/
export function extractVideoLinks(
text: string,
Expand Down
7 changes: 4 additions & 3 deletions lib/admins/content/fetchThreadVideoLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ interface ConversationsRepliesResponse {
* Fetches bot replies in a Slack thread and returns any video/media URLs found.
* Extracts URLs from message text, attachment action buttons, and Block Kit blocks.
*
* @param token
* @param channel
* @param threadTs
* @param token - Slack bot token used to authenticate the conversations.replies API call.
* @param channel - Slack channel ID containing the thread.
* @param threadTs - Timestamp of the parent message that identifies the thread.
* @returns A deduplicated array of video/media URLs posted by bots in the thread.
*/
export async function fetchThreadVideoLinks(
token: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({
validateAdminAuth: vi.fn(),
}));

/**
* Creates a minimal mock NextRequest from a URL string for use in unit tests.
*
* @param url - The full URL string to construct the mock request from.
* @returns A mock NextRequest with url and nextUrl populated.
*/
function createMockRequest(url: string): NextRequest {
return {
url,
Expand Down
6 changes: 6 additions & 0 deletions lib/admins/pr/__tests__/getPrMergedStatusHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ vi.mock("@/lib/github/fetchGithubPrStatus", () => ({
const PR_URL_1 = "https://github.com/recoupable/api/pull/42";
const PR_URL_2 = "https://github.com/recoupable/chat/pull/100";

/**
* Creates a NextRequest targeting the PR status endpoint with the given PR URLs as query params.
*
* @param urls - Array of GitHub pull request URLs to include as pull_requests query parameters.
* @returns A NextRequest with all provided URLs appended as repeated pull_requests params.
*/
function makeRequest(urls: string[] = [PR_URL_1]) {
const params = new URLSearchParams();
urls.forEach(url => params.append("pull_requests", url));
Expand Down
3 changes: 3 additions & 0 deletions lib/admins/pr/getPrStatusHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { fetchGithubPrStatus } from "@/lib/github/fetchGithubPrStatus";
* Uses the GitHub REST API to check each PR's state.
*
* Requires admin authentication.
*
* @param request - The incoming Next.js request containing pull_requests query parameters.
* @returns A NextResponse with each PR URL mapped to its current status, or an error response.
*/
export async function getPrStatusHandler(request: NextRequest): Promise<NextResponse> {
try {
Expand Down
4 changes: 3 additions & 1 deletion lib/admins/privy/__tests__/getPrivyLoginsHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ const mockLogins = [
];

/**
* Creates a NextRequest targeting the Privy logins endpoint with the given period query param.
*
* @param period
* @param period - The time period to filter logins by (e.g., "daily", "weekly", "monthly", "all").
* @returns A NextRequest instance for the Privy logins admin endpoint.
*/
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 @@ -15,8 +15,10 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({
const mockAuth = { accountId: "test-account", orgId: null, authToken: "token" };

/**
* Creates a NextRequest targeting the Privy logins query endpoint, optionally with a period param.
*
* @param period
* @param period - Optional time period value to include as a query parameter.
* @returns A NextRequest for the Privy logins admin endpoint with or without a period filter.
*/
function makeRequest(period?: string) {
const url = period
Expand Down
4 changes: 4 additions & 0 deletions lib/admins/privy/countNewAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { getCutoffMs } from "./getCutoffMs";

/**
* Counts how many users in the list were created within the cutoff period.
*
* @param users - Array of Privy user objects to evaluate.
* @param period - The time window to check against (e.g., "daily", "weekly", "monthly", or "all").
* @returns The number of users whose created_at timestamp falls within the given period.
*/
export function countNewAccounts(users: User[], period: PrivyLoginsPeriod): number {
const cutoffMs = getCutoffMs(period);
Expand Down
8 changes: 8 additions & 0 deletions lib/admins/privy/fetchPrivyLogins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ export type FetchPrivyLoginsResult = {
totalPrivyUsers: number;
};

/**
* Fetches Privy users active or created within the given period via the Privy Management API.
* Paginates through all users and collects those whose created_at or latest_verified_at
* falls within the time window determined by the period.
*
* @param period - The time window to filter users by ("daily", "weekly", "monthly", or "all").
* @returns An object containing the matching users sorted by created_at descending and the total Privy user count.
*/
export async function fetchPrivyLogins(period: PrivyLoginsPeriod): Promise<FetchPrivyLoginsResult> {
const isAll = period === "all";
const cutoffMs = getCutoffMs(period);
Expand Down
3 changes: 3 additions & 0 deletions lib/admins/privy/getCutoffMs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { PERIOD_DAYS } from "./periodDays";
* Returns the cutoff timestamp in milliseconds for a given period.
* Uses midnight UTC calendar day boundaries to match Privy dashboard behavior.
* Returns 0 for "all" (no cutoff).
*
* @param period - The time window ("daily", "weekly", "monthly", or "all") to compute a cutoff for.
* @returns Milliseconds timestamp representing the start of the period, or 0 for "all".
*/
export function getCutoffMs(period: PrivyLoginsPeriod): number {
if (period === "all") return 0;
Expand Down
3 changes: 3 additions & 0 deletions lib/admins/privy/getLatestVerifiedAt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import type { User } from "@privy-io/node";
/**
* Returns the most recent latest_verified_at (in ms) across all linked_accounts for a Privy user.
* Returns null if no linked account has a latest_verified_at.
*
* @param user - The Privy user object whose linked accounts are inspected.
* @returns The most recent verified-at timestamp in milliseconds, or null if none exists.
*/
export function getLatestVerifiedAt(user: User): number | null {
const linkedAccounts = user.linked_accounts;
Expand Down
3 changes: 3 additions & 0 deletions lib/admins/privy/toMs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/**
* Normalizes a Privy timestamp to milliseconds.
* Privy docs say milliseconds but examples show seconds (10 digits).
*
* @param timestamp - A Privy timestamp that may be in either seconds or milliseconds.
* @returns The timestamp normalized to milliseconds.
*/
export function toMs(timestamp: number): number {
return timestamp > 1e12 ? timestamp : timestamp * 1000;
Expand Down
4 changes: 3 additions & 1 deletion lib/admins/slack/__tests__/createSlackTagsHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ const handler = createSlackTagsHandler({
});

/**
* Creates a NextRequest targeting the coding Slack tags endpoint with the given period query param.
*
* @param period
* @param period - The time period to filter tags by (e.g., "all", "daily", "weekly", "monthly").
* @returns A NextRequest instance for the coding Slack tags admin endpoint.
*/
function makeRequest(period = "all") {
return new NextRequest(`https://example.com/api/admins/coding/slack?period=${period}`);
Expand Down
4 changes: 3 additions & 1 deletion lib/admins/slack/__tests__/getSlackTagsHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ const mockTags = [
];

/**
* Creates a NextRequest targeting the Slack tags endpoint with the given period query param.
*
* @param period
* @param period - The time period to filter tags by (e.g., "all", "daily", "weekly", "monthly").
* @returns A NextRequest instance for the Slack tags admin endpoint.
*/
function makeRequest(period = "all") {
return new NextRequest(`https://example.com/api/admins/coding/slack?period=${period}`);
Expand Down
4 changes: 3 additions & 1 deletion lib/admins/slack/__tests__/validateGetSlackTagsQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({
const mockAuth = { accountId: "test-account", orgId: null, authToken: "token" };

/**
* Creates a NextRequest targeting the Slack tags query endpoint, optionally with a period param.
*
* @param period
* @param period - Optional time period value to include as a query parameter.
* @returns A NextRequest for the coding Slack tags admin endpoint with or without a period filter.
*/
function makeRequest(period?: string) {
const url = period
Expand Down
5 changes: 4 additions & 1 deletion lib/admins/slack/createSlackTagsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ interface SlackTagsHandlerConfig {
* Factory that creates a handler for admin slack tags endpoints.
* Parameterized by validation, fetching, and response field naming.
*
* @param config
* @param config - Configuration object specifying the validate function, fetchMentions function,
* and the response field names used to expose per-tag response counts and totals.
* @returns An async request handler function that validates the request, fetches mentions,
* and returns aggregated tag statistics as a JSON response.
*/
export function createSlackTagsHandler(
config: SlackTagsHandlerConfig,
Expand Down
Loading
Loading