From 9fdd0320154f38712ebfe8a87733fd3f38c1b9d3 Mon Sep 17 00:00:00 2001 From: Skynet Date: Sat, 16 Sep 2023 21:58:48 +0200 Subject: [PATCH 1/3] Add support for query params on s3 rewuasts to be passed through --- src/handlers/s3.ts | 2 +- test/handlers/s3.test.ts | 24 ++++++++++++++++++++++++ test/helpers.ts | 7 ++----- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/handlers/s3.ts b/src/handlers/s3.ts index 7c3dc4c..3011627 100644 --- a/src/handlers/s3.ts +++ b/src/handlers/s3.ts @@ -74,7 +74,7 @@ export default function s3HandlerFactory({ headers.range = ctx.request.headers.range; } - const response = await aws.fetch(url, { + const response = await aws.fetch(url + (ctx.request.search || ''), { method: ctx.method || ctx.request.method, headers, }); diff --git a/test/handlers/s3.test.ts b/test/handlers/s3.test.ts index 3e878c2..1646d92 100644 --- a/test/handlers/s3.test.ts +++ b/test/handlers/s3.test.ts @@ -81,4 +81,28 @@ describe('s3', () => { await s3(ctx); expect(ctx.status).to.equal(200); }); + + + + it('List bucket forwards query params', async () => { + fetchMock.mock(`http://localhost:9000/myBucket?prefix=foo&list-type=2`, { + status: 200, + }); + + const s3 = s3Factory({ + endpoint: 'http://localhost:9000', + forcePathStyle: true, + bucket: 'myBucket', + accessKeyId: 'DERP', + secretAccessKey: 'DERP', + enableBucketOperations: true, + }); + + const ctx = helpers.getCtx(); + ctx.request.search = '?prefix=foo&list-type=2'; + ctx.params = {}; + await s3(ctx); + + expect(ctx.status).to.equal(200); + }); }); diff --git a/test/helpers.ts b/test/helpers.ts index a837ce1..27687c4 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -2,7 +2,7 @@ class Context { request: { method: string; path: string; - query: {}; + search?: string; hostname: string; host: string; protocol: string; @@ -23,7 +23,7 @@ class Context { host: 'example.com', hostname: 'example.com', protocol: 'http', - query: {}, + search: '', headers: {}, }; this.event = {}; @@ -34,9 +34,6 @@ class Context { }; this.body = undefined; this.status = 404; - - // Shortcuts directly on the context - this.query = this.request.query; } set(key: string, value: string) { From 13b5f1e26332d0e6f96f005d083cdadbdc2ce09f Mon Sep 17 00:00:00 2001 From: Skynet Date: Fri, 22 Sep 2023 22:10:10 +0200 Subject: [PATCH 2/3] Add body and header forwarding --- src/handlers/s3.ts | 7 ++---- test/handlers/s3.test.ts | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/handlers/s3.ts b/src/handlers/s3.ts index 3011627..92049fa 100644 --- a/src/handlers/s3.ts +++ b/src/handlers/s3.ts @@ -68,15 +68,12 @@ export default function s3HandlerFactory({ ? utils.resolveParams(`${resolvedEndpoint}/{file}`, ctx.params) : resolvedEndpoint; // Bucket operations - const headers: Record = {}; - - if (ctx.request.headers.range) { - headers.range = ctx.request.headers.range; - } + const headers = ctx.request.headers || {}; const response = await aws.fetch(url + (ctx.request.search || ''), { method: ctx.method || ctx.request.method, headers, + ...(ctx.request.body && { body: ctx.request.body }), }); ctx.status = response.status; diff --git a/test/handlers/s3.test.ts b/test/handlers/s3.test.ts index 1646d92..3763eaf 100644 --- a/test/handlers/s3.test.ts +++ b/test/handlers/s3.test.ts @@ -82,6 +82,56 @@ describe('s3', () => { expect(ctx.status).to.equal(200); }); + it('Request headers are forwarded', async () => { + fetchMock.mock(`http://localhost:9000/myBucket`, (req) => { + return { + status: 200, + }; + }); + const s3 = s3Factory({ + endpoint: 'http://localhost:9000', + forcePathStyle: true, + bucket: 'myBucket', + accessKeyId: 'DERP', + secretAccessKey: 'DERP', + enableBucketOperations: true, + }); + + const ctx = helpers.getCtx(); + ctx.params = {}; + ctx.request.headers['If-None-Match'] = 'blah'; + await s3(ctx); + expect(ctx.status).to.equal(200); + expect(fetchMock.lastOptions().headers['if-none-match']).to.equal('blah'); + }); + + it('Request body is forwarded', async () => { + fetchMock.mock(`http://localhost:9000/myBucket`, (req) => { + return { + status: 200, + }; + }); + const s3 = s3Factory({ + endpoint: 'http://localhost:9000', + forcePathStyle: true, + bucket: 'myBucket', + accessKeyId: 'DERP', + secretAccessKey: 'DERP', + enableBucketOperations: true, + }); + + const ctx = helpers.getCtx(); + ctx.request.method = 'PUT'; + ctx.request.body = 'hello world'; + ctx.params = {}; + ctx.request.headers['If-None-Match'] = 'blah'; + await s3(ctx); + expect(ctx.status).to.equal(200); + // aws4fetch converts body to readable stream so it is not so easy to figure out the + // actual value + expect(await fetchMock.lastOptions().body).is.not.undefined; + }); + it('List bucket forwards query params', async () => { From 6df7afd42ffa2b3c66cbe65cc2b52239ada631d4 Mon Sep 17 00:00:00 2001 From: Skynet Date: Sat, 23 Sep 2023 10:17:50 +0200 Subject: [PATCH 3/3] Full headers is breaking AWS signing --- src/handlers/s3.ts | 4 +++- test/handlers/s3.test.ts | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/handlers/s3.ts b/src/handlers/s3.ts index 92049fa..ab6c71c 100644 --- a/src/handlers/s3.ts +++ b/src/handlers/s3.ts @@ -68,7 +68,9 @@ export default function s3HandlerFactory({ ? utils.resolveParams(`${resolvedEndpoint}/{file}`, ctx.params) : resolvedEndpoint; // Bucket operations - const headers = ctx.request.headers || {}; + const headers = { + ...(ctx.request?.headers?.range && { range: ctx.request.headers.range }), + }; const response = await aws.fetch(url + (ctx.request.search || ''), { method: ctx.method || ctx.request.method, diff --git a/test/handlers/s3.test.ts b/test/handlers/s3.test.ts index 3763eaf..0cdfe6c 100644 --- a/test/handlers/s3.test.ts +++ b/test/handlers/s3.test.ts @@ -82,7 +82,7 @@ describe('s3', () => { expect(ctx.status).to.equal(200); }); - it('Request headers are forwarded', async () => { + it('Range headers are forwarded', async () => { fetchMock.mock(`http://localhost:9000/myBucket`, (req) => { return { status: 200, @@ -99,10 +99,10 @@ describe('s3', () => { const ctx = helpers.getCtx(); ctx.params = {}; - ctx.request.headers['If-None-Match'] = 'blah'; + ctx.request.headers['range'] = 'blah'; await s3(ctx); expect(ctx.status).to.equal(200); - expect(fetchMock.lastOptions().headers['if-none-match']).to.equal('blah'); + expect(fetchMock.lastOptions().headers['range']).to.equal('blah'); }); it('Request body is forwarded', async () => {