From 9bdedf57810282e7361ee483332e510ec32aa1be Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Thu, 15 May 2025 15:11:15 +0200 Subject: [PATCH 01/10] refactor: team api key creation --- .../dashboard/keys/create-api-key-dialog.tsx | 2 +- src/server/keys/key-actions.ts | 65 +++++++++---------- src/types/api.d.ts | 18 +++++ 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/features/dashboard/keys/create-api-key-dialog.tsx b/src/features/dashboard/keys/create-api-key-dialog.tsx index bb055d8..123b685 100644 --- a/src/features/dashboard/keys/create-api-key-dialog.tsx +++ b/src/features/dashboard/keys/create-api-key-dialog.tsx @@ -68,7 +68,7 @@ const CreateApiKeyDialog: FC = ({ const { execute: createApiKey, isPending } = useAction(createApiKeyAction, { onSuccess: ({ data }) => { if (data?.createdApiKey) { - setCreatedApiKey(data.createdApiKey) + setCreatedApiKey(data.createdApiKey.key) form.reset() } }, diff --git a/src/server/keys/key-actions.ts b/src/server/keys/key-actions.ts index 5854935..5bd4b1e 100644 --- a/src/server/keys/key-actions.ts +++ b/src/server/keys/key-actions.ts @@ -1,13 +1,15 @@ 'use server' -import { API_KEY_PREFIX } from '@/configs/constants' -import { checkUserTeamAuthorization } from '@/lib/utils/server' +import { checkUserTeamAuthorization, getApiUrl } from '@/lib/utils/server' import { supabaseAdmin } from '@/lib/clients/supabase/admin' import { z } from 'zod' import { revalidatePath } from 'next/cache' -import { InvalidParametersError } from '@/types/errors' import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' +import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { logError } from '@/lib/clients/logger' +import { ERROR_CODES } from '@/configs/logs' +import { CreatedTeamAPIKey } from '@/types/api' // Create API Key @@ -20,49 +22,42 @@ const CreateApiKeySchema = z.object({ .trim(), }) -export async function generateTeamApiKey(): Promise { - const randomBytes = crypto.getRandomValues(new Uint8Array(20)) - const hexString = Array.from(randomBytes) - .map((b) => b.toString(16).padStart(2, '0')) - .join('') - - return API_KEY_PREFIX + hexString -} - export const createApiKeyAction = authActionClient .schema(CreateApiKeySchema) .metadata({ actionName: 'createApiKey' }) .action(async ({ parsedInput, ctx }) => { const { teamId, name } = parsedInput - const { user, supabase } = ctx - - const isAuthorized = await checkUserTeamAuthorization(user.id, teamId) - - if (!isAuthorized) - return returnServerError('Not authorized to create team api keys') - - const apiKeyValue = await generateTeamApiKey() - - const { error } = await supabaseAdmin - .from('team_api_keys') - .insert({ - team_id: teamId, - name: name, - api_key: apiKeyValue, - created_by: user.id, - created_at: new Date().toISOString(), + const { session } = ctx + + const accessToken = session.access_token + + const { url } = await getApiUrl() + + const apiKeyResponse = await fetch(`${url}/api-keys`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...SUPABASE_AUTH_HEADERS(accessToken, teamId), + }, + body: JSON.stringify({ name }), + }) + + if (!apiKeyResponse.ok) { + const text = await apiKeyResponse.text() + logError(ERROR_CODES.INFRA, 'Failed to create api key', { + teamId, + name, + error: text, }) - .select() - .single() - - if (error) { - throw error + return returnServerError('Failed to create api key') } + const apiKeyData = (await apiKeyResponse.json()) as CreatedTeamAPIKey + revalidatePath(`/dashboard/[teamIdOrSlug]/keys`, 'page') return { - createdApiKey: apiKeyValue, + createdApiKey: apiKeyData, } }) diff --git a/src/types/api.d.ts b/src/types/api.d.ts index 70722dd..81bf709 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -44,6 +44,23 @@ interface CreatedAccessToken { token: string tokenMask: string createdAt: string + createdBy: { + email: string + id: string + } | null +} + +interface CreatedTeamAPIKey { + id: string + name: string + key: string + keyMask: string + createdAt: string + createdBy: { + email: string + id: string + } | null + lastUsed: string | null } export type { @@ -52,4 +69,5 @@ export type { SandboxMetrics, DefaultTemplate, CreatedAccessToken, + CreatedTeamAPIKey, } From 9763b7ec03b3cfd28e54e413b7356dcf65a4c3aa Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Thu, 15 May 2025 15:22:46 +0200 Subject: [PATCH 02/10] refactor: api key retrieval --- src/features/dashboard/keys/table-body.tsx | 21 ++++---- src/features/dashboard/keys/table-row.tsx | 8 +-- src/server/keys/get-api-keys.ts | 58 +++++++++------------- src/types/api.d.ts | 13 +++++ 4 files changed, 51 insertions(+), 49 deletions(-) diff --git a/src/features/dashboard/keys/table-body.tsx b/src/features/dashboard/keys/table-body.tsx index 319646f..0a873da 100644 --- a/src/features/dashboard/keys/table-body.tsx +++ b/src/features/dashboard/keys/table-body.tsx @@ -17,16 +17,17 @@ export default async function TableBodyContent({ const result = await getTeamApiKeys({ teamId }) if (!result?.data || result.serverError || result.validationErrors) { - ; - - - - - return + return ( + + + + + + ) } const { apiKeys } = result.data diff --git a/src/features/dashboard/keys/table-row.tsx b/src/features/dashboard/keys/table-row.tsx index 670d7d1..7f46ef3 100644 --- a/src/features/dashboard/keys/table-row.tsx +++ b/src/features/dashboard/keys/table-row.tsx @@ -12,7 +12,6 @@ import { DropdownMenuLabel, DropdownMenuTrigger, } from '@/ui/primitives/dropdown-menu' -import { ObscuredApiKey } from '@/server/keys/types' import { deleteApiKeyAction } from '@/server/keys/key-actions' import { AlertDialog } from '@/ui/alert-dialog' import { useState } from 'react' @@ -21,9 +20,10 @@ import { motion } from 'motion/react' import { exponentialSmoothing } from '@/lib/utils' import { useAction } from 'next-safe-action/hooks' import { defaultSuccessToast, defaultErrorToast } from '@/lib/hooks/use-toast' +import { TeamAPIKey } from '@/types/api' interface TableRowProps { - apiKey: ObscuredApiKey + apiKey: TeamAPIKey index: number } @@ -85,10 +85,10 @@ export default function ApiKeyTableRow({ apiKey, index }: TableRowProps) { > {apiKey.name} - {apiKey.maskedKey} + {apiKey.keyMask} - {apiKey.createdBy} + {apiKey.createdBy?.email} {apiKey.createdAt diff --git a/src/server/keys/get-api-keys.ts b/src/server/keys/get-api-keys.ts index 6920e7d..29d062b 100644 --- a/src/server/keys/get-api-keys.ts +++ b/src/server/keys/get-api-keys.ts @@ -2,11 +2,15 @@ import 'server-only' import { z } from 'zod' import { supabaseAdmin } from '@/lib/clients/supabase/admin' -import { maskApiKey } from '@/lib/utils/server' +import { getApiUrl, maskApiKey } from '@/lib/utils/server' import { checkUserTeamAuthorization } from '@/lib/utils/server' import { ObscuredApiKey } from './types' import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' +import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { logError } from '@/lib/clients/logger' +import { ERROR_CODES } from '@/configs/logs' +import { TeamAPIKey } from '@/types/api' const GetApiKeysSchema = z.object({ teamId: z.string({ required_error: 'Team ID is required' }).uuid(), @@ -17,45 +21,29 @@ export const getTeamApiKeys = authActionClient .metadata({ serverFunctionName: 'getTeamApiKeys' }) .action(async ({ parsedInput, ctx }) => { const { teamId } = parsedInput - const { user } = ctx + const { session } = ctx - const isAuthorized = await checkUserTeamAuthorization(user.id, teamId) + const accessToken = session.access_token - if (!isAuthorized) - return returnServerError('Not authorized to edit team api keys') + const { url } = await getApiUrl() - const { data, error } = await supabaseAdmin - .from('team_api_keys') - .select('*') - .eq('team_id', teamId) - .order('created_at', { ascending: true }) + const response = await fetch(`${url}/api-keys`, { + headers: { + 'Content-Type': 'application/json', + ...SUPABASE_AUTH_HEADERS(accessToken, teamId), + }, + }) - if (error) throw error - - const resultApiKeys: ObscuredApiKey[] = [] - - for (const apiKey of data) { - let userEmail: string | null = null - - if (apiKey.created_by) { - const { data: keyUserData } = await supabaseAdmin - .from('auth_users') - .select('email') - .eq('id', apiKey.created_by) - - if (keyUserData && keyUserData[0]) { - userEmail = keyUserData[0].email - } - } - - resultApiKeys.push({ - id: apiKey.id, - name: apiKey.name, - maskedKey: maskApiKey(apiKey), - createdAt: apiKey.created_at, - createdBy: userEmail, + if (!response.ok) { + const text = await response.text() + logError(ERROR_CODES.INFRA, 'Failed to get api keys', { + teamId, + error: text, }) + return returnServerError('Failed to get api keys') } - return { apiKeys: resultApiKeys } + const data = (await response.json()) as TeamAPIKey[] + + return { apiKeys: data } }) diff --git a/src/types/api.d.ts b/src/types/api.d.ts index 81bf709..8c156b6 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -63,6 +63,18 @@ interface CreatedTeamAPIKey { lastUsed: string | null } +interface TeamAPIKey { + id: string + name: string + keyMask: string + createdAt: string + createdBy: { + email: string + id: string + } | null + lastUsed: string | null +} + export type { Sandbox, Template, @@ -70,4 +82,5 @@ export type { DefaultTemplate, CreatedAccessToken, CreatedTeamAPIKey, + TeamAPIKey, } From 1afbf7675ec853862c2a745ce089cf0de647baa9 Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Fri, 23 May 2025 16:01:40 +0200 Subject: [PATCH 03/10] refactor: key crud / masking to use infra apis --- src/features/dashboard/keys/table-row.tsx | 8 ++++-- src/server/keys/get-api-keys.ts | 5 +--- src/types/api.d.ts | 35 +++++++++++++---------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/features/dashboard/keys/table-row.tsx b/src/features/dashboard/keys/table-row.tsx index 7f46ef3..5e9bcce 100644 --- a/src/features/dashboard/keys/table-row.tsx +++ b/src/features/dashboard/keys/table-row.tsx @@ -63,6 +63,8 @@ export default function ApiKeyTableRow({ apiKey, index }: TableRowProps) { }) } + const concatedKeyMask = `${apiKey.mask.prefix}${apiKey.mask.maskedValuePrefix}......${apiKey.mask.maskedValueSuffix}` + return ( <> setHoveredRowIndex(index)} onMouseLeave={() => setHoveredRowIndex(-1)} > - + {apiKey.name} - {apiKey.keyMask} + + {concatedKeyMask} + {apiKey.createdBy?.email} diff --git a/src/server/keys/get-api-keys.ts b/src/server/keys/get-api-keys.ts index 29d062b..12f9046 100644 --- a/src/server/keys/get-api-keys.ts +++ b/src/server/keys/get-api-keys.ts @@ -1,10 +1,7 @@ import 'server-only' import { z } from 'zod' -import { supabaseAdmin } from '@/lib/clients/supabase/admin' -import { getApiUrl, maskApiKey } from '@/lib/utils/server' -import { checkUserTeamAuthorization } from '@/lib/utils/server' -import { ObscuredApiKey } from './types' +import { getApiUrl } from '@/lib/utils/server' import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' diff --git a/src/types/api.d.ts b/src/types/api.d.ts index 8c156b6..909bcd0 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -38,40 +38,43 @@ interface SandboxMetrics { timestamp: string } +interface TeamUser { + id: string + email: string +} + +interface IdentifierMaskingDetails { + prefix: string + valueLength: number + maskedValuePrefix: string + maskedValueSuffix: string +} + interface CreatedAccessToken { id: string name: string token: string - tokenMask: string + mask: IdentifierMaskingDetails createdAt: string - createdBy: { - email: string - id: string - } | null + createdBy: TeamUser | null } interface CreatedTeamAPIKey { id: string name: string key: string - keyMask: string + mask: IdentifierMaskingDetails createdAt: string - createdBy: { - email: string - id: string - } | null + createdBy: TeamUser | null lastUsed: string | null } interface TeamAPIKey { id: string name: string - keyMask: string + mask: IdentifierMaskingDetails createdAt: string - createdBy: { - email: string - id: string - } | null + createdBy: TeamUser | null lastUsed: string | null } @@ -83,4 +86,6 @@ export type { CreatedAccessToken, CreatedTeamAPIKey, TeamAPIKey, + TeamUser, + IdentifierMaskingDetails, } From 695efb393c64d653e79db3722097c7256862edeb Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Fri, 23 May 2025 16:04:03 +0200 Subject: [PATCH 04/10] refactor: key deletion to use infra api --- src/server/keys/key-actions.ts | 44 ++++++++++++++-------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/server/keys/key-actions.ts b/src/server/keys/key-actions.ts index 5bd4b1e..1380b55 100644 --- a/src/server/keys/key-actions.ts +++ b/src/server/keys/key-actions.ts @@ -73,37 +73,29 @@ export const deleteApiKeyAction = authActionClient .metadata({ actionName: 'deleteApiKey' }) .action(async ({ parsedInput, ctx }) => { const { teamId, apiKeyId } = parsedInput - const { user, supabase } = ctx - - const isAuthorized = await checkUserTeamAuthorization(user.id, teamId) - - if (!isAuthorized) { - return returnServerError('Not authorized to delete team api keys') - } + const { session } = ctx - const { data: apiKeys, error: fetchError } = await supabaseAdmin - .from('team_api_keys') - .select('id') - .eq('team_id', teamId) + const accessToken = session.access_token - if (fetchError) { - throw fetchError - } + const { url } = await getApiUrl() - if (apiKeys.length === 1) { - return returnServerError( - 'A team must have at least one API key. Please create a new API key before deleting this one.' - ) - } + const response = await fetch(`${url}/api-keys/${apiKeyId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + ...SUPABASE_AUTH_HEADERS(accessToken, teamId), + }, + }) - const { error } = await supabaseAdmin - .from('team_api_keys') - .delete() - .eq('team_id', teamId) - .eq('id', apiKeyId) + if (!response.ok) { + const text = await response.text() + logError(ERROR_CODES.INFRA, 'Failed to delete api key', { + teamId, + apiKeyId, + error: text, + }) - if (error) { - throw error + return returnServerError(text) } revalidatePath(`/dashboard/[teamIdOrSlug]/keys`, 'page') From 027020b8bdd0ac6f5904a5ad6e2096a0bef4c7da Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Fri, 23 May 2025 16:24:53 +0200 Subject: [PATCH 05/10] improve: sensitive information redacting from action client --- src/lib/clients/action.ts | 60 ++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/src/lib/clients/action.ts b/src/lib/clients/action.ts index eed8a28..af86687 100644 --- a/src/lib/clients/action.ts +++ b/src/lib/clients/action.ts @@ -14,8 +14,26 @@ const BLACKLISTED_INPUT_KEYS = [ 'secret', 'token', 'apiKey', + 'key', ] +function sanitizeObject(data: unknown, blacklist: string[]): unknown { + if (typeof data === 'object' && data !== null && !Array.isArray(data)) { + const sanitized = { ...(data as Record) } + for (const key in sanitized) { + if (blacklist.includes(key)) { + sanitized[key] = '[REDACTED]' + } else { + sanitized[key] = sanitizeObject(sanitized[key], blacklist) + } + } + return sanitized + } else if (Array.isArray(data)) { + return data.map((item) => sanitizeObject(item, blacklist)) + } + return data +} + export const actionClient = createSafeActionClient({ handleServerError(e) { if (e instanceof ActionError) { @@ -60,41 +78,11 @@ export const actionClient = createSafeActionClient({ // filter out blacklisted keys from clientInput for logging let sanitizedInput: unknown = clientInput + sanitizedInput = sanitizeObject(clientInput, BLACKLISTED_INPUT_KEYS) - // handle object case - if ( - typeof clientInput === 'object' && - clientInput !== null && - !Array.isArray(clientInput) - ) { - sanitizedInput = { ...(clientInput as Record) } - const sanitizedObj = sanitizedInput as Record - - for (const key of BLACKLISTED_INPUT_KEYS) { - if (key in sanitizedObj) { - sanitizedObj[key] = '[REDACTED]' - } - } - } - // handle array case - else if (Array.isArray(clientInput)) { - sanitizedInput = [...clientInput] - const sanitizedArray = sanitizedInput as unknown[] - - // check if any array elements are objects that need sanitizing - for (let i = 0; i < sanitizedArray.length; i++) { - const item = sanitizedArray[i] - if (typeof item === 'object' && item !== null) { - const sanitizedItem = { ...(item as Record) } - for (const key of BLACKLISTED_INPUT_KEYS) { - if (key in sanitizedItem) { - sanitizedItem[key] = '[REDACTED]' - } - } - sanitizedArray[i] = sanitizedItem - } - } - } + // Sanitize result object + let sanitizedRest: unknown = rest + sanitizedRest = sanitizeObject(rest, BLACKLISTED_INPUT_KEYS) if ( result.serverError || @@ -102,12 +90,12 @@ export const actionClient = createSafeActionClient({ result.success === false ) { logError(`${actionOrFunction} '${actionOrFunctionName}' failed:`, { - result: rest, + result: sanitizedRest, input: sanitizedInput, }) } else if (VERBOSE) { logSuccess(`${actionOrFunction} '${actionOrFunctionName}' succeeded:`, { - result: rest, + result: sanitizedRest, input: sanitizedInput, }) } From dbad5ff91f774c913760fcc1a517adf32f109835 Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Fri, 23 May 2025 16:34:29 +0200 Subject: [PATCH 06/10] refactor: filter cli generate api keys --- src/server/keys/get-api-keys.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/server/keys/get-api-keys.ts b/src/server/keys/get-api-keys.ts index 12f9046..2317eb8 100644 --- a/src/server/keys/get-api-keys.ts +++ b/src/server/keys/get-api-keys.ts @@ -42,5 +42,11 @@ export const getTeamApiKeys = authActionClient const data = (await response.json()) as TeamAPIKey[] - return { apiKeys: data } + return { apiKeys: filterGeneratedServiceKeys(data) } }) + +const CLI_GENERATED_KEY_NAME = 'CLI login/configure' + +function filterGeneratedServiceKeys(apiKeys: TeamAPIKey[]): TeamAPIKey[] { + return apiKeys.filter((key) => key.name !== CLI_GENERATED_KEY_NAME) +} From ab98b700d25dfd75115ca4fb44cae78ec3cac7c8 Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Fri, 23 May 2025 17:49:38 +0200 Subject: [PATCH 07/10] revert: hide cli generated api keys --- src/server/keys/get-api-keys.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/server/keys/get-api-keys.ts b/src/server/keys/get-api-keys.ts index 2317eb8..12f9046 100644 --- a/src/server/keys/get-api-keys.ts +++ b/src/server/keys/get-api-keys.ts @@ -42,11 +42,5 @@ export const getTeamApiKeys = authActionClient const data = (await response.json()) as TeamAPIKey[] - return { apiKeys: filterGeneratedServiceKeys(data) } + return { apiKeys: data } }) - -const CLI_GENERATED_KEY_NAME = 'CLI login/configure' - -function filterGeneratedServiceKeys(apiKeys: TeamAPIKey[]): TeamAPIKey[] { - return apiKeys.filter((key) => key.name !== CLI_GENERATED_KEY_NAME) -} From 33682bdb8b76736fd4016934a9ad0762bfa4d360 Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Fri, 23 May 2025 18:01:19 +0200 Subject: [PATCH 08/10] chore: rename api config file + add: generated api key separation in keys table --- src/app/(auth)/sign-in/page.tsx | 1 - src/configs/{constants.ts => api.ts} | 2 ++ src/features/dashboard/keys/table-body.tsx | 31 ++++++++++++++++++++-- src/features/dashboard/keys/table-row.tsx | 8 +++++- src/lib/utils/server.ts | 2 +- src/server/billing/billing-actions.ts | 2 +- src/server/billing/get-billing-limits.ts | 4 +-- src/server/billing/get-invoices.ts | 2 +- src/server/keys/get-api-keys.ts | 2 +- src/server/keys/key-actions.ts | 2 +- src/server/sandboxes/get-team-sandboxes.ts | 2 +- src/server/team/team-actions.ts | 2 +- src/server/templates/get-team-templates.ts | 2 +- src/server/templates/templates-actions.ts | 2 +- src/server/usage/get-usage.ts | 2 +- src/ui/text-separator.tsx | 17 +++++++++--- 16 files changed, 63 insertions(+), 20 deletions(-) rename src/configs/{constants.ts => api.ts} (85%) diff --git a/src/app/(auth)/sign-in/page.tsx b/src/app/(auth)/sign-in/page.tsx index e0ad7d6..4ef84e4 100644 --- a/src/app/(auth)/sign-in/page.tsx +++ b/src/app/(auth)/sign-in/page.tsx @@ -6,7 +6,6 @@ import { OAuthProviders } from '@/features/auth/oauth-provider-buttons' import TextSeparator from '@/ui/text-separator' import { Button } from '@/ui/primitives/button' import { Input } from '@/ui/primitives/input' -import { Label } from '@/ui/primitives/label' import { AUTH_URLS } from '@/configs/urls' import Link from 'next/link' import { useSearchParams } from 'next/navigation' diff --git a/src/configs/constants.ts b/src/configs/api.ts similarity index 85% rename from src/configs/constants.ts rename to src/configs/api.ts index 6796ce9..fc3406b 100644 --- a/src/configs/constants.ts +++ b/src/configs/api.ts @@ -7,3 +7,5 @@ export const SUPABASE_AUTH_HEADERS = (token: string, teamId?: string) => ({ [SUPABASE_TOKEN_HEADER]: token, ...(teamId && { [SUPABASE_TEAM_HEADER]: teamId }), }) + +export const CLI_GENERATED_KEY_NAME = 'CLI login/configure' diff --git a/src/features/dashboard/keys/table-body.tsx b/src/features/dashboard/keys/table-body.tsx index 0a873da..8730c72 100644 --- a/src/features/dashboard/keys/table-body.tsx +++ b/src/features/dashboard/keys/table-body.tsx @@ -4,6 +4,9 @@ import { TableCell, TableRow } from '@/ui/primitives/table' import ApiKeyTableRow from './table-row' import { bailOutFromPPR } from '@/lib/utils/server' import { ErrorIndicator } from '@/ui/error-indicator' +import { CLI_GENERATED_KEY_NAME } from '@/configs/api' +import { Separator } from '@/ui/primitives/separator' +import TextSeparator from '@/ui/text-separator' interface TableBodyContentProps { teamId: string @@ -47,10 +50,34 @@ export default async function TableBodyContent({ ) } + const normalKeys = apiKeys.filter( + (key) => key.name !== CLI_GENERATED_KEY_NAME + ) + const cliKeys = apiKeys.filter((key) => key.name === CLI_GENERATED_KEY_NAME) + return ( <> - {apiKeys.map((key, index) => ( - + {normalKeys.map((key, index) => ( + + ))} + {cliKeys.length > 0 && normalKeys.length > 0 && ( + + + + + + )} + {cliKeys.map((key, index) => ( + ))} ) diff --git a/src/features/dashboard/keys/table-row.tsx b/src/features/dashboard/keys/table-row.tsx index 5e9bcce..9897d3a 100644 --- a/src/features/dashboard/keys/table-row.tsx +++ b/src/features/dashboard/keys/table-row.tsx @@ -25,9 +25,14 @@ import { TeamAPIKey } from '@/types/api' interface TableRowProps { apiKey: TeamAPIKey index: number + className?: string } -export default function ApiKeyTableRow({ apiKey, index }: TableRowProps) { +export default function ApiKeyTableRow({ + apiKey, + index, + className, +}: TableRowProps) { const { toast } = useToast() const selectedTeam = useSelectedTeam() const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) @@ -84,6 +89,7 @@ export default function ApiKeyTableRow({ apiKey, index }: TableRowProps) { key={`${apiKey.name}-${index}`} onMouseEnter={() => setHoveredRowIndex(index)} onMouseLeave={() => setHoveredRowIndex(-1)} + className={className} > {apiKey.name} diff --git a/src/lib/utils/server.ts b/src/lib/utils/server.ts index 26ba6c9..be7d696 100644 --- a/src/lib/utils/server.ts +++ b/src/lib/utils/server.ts @@ -17,7 +17,7 @@ import { kv } from '@/lib/clients/kv' import { KV_KEYS } from '@/configs/keys' import { ERROR_CODES, INFO_CODES } from '@/configs/logs' import { getEncryptedCookie } from './cookies' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' import { CreatedAccessToken } from '@/types/api' /* diff --git a/src/server/billing/billing-actions.ts b/src/server/billing/billing-actions.ts index d81ae77..9b61b64 100644 --- a/src/server/billing/billing-actions.ts +++ b/src/server/billing/billing-actions.ts @@ -1,6 +1,6 @@ 'use server' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' import { CustomerPortalResponse } from '@/types/billing' diff --git a/src/server/billing/get-billing-limits.ts b/src/server/billing/get-billing-limits.ts index 518ab77..ab3d87b 100644 --- a/src/server/billing/get-billing-limits.ts +++ b/src/server/billing/get-billing-limits.ts @@ -2,7 +2,7 @@ import 'server-only' import { z } from 'zod' import { BillingLimit } from '@/types/billing' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' import { authActionClient } from '@/lib/clients/action' const GetBillingLimitsParamsSchema = z.object({ @@ -32,7 +32,7 @@ export const getBillingLimits = authActionClient throw new Error( text ?? - `Failed to fetch billing endpoint: /teams/${teamId}/billing-limits` + `Failed to fetch billing endpoint: /teams/${teamId}/billing-limits` ) } diff --git a/src/server/billing/get-invoices.ts b/src/server/billing/get-invoices.ts index 1990312..f209457 100644 --- a/src/server/billing/get-invoices.ts +++ b/src/server/billing/get-invoices.ts @@ -2,7 +2,7 @@ import 'server-only' import { Invoice } from '@/types/billing' import { z } from 'zod' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' import { authActionClient } from '@/lib/clients/action' const GetInvoicesParamsSchema = z.object({ diff --git a/src/server/keys/get-api-keys.ts b/src/server/keys/get-api-keys.ts index 12f9046..ea080d7 100644 --- a/src/server/keys/get-api-keys.ts +++ b/src/server/keys/get-api-keys.ts @@ -4,7 +4,7 @@ import { z } from 'zod' import { getApiUrl } from '@/lib/utils/server' import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' import { logError } from '@/lib/clients/logger' import { ERROR_CODES } from '@/configs/logs' import { TeamAPIKey } from '@/types/api' diff --git a/src/server/keys/key-actions.ts b/src/server/keys/key-actions.ts index 1380b55..b08ffe3 100644 --- a/src/server/keys/key-actions.ts +++ b/src/server/keys/key-actions.ts @@ -6,7 +6,7 @@ import { z } from 'zod' import { revalidatePath } from 'next/cache' import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' import { logError } from '@/lib/clients/logger' import { ERROR_CODES } from '@/configs/logs' import { CreatedTeamAPIKey } from '@/types/api' diff --git a/src/server/sandboxes/get-team-sandboxes.ts b/src/server/sandboxes/get-team-sandboxes.ts index 0aea801..b5f0a87 100644 --- a/src/server/sandboxes/get-team-sandboxes.ts +++ b/src/server/sandboxes/get-team-sandboxes.ts @@ -8,7 +8,7 @@ import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' import { getApiUrl } from '@/lib/utils/server' import { Sandbox } from '@/types/api' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' const GetTeamSandboxesSchema = z.object({ teamId: z.string().uuid(), diff --git a/src/server/team/team-actions.ts b/src/server/team/team-actions.ts index feae921..63de5aa 100644 --- a/src/server/team/team-actions.ts +++ b/src/server/team/team-actions.ts @@ -14,7 +14,7 @@ import { zfd } from 'zod-form-data' import { logWarning } from '@/lib/clients/logger' import { returnValidationErrors } from 'next-safe-action' import { getTeam } from './get-team' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' import { CreateTeamSchema, UpdateTeamNameSchema } from '@/server/team/types' import { CreateTeamsResponse } from '@/types/billing' diff --git a/src/server/templates/get-team-templates.ts b/src/server/templates/get-team-templates.ts index 50c248e..ed3eb1a 100644 --- a/src/server/templates/get-team-templates.ts +++ b/src/server/templates/get-team-templates.ts @@ -12,7 +12,7 @@ import { ERROR_CODES } from '@/configs/logs' import { supabaseAdmin } from '@/lib/clients/supabase/admin' import { actionClient, authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' const GetTeamTemplatesSchema = z.object({ teamId: z.string().uuid(), diff --git a/src/server/templates/templates-actions.ts b/src/server/templates/templates-actions.ts index 9df5246..75da2d3 100644 --- a/src/server/templates/templates-actions.ts +++ b/src/server/templates/templates-actions.ts @@ -5,7 +5,7 @@ import { getApiUrl } from '@/lib/utils/server' import { revalidatePath } from 'next/cache' import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' const DeleteTemplateParamsSchema = z.object({ templateId: z.string(), diff --git a/src/server/usage/get-usage.ts b/src/server/usage/get-usage.ts index a4f89c7..3d17299 100644 --- a/src/server/usage/get-usage.ts +++ b/src/server/usage/get-usage.ts @@ -9,7 +9,7 @@ import { import { z } from 'zod' import { authActionClient } from '@/lib/clients/action' import { returnServerError } from '@/lib/utils/action' -import { SUPABASE_AUTH_HEADERS } from '@/configs/constants' +import { SUPABASE_AUTH_HEADERS } from '@/configs/api' import { UsageResponse } from '@/types/billing' const GetUsageAuthActionSchema = z.object({ diff --git a/src/ui/text-separator.tsx b/src/ui/text-separator.tsx index 06bb106..c806b36 100644 --- a/src/ui/text-separator.tsx +++ b/src/ui/text-separator.tsx @@ -1,15 +1,24 @@ +import { cn } from '@/lib/utils' import { Separator } from './primitives/separator' interface TextSeparatorProps { text: string + classNames?: { + text?: string + } } -export default function TextSeparator({ text }: TextSeparatorProps) { +export default function TextSeparator({ + text, + classNames, +}: TextSeparatorProps) { return (
- - {text} - + + + {text} + +
) } From 0542e9f820a996c633f1744684c9af5b9efcee3a Mon Sep 17 00:00:00 2001 From: Ben Fornefeld <50748440+ben-fornefeld@users.noreply.github.com> Date: Fri, 23 May 2025 18:12:22 +0200 Subject: [PATCH 09/10] Update src/features/dashboard/keys/table-row.tsx Co-authored-by: Jakub Dobry --- src/features/dashboard/keys/table-row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/dashboard/keys/table-row.tsx b/src/features/dashboard/keys/table-row.tsx index 9897d3a..678672d 100644 --- a/src/features/dashboard/keys/table-row.tsx +++ b/src/features/dashboard/keys/table-row.tsx @@ -91,7 +91,7 @@ export default function ApiKeyTableRow({ onMouseLeave={() => setHoveredRowIndex(-1)} className={className} > - + {apiKey.name} {concatedKeyMask} From 8f87d4b40ad581016f3d30c835024e898f0479f2 Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Fri, 23 May 2025 18:22:36 +0200 Subject: [PATCH 10/10] chore: remove separator --- src/features/dashboard/keys/table-body.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/features/dashboard/keys/table-body.tsx b/src/features/dashboard/keys/table-body.tsx index 8730c72..3f6b74d 100644 --- a/src/features/dashboard/keys/table-body.tsx +++ b/src/features/dashboard/keys/table-body.tsx @@ -58,20 +58,8 @@ export default async function TableBodyContent({ return ( <> {normalKeys.map((key, index) => ( - + ))} - {cliKeys.length > 0 && normalKeys.length > 0 && ( - - - - - - )} {cliKeys.map((key, index) => (