From a56ad2ed15b3cff55d8c6f7d3032903285504e68 Mon Sep 17 00:00:00 2001 From: Lenny Date: Fri, 8 Aug 2025 11:26:20 -0400 Subject: [PATCH] fix: exclude bucket type in response for storage-py < 0.12.1 and supabase-py < 2.18.0 --- src/http/routes/bucket/getAllBuckets.ts | 11 ++++++--- src/storage/limits.ts | 17 +++++++++++-- src/test/bucket.test.ts | 33 ++++++++++++++++--------- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/http/routes/bucket/getAllBuckets.ts b/src/http/routes/bucket/getAllBuckets.ts index 6f537627..75c9cac8 100644 --- a/src/http/routes/bucket/getAllBuckets.ts +++ b/src/http/routes/bucket/getAllBuckets.ts @@ -4,7 +4,7 @@ import { createDefaultSchema } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' import { bucketSchema } from '@storage/schemas' import { ROUTE_OPERATIONS } from '../operations' -import { isPythonClientBefore } from '@storage/limits' +import { isClientVersionBefore } from '@storage/limits' const successResponseSchema = { type: 'array', @@ -63,11 +63,16 @@ export default async function routes(fastify: FastifyInstance) { // Detects user agents that support the type property in bucket list response // storage-py < v0.12.1 throws fatal error if type property is present // type property added in v0.12.1 -- https://github.com/supabase/storage-py/releases/tag/v0.12.1 - const includeBucketType = !isPythonClientBefore(request.headers['user-agent'] || '', '0.12.1') + // added to supabase-py in v2.18.0 -- https://github.com/supabase/supabase-py/releases/tag/v2.18.0 + const clientInfo = (request.headers['x-client-info'] as string) || '' + const userAgent = request.headers['user-agent'] || '' + const omitBucketType = + isClientVersionBefore('supabase-py', clientInfo, '2.18.0') || + isClientVersionBefore('storage3', userAgent, '0.12.1') const results = await request.storage.listBuckets( 'id, name, public, owner, created_at, updated_at, file_size_limit, allowed_mime_types' + - (includeBucketType ? ', type' : ''), + (omitBucketType ? '' : ', type'), { limit, offset, sortColumn, sortOrder, search } ) diff --git a/src/storage/limits.ts b/src/storage/limits.ts index 91da41d5..562f8e81 100644 --- a/src/storage/limits.ts +++ b/src/storage/limits.ts @@ -139,15 +139,28 @@ export function isEmptyFolder(object: string) { return object.endsWith('.emptyFolderPlaceholder') } +const CLIENT_AGENT_REGEX = { + // storage-py (storage3) = supabase-py/storage3 v0.12.1 + storage3: /supabase-py\/storage3 v(\d+)\.(\d+)\.(\d+)/i, + // supabase-py = supabase-py/2.17.0 + 'supabase-py': /supabase-py\/(\d+)\.(\d+)\.(\d+)/i, +} +export type ClientAgent = keyof typeof CLIENT_AGENT_REGEX + /** * Checks if the client is supabase-py and before the specified version * + * @param client which client type are we checking for * @param userAgent user agent header string * @param version semver to check against, must be in format '0.0.0' */ -export function isPythonClientBefore(userAgent: string, version: string): boolean { +export function isClientVersionBefore( + client: ClientAgent, + userAgent: string, + version: string +): boolean { const [minMajor, minMinor, minPatch] = version.split('.').map(Number) - const match = userAgent.match(/supabase-py\/storage3 v(\d+)\.(\d+)\.(\d+)/i) + const match = userAgent.match(CLIENT_AGENT_REGEX[client]) if (!match) { return false } diff --git a/src/test/bucket.test.ts b/src/test/bucket.test.ts index c56e6332..89d8e7be 100644 --- a/src/test/bucket.test.ts +++ b/src/test/bucket.test.ts @@ -125,24 +125,33 @@ describe('testing GET all buckets', () => { }) }) - for (const [userAgent, shouldIncludeType] of [ - ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/138.0.0.0 Safari/537.36', true], - ['supabase-py/storage3 v0.11.9', false], - ['supabase-py/storage3 v0.12.0', false], - ['supabase-py/storage3 v0.12.1', true], - ['supabase-py/storage3 v0.12.2', true], - ['supabase-py/storage3 v0.13.0', true], - ['supabase-py/storage3 v1.0.0', true], + for (const [headers, shouldIncludeType] of [ + [ + { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/138.0.0.0 Safari/537.36' }, + true, + ], + // storage-py (storage3) >= 0.12.1 + [{ 'user-agent': 'supabase-py/storage3 v0.11.9' }, false], + [{ 'user-agent': 'supabase-py/storage3 v0.12.0' }, false], + [{ 'user-agent': 'supabase-py/storage3 v0.12.1' }, true], + [{ 'user-agent': 'supabase-py/storage3 v0.12.2' }, true], + [{ 'user-agent': 'supabase-py/storage3 v0.13.0' }, true], + [{ 'user-agent': 'supabase-py/storage3 v1.0.0' }, true], + // supabase-py >= 2.18.0 + [{ 'x-client-info': 'supabase-py/2.17.3' }, false], + [{ 'x-client-info': 'supabase-py/2.18.0' }, true], + [{ 'x-client-info': 'supabase-py/2.18.1' }, true], + [{ 'x-client-info': 'supabase-py/2.19.0' }, true], ]) { - test(`Should ${ - shouldIncludeType ? '' : 'NOT ' - }include type for ${userAgent} client`, async () => { + test.only(`Should ${shouldIncludeType ? '' : 'NOT '}include type for ${JSON.stringify( + headers + )} client`, async () => { const response = await appInstance.inject({ method: 'GET', url: `/bucket`, headers: { authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, - 'user-agent': userAgent as string, + ...(headers as Record), }, }) expect(response.statusCode).toBe(200)