Ahmed
Ahmed

Reputation: 1

AWS S3 Bucket React v3-client constantly returning 400 Bad Request

Got a custom text-editor with TipTap for a personal project. Had image upload to a basic filesystem server on EC2, now converting to S3.

Built the general logic, but on testing two part upload, I can get a signed URL but cannot PUT to create the image. I've tried all fixes - my CORS policy is set with allowing ETag headers, the content-type matches and the key is correct. The upload data looks good too. Would appericate any tips to get S3 working, I've searched far and wide for the solution. No response body from the 400 Bad Request.

Signed URL (Next.js 14 API code)

import { NextRequest, NextResponse } from 'next/server';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

interface UploadRequestBody {
    filename: string;
    contentType: string;
};

const s3Client = new S3Client({
    region: process.env.AWS_REGION,
    credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    },
});

export async function POST(req: NextRequest): Promise<NextResponse> {
    try {
        const { filename, contentType }: { filename: string; contentType: string } = await req.json();

        const key = `${Date.now()}_${filename}`;

        const command = new PutObjectCommand({
            Bucket: process.env.AWS_BUCKET_NAME!,
            Key: key,
            ContentType: contentType,
            ACL: 'public-read',
            Body: '', 
        });

        const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 120 }); 

        const imageUrl = `https://${process.env.AWS_BUCKET_NAME}.s3.amazonaws.com/${key}`;

        return NextResponse.json({ signedUrl, imageUrl });
    } catch (err) {
        console.error('Error generating signed URL', err);

        return NextResponse.json(
            { error: 'Failed to generate signed URL' },
            { status: 500 }
        );
    };
}

Uploading image data (Embded within a TipTap function)

if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) { 
          let file = event.dataTransfer.files[0];

          let img = new Image();
          let _URL = window.URL || window.webkitURL;

          img.src = _URL.createObjectURL(file);

          console.log('IMAGE UPLOAD');

          img.onload = async () => {
            try {
              const signedUrlResponse = await fetch('/api/s3', {
                method: 'POST',
                body: JSON.stringify({ filename: file.name, contentType: file.type }),
                headers: {
                  'Content-Type': 'application/json'
                },
              });

              if (!signedUrlResponse.ok) {
                throw new Error('Failed to get signed URL');
              };

              const { signedUrl, imageUrl } = await signedUrlResponse.json();

              const uploadResponse = await fetch(signedUrl, {
                method: 'PUT',
                body: file,
                headers: {
                  'Content-Type': file.type,
                },
              });

              if (!uploadResponse.ok) {
                console.log(uploadResponse);
                throw new Error('Failed to upload image to S3');
              }

              console.log('Image uploaded to S3:', imageUrl);
            } catch (error) {
              window.alert('Error uploading image: ' + error.message);
            }
          };
          return true;
        }

        return false;

CORS Policy AWS S3

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST"
        ],
        "AllowedOrigins": [
            "*",
            "http://localhost:3000/"
        ],
        "ExposeHeaders": [
            "x-amz-server-side-encryption",
            "x-amz-request-id",
            "x-amz-id-2",
            "ETag"
        ],
        "MaxAgeSeconds": 3000
    }
]

Tried correct CORS, Content-Type and checking correct file upload. Blob is made for temp showing image as upload is happening so 99% sure image is uploading correct and encoded correctly. Access keys are all good - they are root users for testing purposes.

Upvotes: 0

Views: 19

Answers (0)

Related Questions