Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ upstash blob cp blob://my-bucket/config.json - | jq .
upstash blob mv blob://my-bucket/a.txt blob://other-bucket/a.txt
upstash blob sync ./site blob://my-bucket/site -d
upstash blob rm my-bucket/tmp -r -n
upstash blob presign my-bucket/report.pdf --expires-in 3600
upstash blob presign my-bucket/report.pdf --expires-in 600
upstash blob mb blob://new-bucket
upstash blob rb new-bucket -f
```
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"author": "Upstash",
"license": "MIT",
"dependencies": {
"@upstash/blob": "0.0.5",
"@upstash/blob": "0.0.7",
"commander": "^13.0.0",
"dotenv": "^16.4.5",
"mime": "4.1.0"
Expand Down
13 changes: 5 additions & 8 deletions src/commands/blob/presign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { printJSON } from "../../output.js";
import { BucketResolver } from "./buckets.js";
import { formatLocation, parseBlobLocation } from "./transfer.js";

const MAX_EXPIRES_IN = 7 * 24 * 60 * 60;
// Upstash signs the URL and caps it at 10 minutes.
const MAX_EXPIRES_IN = 600;

function expiresIn(value: string): number {
const seconds = Number(value);
Expand All @@ -17,21 +18,17 @@ export function registerBlobPresign(blob: Command): void {
blob
.command("presign <path>")
.description("Create a temporary download URL for an object, like aws s3 presign")
.option("--expires-in <seconds>", "How long the URL should work", expiresIn, 3600)
.option("--expires-in <seconds>", `How long the URL should work, at most ${MAX_EXPIRES_IN}`, expiresIn, MAX_EXPIRES_IN)
.option("--token <token>", "Blob bucket token, used for the bucket it was issued for (default: UPSTASH_BLOB_TOKEN)")
.addHelpText("after", `
A URL never outlives the temporary credential that signs it, so it can expire
sooner than requested; expires_at is when it actually stops working.
Upstash signs the URL for this one object; it carries no bucket credential and
lives at most 10 minutes. expires_at is when it actually stops working.
`)
.action(async (uri: string, options: { expiresIn: number; token?: string }, cmd: Command) => {
const location = parseBlobLocation(uri);
if (!location.key || location.key.endsWith("/")) throw new Error(`${formatLocation(location)} names no object`);
const bucket = await new BucketResolver(cmd, options.token).open(location.bucket);
const signed = await bucket.signedReadUrl(location.key, { expiresIn: options.expiresIn });
const requested = Date.now() + options.expiresIn * 1000;
if (signed.expiresAt.getTime() < requested - 60_000) {
console.error(`note: the URL expires at ${signed.expiresAt.toISOString()}, sooner than requested, because its signing credential expires then`);
}
printJSON({ url: signed.url, expires_at: signed.expiresAt.toISOString() });
});
}
2 changes: 2 additions & 0 deletions tests/unit/blob-s3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ describe("argument checks", () => {
await expect(runCommand(await createBlobProgram(), ["blob", "rm", "blob://b"])).rejects.toThrow("names no object");
await expect(runCommand(await createBlobProgram(), ["blob", "presign", "blob://b/k", "--expires-in", "0"]))
.rejects.toThrow();
await expect(runCommand(await createBlobProgram(), ["blob", "presign", "blob://b/k", "--expires-in", "601"]))
.rejects.toThrow("from 1 to 600");
await expect(runCommand(await createBlobProgram(), ["blob", "mb", "blob://b/key"])).rejects.toThrow("includes a key");
});
});
Loading