diff --git a/.github/workflows/ci-admin.yml b/.github/workflows/ci-admin.yml index 1b94778..7cbf689 100644 --- a/.github/workflows/ci-admin.yml +++ b/.github/workflows/ci-admin.yml @@ -32,7 +32,9 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 'lts/*' + # Astro 6.x requires Node >=22.12.0. + # Pin to Node 22 to avoid LTS rollover surprises breaking Admin CI. + node-version: '22.12.0' cache: 'npm' cache-dependency-path: gitstore-admin/package-lock.json @@ -47,6 +49,9 @@ jobs: run: docker compose up -d --wait env: GITSTORE_GIT__DATA_DIR: ./data/repos + GITSTORE_AUTH__ADMIN__USERNAME: admin + GITSTORE_AUTH__ADMIN__PASSWORD_HASH: $2a$12$test.hash.for.ci.only.not.a.real.secret.AAAA + GITSTORE_AUTH__JWT__SECRET: ci-test-jwt-secret-minimum-32-characters-long - name: Install Playwright browsers run: npx playwright install --with-deps diff --git a/gitstore-admin/src/components/products/ProductForm.tsx b/gitstore-admin/src/components/products/ProductForm.tsx index 1a08d37..7bcf6a6 100644 --- a/gitstore-admin/src/components/products/ProductForm.tsx +++ b/gitstore-admin/src/components/products/ProductForm.tsx @@ -52,6 +52,18 @@ const INVENTORY_STATUSES = [ * Handles all product fields including title, SKU, price, category, collections */ export function ProductForm({ product, onSubmit, onCancel, isLoading = false }: ProductFormProps) { + const sanitizeImageUrl = (raw: string): string | null => { + try { + const parsed = new URL(raw); + if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { + return parsed.toString(); + } + return null; + } catch { + return null; + } + }; + const [formData, setFormData] = useState({ title: '', sku: '', @@ -119,10 +131,11 @@ export function ProductForm({ product, onSubmit, onCancel, isLoading = false }: }; const handleAddImage = () => { - if (imageInput.trim()) { + const sanitizedImageUrl = sanitizeImageUrl(imageInput.trim()); + if (sanitizedImageUrl) { setFormData(prev => ({ ...prev, - images: [...prev.images, imageInput.trim()], + images: [...prev.images, sanitizedImageUrl], })); setImageInput(''); } @@ -415,20 +428,23 @@ export function ProductForm({ product, onSubmit, onCancel, isLoading = false }: {formData.images.length > 0 && (
- {formData.images.map((image, index) => ( -
- {`Product -
{image}
- -
- ))} + {formData.images.map((image, index) => { + const safeImageUrl = sanitizeImageUrl(image); + return ( +
+ {`Product +
{image}
+ +
+ ); + })}
)}