From 31a026e6f032d0f67e64c59a45c7d5f736c94a94 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 25 Nov 2025 12:34:07 +0800 Subject: [PATCH] Joshen/depr 253 tighten field validation for bucket name and table name (#40739) * Update validation for analytics bucket name * Update comment * Update bucket name validation for vector buckets --- .../CreateAnalyticsBucketModal.tsx | 58 +++++++++++++++++-- .../CreateAnalyticsBucketModal.utils.ts | 14 +++++ .../CreateVectorBucketDialog.tsx | 38 +++++++++--- .../CreateVectorBucketDialog.utils.ts | 7 +++ 4 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 apps/studio/components/interfaces/Storage/AnalyticsBuckets/CreateAnalyticsBucketModal.utils.ts create mode 100644 apps/studio/components/interfaces/Storage/VectorBuckets/CreateVectorBucketDialog.utils.ts diff --git a/apps/studio/components/interfaces/Storage/AnalyticsBuckets/CreateAnalyticsBucketModal.tsx b/apps/studio/components/interfaces/Storage/AnalyticsBuckets/CreateAnalyticsBucketModal.tsx index 8f497f50d61..c752555f9ec 100644 --- a/apps/studio/components/interfaces/Storage/AnalyticsBuckets/CreateAnalyticsBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/AnalyticsBuckets/CreateAnalyticsBucketModal.tsx @@ -38,17 +38,21 @@ import { } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { inverseValidBucketNameRegex, validBucketNameRegex } from '../CreateBucketModal.utils' import { BUCKET_TYPES } from '../Storage.constants' import { useIcebergWrapperExtension } from './AnalyticsBucketDetails/useIcebergWrapper' +import { + reservedPrefixes, + reservedSuffixes, + validBucketNameRegex, +} from './CreateAnalyticsBucketModal.utils' const FormSchema = z .object({ name: z .string() .trim() - .min(1, 'Please provide a name for your bucket') - .max(100, 'Bucket name should be below 100 characters') + .min(3, 'Bucket name should be at least 3 characters') + .max(63, 'Bucket name should be up to 63 characters') .refine( (value) => !value.endsWith(' '), 'The name of the bucket cannot end with a whitespace' @@ -59,9 +63,51 @@ const FormSchema = z ), }) .superRefine((data, ctx) => { + if (reservedPrefixes.test(data.name)) { + const [match] = data.name.match(reservedPrefixes) ?? [] + return ctx.addIssue({ + path: ['name'], + code: z.ZodIssueCode.custom, + message: `Bucket name cannot start with "${match}"`, + }) + } + + if (reservedSuffixes.test(data.name)) { + const [match] = data.name.match(reservedSuffixes) ?? [] + return ctx.addIssue({ + path: ['name'], + code: z.ZodIssueCode.custom, + message: `Bucket name cannot end with "${match}"`, + }) + } + + if (/[A-Z]/.test(data.name)) { + return ctx.addIssue({ + path: ['name'], + code: z.ZodIssueCode.custom, + message: 'Bucket name can only be lowercase characters', + }) + } + if (!validBucketNameRegex.test(data.name)) { - const [match] = data.name.match(inverseValidBucketNameRegex) ?? [] - ctx.addIssue({ + if (!/^[a-z0-9]/.test(data.name)) { + return ctx.addIssue({ + path: ['name'], + code: z.ZodIssueCode.custom, + message: 'Bucket name must start with a lowercase letter or number.', + }) + } + + if (!/[a-z0-9]$/.test(data.name)) { + return ctx.addIssue({ + path: ['name'], + code: z.ZodIssueCode.custom, + message: 'Bucket name must end with a lowercase letter or number.', + }) + } + + const [match] = data.name.match(/[^a-z0-9-]/) ?? [] + return ctx.addIssue({ path: ['name'], code: z.ZodIssueCode.custom, message: !!match @@ -238,7 +284,7 @@ export const CreateAnalyticsBucketModal = ({ name="name" label="Bucket name" labelOptional="Cannot be changed after creation" - description="Must be between 3–63 characters. Only lowercase letters, numbers, dots, and hyphens are allowed." + description="Must be between 3–63 characters. Only lowercase letters, numbers, and hyphens are allowed." > { - if (!validBucketNameRegex.test(name)) { - const [match] = name.match(inverseValidBucketNameRegex) ?? [] - ctx.addIssue({ + if (!validVectorBucketName.test(name)) { + if (/[A-Z]/.test(name)) { + return ctx.addIssue({ + path: [], + code: z.ZodIssueCode.custom, + message: 'Bucket name can only be lowercase characters', + }) + } + + if (!/^[a-z0-9]/.test(name)) { + return ctx.addIssue({ + path: [], + code: z.ZodIssueCode.custom, + message: 'Bucket name must start with a lowercase letter or number.', + }) + } + + if (!/[a-z0-9]$/.test(name)) { + return ctx.addIssue({ + path: [], + code: z.ZodIssueCode.custom, + message: 'Bucket name must end with a lowercase letter or number.', + }) + } + + const [match] = name.match(/[^a-z0-9-]/) ?? [] + return ctx.addIssue({ path: [], code: z.ZodIssueCode.custom, message: !!match diff --git a/apps/studio/components/interfaces/Storage/VectorBuckets/CreateVectorBucketDialog.utils.ts b/apps/studio/components/interfaces/Storage/VectorBuckets/CreateVectorBucketDialog.utils.ts new file mode 100644 index 00000000000..8977e13c9d4 --- /dev/null +++ b/apps/studio/components/interfaces/Storage/VectorBuckets/CreateVectorBucketDialog.utils.ts @@ -0,0 +1,7 @@ +/** + * Rules: https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-buckets-naming.html?icmpid=docs_amazons3_console + * Vector bucket names must be between 3 and 63 characters long. + * Vector bucket names can consist only of lowercase letters (a-z), numbers (0-9), and hyphens (-). + * Vector bucket names must begin and end with a letter or number. + */ +export const validVectorBucketName = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/