Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 3 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { encodeMetadata, Metadata, METADATA_HEADER_EXTERNAL, METADATA_HEADER_INT
import { fetchAndRetry } from './retry.ts'
import { BlobInput, Fetcher, HTTPMethod } from './types.ts'

export const SIGNED_URL_ACCEPT_HEADER = 'application/json;type=signed-url'

interface MakeStoreRequestOptions {
body?: BlobInput | null
consistency?: ConsistencyMode
Expand Down Expand Up @@ -135,7 +137,7 @@ export class Client {
}

const res = await this.fetch(url.toString(), {
headers: { ...apiHeaders, accept: `application/json;type=signed-url` },
headers: { ...apiHeaders, accept: SIGNED_URL_ACCEPT_HEADER },
method,
})

Expand Down
61 changes: 61 additions & 0 deletions src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,64 @@ test('Lists site stores', async () => {

expect(stores).toStrictEqual(['coldplay', 'phoenix'])
})

test('Returns a signed URL or the blob directly based on the request parameters', async () => {
const siteID = '9a003659-aaaa-0000-aaaa-63d3720d8621'
const token = 'some token'
const value = 'value 1'
const directory = await tmp.dir()
const server = new BlobsServer({
directory: directory.path,
token,
})

const { port } = await server.start()
const store = getStore({
edgeURL: `http://localhost:${port}`,
name: 'my-store',
token,
siteID,
})

await store.set('key-1', value)

// When reading through a legacy API endpoint, we should get a signed URL.
const res1 = await fetch(`http://localhost:${port}/api/v1/sites/${siteID}/blobs/key-1?context=site:my-store`, {
headers: {
authorization: `Bearer ${token}`,
},
})
const { url: url1 } = await res1.json()
const data1 = await fetch(url1)

expect(await data1.text()).toBe(value)

// When reading through a new API endpoint, we should get the blob data by
// default.
const res2 = await fetch(`http://localhost:${port}/api/v1/blobs/${siteID}/site:my-store/key-1`, {
headers: {
accept: 'application/json;type=signed-url',
authorization: `Bearer ${token}`,
},
})
const { url: url2 } = await res2.json()
const data2 = await fetch(url2)

expect(await data2.text()).toBe(value)

// When reading through a new API endpoint and requesting a signed URL, we
// should get one.
const res3 = await fetch(`http://localhost:${port}/api/v1/blobs/${siteID}/site:my-store/key-1`, {
headers: {
accept: 'application/json;type=signed-url',
authorization: `Bearer ${token}`,
},
})
const { url: url3 } = await res3.json()
const data3 = await fetch(url3)

expect(await data3.text()).toBe(value)

await server.stop()
await fs.rm(directory.path, { force: true, recursive: true })
})
10 changes: 5 additions & 5 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import stream from 'node:stream'
import { promisify } from 'node:util'

import { ListResponse } from './backend/list.ts'
import { SIGNED_URL_ACCEPT_HEADER } from './client.ts'
import { decodeMetadata, encodeMetadata, METADATA_HEADER_INTERNAL } from './metadata.ts'
import { HTTPMethod } from './types.ts'
import { isNodeError, Logger } from './util.ts'
Expand Down Expand Up @@ -137,11 +138,11 @@ export class BlobsServer {
const apiMatch = this.parseAPIRequest(req)
const url = apiMatch?.url ?? new URL(req.url ?? '', this.address)

if (apiMatch?.key) {
if (apiMatch?.key && apiMatch?.useSignedURL) {
return this.sendResponse(req, res, 200, JSON.stringify({ url: apiMatch.url.toString() }))
}

const { dataPath, key, metadataPath, rootPath } = this.getLocalPaths(url)
const { dataPath, key, metadataPath, rootPath } = this.getLocalPaths(apiMatch?.url ?? url)

// If there's no root path, the request is invalid.
if (!rootPath) {
Expand Down Expand Up @@ -404,7 +405,7 @@ export class BlobsServer {
siteID,
storeName,
url,
useSignedURL: req.headers.accept === 'application/json;type=signed-url',
useSignedURL: req.headers.accept === SIGNED_URL_ACCEPT_HEADER,
}
}

Expand Down Expand Up @@ -481,9 +482,8 @@ export class BlobsServer {
}

const { authorization = '' } = req.headers
const parts = authorization.split(' ')

if (parts.length === 2 || (parts[0].toLowerCase() === 'bearer' && parts[1] === this.token)) {
if (authorization.toLowerCase().startsWith('bearer ') && authorization.slice('bearer '.length) === this.token) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous implementation didn't handle tokens with a space.

return true
}

Expand Down