405 Error with S3 Presigned URL's

I currently have a 'PDF viewer' on my website which is suppose to render a PDF from my s3 bucket and prevent the user from being able to download, print, etc the pdf. Here's the code for the /exams/page.tsx file (the PDF viewer)
"use client";
import React, { useState, useEffect } from "react";

const PDFViewer = (presignedUrl: RequestInfo | URL) => {
const [pdfUrl, setPdfUrl] = useState("");

useEffect(() => {
const fetchPdf = async () => {
try {
const objectKey = "biology/Photosynthesis 1.pdf";
const response = await fetch(`/api/exams?objectKey=${objectKey}`);
const pdfObjectUrl = URL.createObjectURL(await response.blob());
setPdfUrl(pdfObjectUrl);
} catch (error) {
console.error("Error fetching PDF:", error);
}
};

fetchPdf();

return () => {
if (pdfUrl) {
URL.revokeObjectURL(pdfUrl);
}
};
}, []);

const handleContextMenu = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
event.preventDefault();
};

const handleMouseDown = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
if (event.button === 2) {
event.preventDefault();
}
};

return (
<div
className="fixed inset-0 flex items-center justify-center"
onContextMenu={handleContextMenu}
onMouseDown={handleMouseDown}
>
<div className="absolute inset-0 bg-gray-900 opacity-75 pointer-events-none" />
<div className="relative w-full h-full flex items-center justify-center">
{pdfUrl ? (
<embed
src={`${pdfUrl}#toolbar=0`}
type="application/pdf"
className="w-full h-full"
onContextMenu={handleContextMenu}
/>
) : (
<p>Loading PDF...</p>
)}
</div>
</div>
);
};

export default PDFViewer;
"use client";
import React, { useState, useEffect } from "react";

const PDFViewer = (presignedUrl: RequestInfo | URL) => {
const [pdfUrl, setPdfUrl] = useState("");

useEffect(() => {
const fetchPdf = async () => {
try {
const objectKey = "biology/Photosynthesis 1.pdf";
const response = await fetch(`/api/exams?objectKey=${objectKey}`);
const pdfObjectUrl = URL.createObjectURL(await response.blob());
setPdfUrl(pdfObjectUrl);
} catch (error) {
console.error("Error fetching PDF:", error);
}
};

fetchPdf();

return () => {
if (pdfUrl) {
URL.revokeObjectURL(pdfUrl);
}
};
}, []);

const handleContextMenu = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
event.preventDefault();
};

const handleMouseDown = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
if (event.button === 2) {
event.preventDefault();
}
};

return (
<div
className="fixed inset-0 flex items-center justify-center"
onContextMenu={handleContextMenu}
onMouseDown={handleMouseDown}
>
<div className="absolute inset-0 bg-gray-900 opacity-75 pointer-events-none" />
<div className="relative w-full h-full flex items-center justify-center">
{pdfUrl ? (
<embed
src={`${pdfUrl}#toolbar=0`}
type="application/pdf"
className="w-full h-full"
onContextMenu={handleContextMenu}
/>
) : (
<p>Loading PDF...</p>
)}
</div>
</div>
);
};

export default PDFViewer;
(continued because im running out of characters)
5 Replies
wlvz
wlvzOP2y ago
I also have an API in /api/exams with a function to get a presigned URL from S3 /api/exams/route.ts
const AWS = require('aws-sdk');

// Configure AWS credentials and region
AWS.config.update({
accessKeyId: 'removed',
secretAccessKey: 'removed',
region: 'me-central-1',
});

const s3 = new AWS.S3();

const generatePresignedUrl = async (bucketName: String, objectKey: any) => {
try {
// Generate a pre-signed URL for the S3 object
const signedUrl = await s3.getSignedUrlPromise('getObject', {
Bucket: "bucketname",
Key: objectKey,
Expires: 60 * 5, // URL expires in 5 minutes
});

return signedUrl;
} catch (error) {
console.error('Error generating pre-signed URL:', error);
throw error;
}
};

export default generatePresignedUrl;
const AWS = require('aws-sdk');

// Configure AWS credentials and region
AWS.config.update({
accessKeyId: 'removed',
secretAccessKey: 'removed',
region: 'me-central-1',
});

const s3 = new AWS.S3();

const generatePresignedUrl = async (bucketName: String, objectKey: any) => {
try {
// Generate a pre-signed URL for the S3 object
const signedUrl = await s3.getSignedUrlPromise('getObject', {
Bucket: "bucketname",
Key: objectKey,
Expires: 60 * 5, // URL expires in 5 minutes
});

return signedUrl;
} catch (error) {
console.error('Error generating pre-signed URL:', error);
throw error;
}
};

export default generatePresignedUrl;
When I change
const response = await fetch(`/api/exams?objectKey=${objectKey}`);
const response = await fetch(`/api/exams?objectKey=${objectKey}`);
to a presigned URL I get directly from S3 console, the PDF works and renders completely fine. However, when I try and get a new presigned URL by calling my api, and then passing that response as oppose to getting a presigned URL from the S3 console, I get the error:
GET http://localhost:3000/api/exams?objectKey=biology/Photosynthesis%201.pdf 405 (Method Not Allowed)
page.tsx:11
GET http://localhost:3000/api/exams?objectKey=biology/Photosynthesis%201.pdf 405 (Method Not Allowed)
page.tsx:11
I've made an IAM user with the permissions required and passed in the credentials to those in "accessKeyId" and "secretAccessKey". The bucket name is also correct, and so is the object key. Anyone know how I can fix this? I also get the following errors in my terminal
Detected default export in '/Users/user/Code/projectName/app/api/exams/route.ts'. Export a named export for each HTTP method instead.
- error No HTTP methods exported in '/Users/user/Code/projectName/app/api/exams/route.ts'. Export a named export for each HTTP method.
Detected default export in '/Users/user/Code/projectName/app/api/exams/route.ts'. Export a named export for each HTTP method instead.
- error No HTTP methods exported in '/Users/user/Code/projectName/app/api/exams/route.ts'. Export a named export for each HTTP method.
as well as a warning to migrate to the AWS SDK (v3) (not sure if the outdated AWS SDK could be causing the issue)
whatplan
whatplan2y ago
not a ton of aws experience but my guess is this is a permissions thing you could try generating a new set of credentials with every permission and see if it works then take away one at a time to see what mightve been causing the error
iukea
iukea2y ago
https://link.medium.com/B21i2jAJlAb Maybe encryption keys with the objects that are at rest
theburningmonk.com
Yes, S3 now encrypts objects by default, but your job is not done y...
Learn to build production-ready serverless applications on AWS
iukea
iukea2y ago
Posting this because when you create a pre signed URL you are in essence sharing the exception key to the user "Any request through the AWS SDK, AWS CLI or via the public URL of an object would give the attacker access to the unencrypted object contents. S3 would decrypt the data and return the unencrypted data because it owns the encryption keys."
wlvz
wlvzOP2y ago
thought it was this too but my IAM user (the one im using to make the api call) has "AmazonS3FullAccess" as part of its permissions fixed it 👍
Want results from more Discord servers?
Add your server