diff --git a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index 5aae43c23d6..d6380f9ffeb 100644 --- a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -22,7 +22,15 @@ import { withAuth } from 'hooks/misc/withAuth' import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' import { useAppStateSnapshot } from 'state/app-state' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' -import { cn, ResizableHandle, ResizablePanel, ResizablePanelGroup, Sheet, SheetContent } from 'ui' +import { + Alert_Shadcn_, + cn, + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, + Sheet, + SheetContent, +} from 'ui' import AppLayout from '../AppLayout/AppLayout' import EnableBranchingModal from '../AppLayout/EnableBranchingButton/EnableBranchingModal' import BuildingState from './BuildingState' @@ -43,6 +51,7 @@ import RestoreFailedState from './RestoreFailedState' import RestoringState from './RestoringState' import { UpgradingState } from './UpgradingState' import MobileSheetNav from 'ui-patterns/MobileSheetNav/MobileSheetNav' +import { useServiceRoleKeyLeakQuery } from 'data/lint/service-role-key-leak-query' // [Joshen] This is temporary while we unblock users from managing their project // if their project is not responding well for any reason. Eventually needs a bit of an overhaul @@ -121,6 +130,9 @@ const ProjectLayout = forwardRef { setIsClient(true) }, []) @@ -224,6 +236,9 @@ const ProjectLayout = forwardRef ) : ( + + Your `service_role` key has been leaked...warning / docs / etc + {children} diff --git a/apps/studio/data/lint/keys.ts b/apps/studio/data/lint/keys.ts index 7a59b16dce2..b5526be382b 100644 --- a/apps/studio/data/lint/keys.ts +++ b/apps/studio/data/lint/keys.ts @@ -1,3 +1,5 @@ export const lintKeys = { lint: (projectRef: string | undefined) => ['projects', projectRef, 'lint'] as const, + serviceRoleKeyLeak: (projectRef: string | undefined) => + ['projects', projectRef, 'service-role-key-leak'] as const, } diff --git a/apps/studio/data/lint/service-role-key-leak-query.ts b/apps/studio/data/lint/service-role-key-leak-query.ts new file mode 100644 index 00000000000..b9d624ea408 --- /dev/null +++ b/apps/studio/data/lint/service-role-key-leak-query.ts @@ -0,0 +1,57 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query' + +import { useSelectedProject } from 'hooks/misc/useSelectedProject' +import { BASE_PATH, PROJECT_STATUS } from 'lib/constants' +import { ResponseError } from 'types' +import { lintKeys } from './keys' + +export type ServiceRoleKeyLeakVariables = { + projectRef?: string +} + +// TODO: type this +export type ServiceRoleKeyLeakResponse = any[] + +export async function checkServiceRoleKeyLeak( + { projectRef }: ServiceRoleKeyLeakVariables, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('Project ref is required') + + const res = await fetch(`${BASE_PATH}/api/check-service-role-key-leak`, { + headers: { + 'x-project': projectRef, + }, + signal, + }) + + if (!res.ok) { + const error = await res.json() + throw new Error(error.error?.message || 'Failed to fetch service role key leak data') + } + + return res.json() +} + +export type ServiceRoleKeyLeakData = Awaited> +export type ServiceRoleKeyLeakError = ResponseError + +export const useServiceRoleKeyLeakQuery = ( + { projectRef }: ServiceRoleKeyLeakVariables, + { + enabled = true, + ...options + }: UseQueryOptions = {} +) => { + const project = useSelectedProject() + const isActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY + + return useQuery( + lintKeys.serviceRoleKeyLeak(projectRef), + ({ signal }) => checkServiceRoleKeyLeak({ projectRef }, signal), + { + enabled: enabled && typeof projectRef !== 'undefined' && isActive, + ...options, + } + ) +} diff --git a/apps/studio/data/usage/resource-warnings-query.ts b/apps/studio/data/usage/resource-warnings-query.ts index 1c4e495bb2a..0be1bdebf68 100644 --- a/apps/studio/data/usage/resource-warnings-query.ts +++ b/apps/studio/data/usage/resource-warnings-query.ts @@ -9,8 +9,18 @@ import { usageKeys } from './keys' export async function getResourceWarnings(signal?: AbortSignal) { const { data, error } = await get(`/platform/projects-resource-warnings`, { signal }) if (error) handleError(error) - - return data + // for testing purposes, return a hardcoded array of resource warnings + return [ + { + project: 'aclodpjssdgdxbvmybdv', //replace with your ref to get an example warning + is_readonly_mode_enabled: false, + disk_io_exhaustion: null, + disk_space_exhaustion: null, + cpu_exhaustion: 'critical', + memory_and_swap_exhaustion: null, + }, + ] as ResourceWarning[] + //return data } export type ResourceWarning = components['schemas']['ProjectResourceWarningsResponse'] diff --git a/apps/studio/middleware.ts b/apps/studio/middleware.ts index b5179623c3f..6cb4df7fbb8 100644 --- a/apps/studio/middleware.ts +++ b/apps/studio/middleware.ts @@ -16,6 +16,8 @@ const HOSTED_SUPPORTED_API_URLS = [ '/ai/sql/cron', '/ai/docs', '/get-ip-address', + '/get-ip-address', + '/check-service-role-key-leak', ] export function middleware(request: NextRequest) { diff --git a/apps/studio/pages/api/check-service-role-key-leak.ts b/apps/studio/pages/api/check-service-role-key-leak.ts new file mode 100644 index 00000000000..f98c41e0653 --- /dev/null +++ b/apps/studio/pages/api/check-service-role-key-leak.ts @@ -0,0 +1,54 @@ +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'GET': + return handleGet(req, res) + default: + res.setHeader('Allow', ['GET']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +async function handleGet(req: NextApiRequest, res: NextApiResponse) { + const projectRef = req.headers['x-project'] as string + const project = 'bzembqluzmwrjuidwley' + console.log('the ref', projectRef) + + if (!project) { + return res.status(400).json({ error: 'Project reference is required' }) + } + + try { + const response = await fetch( + `https://api.logflare.app/api/endpoints/query/exposed%20service_role%20advisor%20hack?project=${project}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json; charset=utf-8', + 'X-API-KEY': process.env.LOGFLARE_API_KEY!, + }, + } + ) + + if (!response.ok) { + throw new Error(`Logflare API responded with status ${response.status}`) + } + + const data = await response.json() + return res.json(data) + } catch (error) { + console.error('Error fetching service role key leak data:', error) + return res.status(500).json({ + error: + 'There was an unknown error fetching the service role key leak data. Please try again.', + }) + } +} + +const wrapper = (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +export default wrapper