I have next.js app that uses the Google Cloud Storage node.js SDK to upload videos and view them by generating signedUrls. Everything works when running locally, but as soon as I deploy the app (Cloud Run), none of my bucket actions work, particularly generating signedUrls. I generate singedUrls on the backend for the client to view videos I have on the bucket.
When I go to the generated link, I see this:
<Code>SignatureDoesNotMatch</Code>
<Message>Access denied.</Message>
<Details>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Details>
Things I have tried:
- taken the entire service account key and encoded into a base64 and used as a env var. This works fine locally.
- triple checked the env value of the secret on GCP and my local value.
- updated the SDK to latest (7.11.0)
- Given the service account that’s running the service “Service Account Token Creator” role.
- adjusted bucket CORS: tried both “*” and “my-domain”. Regardless of what the cors is, seems to just work fine locally.
- tried changing content type on the generating url function, but that only returned new errors.
- tried to remove the env injection when making the
new Storage()
instance and let GCP input its own credentials(?) as a part of the cloud run(?). Just got 500 errors then.
My code:
import { Storage } from "@google-cloud/storage";
import { env } from "process";
const rawKey = env.GCP_BUCKET_HANDLER_KEY ?? "";
const creds = rawKey
? JSON.parse(Buffer.from(rawKey, "base64").toString())
: {};
const storage = new Storage({
projectId: creds.project_id,
credentials: creds,
});
const PRIMARY_BUCKET_NAME = env.GCP_PRIMARY_BUCKET_NAME ?? "invalid";
export const primaryBucket = storage.bucket(PRIMARY_BUCKET_NAME);
export async function gcGenerateReadSignedUrl({
fileName,
id,
}: GcVideoFilePathProps) {
const filePath = `video/${id}/${fileName}`;
const options: GetSignedUrlConfig = {
version: "v4",
action: "read",
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
};
const [url] = await primaryBucket.file(filePath).getSignedUrl(options);
return url;
}
Running out of ideas here. Been bashing my head against this for two days now.