Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: respect accept header in server #153

Merged
merged 3 commits into from Mar 6, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/client.ts
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
57 changes: 57 additions & 0 deletions src/server.test.ts
Expand Up @@ -366,3 +366,60 @@ 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: {
authorization: `Bearer ${token}`,
},
})
expect(await res2.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
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