From 7dcb7797670f5a8c287041625835bf0c9e3618ea Mon Sep 17 00:00:00 2001 From: pqnet <119850+pqnet@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:49:39 +0200 Subject: [PATCH 01/12] Support customization of error code Signed-off-by: pqnet <119850+pqnet@users.noreply.github.com> --- index.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index e6ff7c2..28eef52 100644 --- a/index.js +++ b/index.js @@ -3,12 +3,6 @@ const fp = require('fastify-plugin') const createError = require('@fastify/error') -const MissingOrBadAuthorizationHeader = createError( - 'FST_BASIC_AUTH_MISSING_OR_BAD_AUTHORIZATION_HEADER', - 'Missing or bad formatted authorization header', - 401 -) - /** * HTTP provides a simple challenge-response authentication framework * that can be used by a server to challenge a client request and by a @@ -75,6 +69,13 @@ async function fastifyBasicAuth (fastify, opts) { const charset = useUtf8 ? 'utf-8' : 'ascii' const authenticateHeader = getAuthenticateHeaders(opts.authenticate, useUtf8) const header = opts.header?.toLowerCase() || 'authorization' + const errorResponseCode = opts.authenticate?.errorResponseCode || 401 + + const MissingOrBadAuthorizationHeader = createError( + 'FST_BASIC_AUTH_MISSING_OR_BAD_AUTHORIZATION_HEADER', + 'Missing or bad formatted authorization header', + errorResponseCode + ) const credentialsRE = strictCredentials ? credentialsStrictRE @@ -124,12 +125,12 @@ async function fastifyBasicAuth (fastify, opts) { function done (err) { if (err !== undefined) { - // We set the status code to be 401 if it is not set + // We set the status code to be `errorResponseCode` (normally 401) if it is not set if (!err.statusCode) { - err.statusCode = 401 + err.statusCode = errorResponseCode } - if (err.statusCode === 401) { + if (err.statusCode === errorResponseCode) { const header = authenticateHeader(req) if (header) { reply.header(header[0], header[1]) From 84ef08789eb6795cf1954bc3d0db0649da909221 Mon Sep 17 00:00:00 2001 From: pqnet <119850+pqnet@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:53:17 +0200 Subject: [PATCH 02/12] Support customization of error code (update types) Signed-off-by: pqnet <119850+pqnet@users.noreply.github.com> --- types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index f1911df..6e444e0 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -28,7 +28,7 @@ declare namespace fastifyBasicAuth { reply: FastifyReply, done: (err?: Error) => void ): void | Promise; - authenticate?: boolean | { realm?: string | ((req: FastifyRequest) => string); header?: string }; + authenticate?: boolean | { realm?: string | ((req: FastifyRequest) => string); header?: string; errorResponseCode?: number }; header?: string; strictCredentials?: boolean | undefined; utf8?: boolean | undefined; From 0560da86e63e8f434a87be28c726ccc00cdea98b Mon Sep 17 00:00:00 2001 From: pqnet <119850+pqnet@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:55:02 +0200 Subject: [PATCH 03/12] Support customization of error code (update tests for types) Signed-off-by: pqnet <119850+pqnet@users.noreply.github.com> --- types/index.test-d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/index.test-d.ts b/types/index.test-d.ts index aed580a..847ab23 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -69,10 +69,10 @@ app.register(fastifyBasicAuth, { } }) -// authenticate with custom header +// authenticate with custom header and status code app.register(fastifyBasicAuth, { validate: () => {}, - authenticate: { header: 'x-custom-authenticate' } + authenticate: { header: 'x-custom-authenticate', errorResponseCode: 400 } }) app.register(fastifyBasicAuth, { From 7fbb66c9750b8afe23329b668d47d0a27a9e6ee6 Mon Sep 17 00:00:00 2001 From: pqnet <119850+pqnet@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:04:41 +0200 Subject: [PATCH 04/12] Support customization of error code (update tests) Signed-off-by: pqnet <119850+pqnet@users.noreply.github.com> --- test/index.test.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/index.test.js b/test/index.test.js index 3f35aad..aebbe3f 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -685,6 +685,68 @@ test('WWW-Authenticate Custom Header (authenticate: {realm: "example", header: " t.assert.strictEqual(res2.statusCode, 200) }) +test('Proxy authentication (header: Proxy-Authorization, authenticate: {realm: "example", header: "Proxy-Authenticate", errorResponseCode: 407 }, utf8: true)', async t => { + t.plan(12) + + const fastify = Fastify() + const authenticate = { realm: 'example', header: 'Proxy-Authenticate', errorResponseCode: 407 } + fastify.register(basicAuth, { validate, authenticate, utf8: true, header: 'Proxy-Authorization' }) + + function validate (username, password, _req, _res, done) { + if (username === 'user' && password === 'pwd') { + done() + } else { + done(new Error('Unauthorized')) + } + } + + fastify.after(() => { + fastify.route({ + method: 'GET', + url: '/', + preHandler: fastify.basicAuth, + handler: (_req, reply) => { + reply.send({ hello: 'world' }) + } + }) + }) + + const res1 = await fastify.inject({ + url: '/', + method: 'GET' + }) + t.assert.ok(res1.body) + t.assert.strictEqual(res1.headers['proxy-authenticate'], 'Basic realm="example", charset="UTF-8"') + t.assert.strictEqual(res1.headers['www-authenticate'], undefined) + t.assert.strictEqual(res1.statusCode, 407) + + const res2 = await fastify.inject({ + url: '/', + method: 'GET', + headers: { + authorization: basicAuthHeader('user', 'pwd') + } + }) + + t.assert.ok(res2.body) + t.assert.strictEqual(res2.headers['proxy-authenticate'], 'Basic realm="example", charset="UTF-8"') + t.assert.strictEqual(res2.headers['www-authenticate'], undefined) + t.assert.strictEqual(res2.statusCode, 407) + + const res3 = await fastify.inject({ + url: '/', + method: 'GET', + headers: { + 'proxy-authorization': basicAuthHeader('user', 'pwd') + } + }) + + t.assert.ok(res3.body) + t.assert.strictEqual(res3.headers['proxy-authenticate'], undefined) + t.assert.strictEqual(res3.headers['www-authenticate'], undefined) + t.assert.strictEqual(res3.statusCode, 200) +}) + test('Header option specified', async t => { t.plan(2) From 0cb6a7b6485aaa9e9eb132b77283f297d11e7d98 Mon Sep 17 00:00:00 2001 From: Leonardo <119850+pqnet@users.noreply.github.com> Date: Mon, 21 Apr 2025 11:34:19 +0200 Subject: [PATCH 05/12] Support customization of error code (update docs) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12f5907..54c5ff2 100644 --- a/README.md +++ b/README.md @@ -205,12 +205,14 @@ fastify.register(require('@fastify/basic-auth'), { }) ``` -The `authenticate` object can include an optional `header` key to customize the header name, replacing the default `WWW-Authenticate`: +The `authenticate` object can include an optional `errorResponseCode` and `header` keys to customize the HTTP status code and the header name for authenticate responses, replacing the default `401` and `WWW-Authenticate`. This allows for example to implement proxy authentication if your application forwards the request to another server: ```js fastify.register(require('@fastify/basic-auth'), { validate, + header: 'Proxy-Authorization', authenticate: { + errorResponseCode: 407, // 407 Proxy Authentication Required header: 'Proxy-Authenticate' // Proxy-Authenticate: Basic } }) From f37b27294ccd82bfb31c4f30296bcb2a5dcbbbe4 Mon Sep 17 00:00:00 2001 From: Leonardo <119850+pqnet@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:19:11 +0200 Subject: [PATCH 06/12] Support "proxyMode" --- index.js | 10 +++++----- test/index.test.js | 6 +++--- types/index.d.ts | 3 ++- types/index.test-d.ts | 11 +++++++++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 28eef52..da3d390 100644 --- a/index.js +++ b/index.js @@ -67,9 +67,9 @@ async function fastifyBasicAuth (fastify, opts) { const strictCredentials = opts.strictCredentials ?? true const useUtf8 = opts.utf8 ?? true const charset = useUtf8 ? 'utf-8' : 'ascii' - const authenticateHeader = getAuthenticateHeaders(opts.authenticate, useUtf8) - const header = opts.header?.toLowerCase() || 'authorization' - const errorResponseCode = opts.authenticate?.errorResponseCode || 401 + const authenticateHeader = getAuthenticateHeaders(opts.authenticate, useUtf8, opts.proxyMode) + const header = opts.header?.toLowerCase() || (opts.proxyMode ? 'proxy-authorization' : 'authorization') + const errorResponseCode = opts.proxyMode ? 407 : 401 const MissingOrBadAuthorizationHeader = createError( 'FST_BASIC_AUTH_MISSING_OR_BAD_AUTHORIZATION_HEADER', @@ -144,8 +144,8 @@ async function fastifyBasicAuth (fastify, opts) { } } -function getAuthenticateHeaders (authenticate, useUtf8) { - const defaultHeaderName = 'WWW-Authenticate' +function getAuthenticateHeaders (authenticate, useUtf8, proxyMode) { + const defaultHeaderName = proxyMode ? 'Proxy-Authenticate' : 'WWW-Authenticate' if (!authenticate) return () => false if (authenticate === true) { return useUtf8 diff --git a/test/index.test.js b/test/index.test.js index aebbe3f..fb883a8 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -685,12 +685,12 @@ test('WWW-Authenticate Custom Header (authenticate: {realm: "example", header: " t.assert.strictEqual(res2.statusCode, 200) }) -test('Proxy authentication (header: Proxy-Authorization, authenticate: {realm: "example", header: "Proxy-Authenticate", errorResponseCode: 407 }, utf8: true)', async t => { +test('Proxy authentication (proxyMode: true, authenticate: true, utf8: true)', async t => { t.plan(12) const fastify = Fastify() - const authenticate = { realm: 'example', header: 'Proxy-Authenticate', errorResponseCode: 407 } - fastify.register(basicAuth, { validate, authenticate, utf8: true, header: 'Proxy-Authorization' }) + const authenticate = { realm: 'example' } + fastify.register(basicAuth, { validate, authenticate, utf8: true, proxyMode: true }) function validate (username, password, _req, _res, done) { if (username === 'user' && password === 'pwd') { diff --git a/types/index.d.ts b/types/index.d.ts index 6e444e0..f3f7fc6 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -28,7 +28,8 @@ declare namespace fastifyBasicAuth { reply: FastifyReply, done: (err?: Error) => void ): void | Promise; - authenticate?: boolean | { realm?: string | ((req: FastifyRequest) => string); header?: string; errorResponseCode?: number }; + authenticate?: boolean | { realm?: string | ((req: FastifyRequest) => string); header?: string; }; + proxyMode?: boolean; header?: string; strictCredentials?: boolean | undefined; utf8?: boolean | undefined; diff --git a/types/index.test-d.ts b/types/index.test-d.ts index 847ab23..2eb4c79 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -69,10 +69,17 @@ app.register(fastifyBasicAuth, { } }) -// authenticate with custom header and status code +// authenticate with custom header app.register(fastifyBasicAuth, { validate: () => {}, - authenticate: { header: 'x-custom-authenticate', errorResponseCode: 400 } + authenticate: { header: 'x-custom-authenticate' } +}) + +// authenticate in proxy mode +app.register(fastifyBasicAuth, { + validate: () => {}, + proxyMode: true, + authenticate: true, }) app.register(fastifyBasicAuth, { From 1911f56a05c050d719b9d41fc80fc56b6fdbb5c5 Mon Sep 17 00:00:00 2001 From: Leonardo <119850+pqnet@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:22:06 +0200 Subject: [PATCH 07/12] Revert "Support customization of error code (update docs)" This reverts commit 0cb6a7b6485aaa9e9eb132b77283f297d11e7d98. --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 54c5ff2..12f5907 100644 --- a/README.md +++ b/README.md @@ -205,14 +205,12 @@ fastify.register(require('@fastify/basic-auth'), { }) ``` -The `authenticate` object can include an optional `errorResponseCode` and `header` keys to customize the HTTP status code and the header name for authenticate responses, replacing the default `401` and `WWW-Authenticate`. This allows for example to implement proxy authentication if your application forwards the request to another server: +The `authenticate` object can include an optional `header` key to customize the header name, replacing the default `WWW-Authenticate`: ```js fastify.register(require('@fastify/basic-auth'), { validate, - header: 'Proxy-Authorization', authenticate: { - errorResponseCode: 407, // 407 Proxy Authentication Required header: 'Proxy-Authenticate' // Proxy-Authenticate: Basic } }) From ecc978b62a452a617a83e74bdb7a4267ce9bf730 Mon Sep 17 00:00:00 2001 From: Leonardo <119850+pqnet@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:36:00 +0200 Subject: [PATCH 08/12] Update docs for proxyMode --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12f5907..5a45007 100644 --- a/README.md +++ b/README.md @@ -170,12 +170,21 @@ This can trigger client-side authentication interfaces, such as the browser auth Setting `authenticate` to `true` adds the header `WWW-Authenticate: Basic`. When `false`, no header is added (default). +When `proxyMode` is `true` it will the [`Proxy-Authenticate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authenticate) header instead + ```js fastify.register(require('@fastify/basic-auth'), { validate, authenticate: true // WWW-Authenticate: Basic }) +fastify.register(require('@fastify/basic-auth'), { + validate, + proxymode: true, + authenticate: true // Proxy-Authenticate: Basic +}) + + fastify.register(require('@fastify/basic-auth'), { validate, authenticate: false // no authenticate header, same as omitting authenticate option @@ -216,10 +225,17 @@ fastify.register(require('@fastify/basic-auth'), { }) ``` +### `proxyMode` Boolean (optional, default: false) + +Setting the `proxyMode` to `true` will make the plugin implement [HTTP proxy authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication#proxy_authentication), rather than resource authentication. In other words, the plugin will: + +- read credentials from the `Proxy-Authorization` header rather than `Authorization` +- use `407` response status code instead of `401` to signal missing or invalid credentials +- use the `Proxy-Authenticate` header rather than `WWW-Authenticate` if the `authenticate` option is set ### `header` String (optional) -The `header` option specifies the header name to get credentials from for validation. +The `header` option specifies the header name to get credentials from for validation. If not specified it defaults to `Authorization` or `Proxy-Authorization` (according to the value of `proxyMode` option) ```js fastify.register(require('@fastify/basic-auth'), { From 7456bbf8c89cce8e181c333594e741d44d1bc7fa Mon Sep 17 00:00:00 2001 From: pqnet <119850+pqnet@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:50:36 +0200 Subject: [PATCH 09/12] Fix typos, formatting, and copy paste errors Signed-off-by: pqnet <119850+pqnet@users.noreply.github.com> --- README.md | 3 +-- test/index.test.js | 2 +- types/index.d.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5a45007..a46e07a 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ This can trigger client-side authentication interfaces, such as the browser auth Setting `authenticate` to `true` adds the header `WWW-Authenticate: Basic`. When `false`, no header is added (default). -When `proxyMode` is `true` it will the [`Proxy-Authenticate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authenticate) header instead +When `proxyMode` is `true` it will use the [`Proxy-Authenticate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authenticate) header instead ```js fastify.register(require('@fastify/basic-auth'), { @@ -184,7 +184,6 @@ fastify.register(require('@fastify/basic-auth'), { authenticate: true // Proxy-Authenticate: Basic }) - fastify.register(require('@fastify/basic-auth'), { validate, authenticate: false // no authenticate header, same as omitting authenticate option diff --git a/test/index.test.js b/test/index.test.js index fb883a8..47b77b4 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -685,7 +685,7 @@ test('WWW-Authenticate Custom Header (authenticate: {realm: "example", header: " t.assert.strictEqual(res2.statusCode, 200) }) -test('Proxy authentication (proxyMode: true, authenticate: true, utf8: true)', async t => { +test('Proxy authentication (proxyMode: true, authenticate: { realm: 'example' }, utf8: true)', async t => { t.plan(12) const fastify = Fastify() diff --git a/types/index.d.ts b/types/index.d.ts index f3f7fc6..135c0f8 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -28,7 +28,7 @@ declare namespace fastifyBasicAuth { reply: FastifyReply, done: (err?: Error) => void ): void | Promise; - authenticate?: boolean | { realm?: string | ((req: FastifyRequest) => string); header?: string; }; + authenticate?: boolean | { realm?: string | ((req: FastifyRequest) => string); header?: string }; proxyMode?: boolean; header?: string; strictCredentials?: boolean | undefined; From 46769a02086be3c593d722bf125796a9f85d1889 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 23 Apr 2025 16:38:11 +0100 Subject: [PATCH 10/12] Update README.md Signed-off-by: Frazer Smith --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a46e07a..f50c6d6 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ This can trigger client-side authentication interfaces, such as the browser auth Setting `authenticate` to `true` adds the header `WWW-Authenticate: Basic`. When `false`, no header is added (default). -When `proxyMode` is `true` it will use the [`Proxy-Authenticate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authenticate) header instead +When `proxyMode` is `true` it will use the [`Proxy-Authenticate`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authenticate) header instead. ```js fastify.register(require('@fastify/basic-auth'), { From 25d37bf12023ca8c6a0ffd6c1f39a95f71772de7 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Wed, 23 Apr 2025 16:39:27 +0100 Subject: [PATCH 11/12] Apply suggestions from code review Signed-off-by: Frazer Smith --- test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.test.js b/test/index.test.js index 47b77b4..e8987af 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -685,7 +685,7 @@ test('WWW-Authenticate Custom Header (authenticate: {realm: "example", header: " t.assert.strictEqual(res2.statusCode, 200) }) -test('Proxy authentication (proxyMode: true, authenticate: { realm: 'example' }, utf8: true)', async t => { +test("Proxy authentication (proxyMode: true, authenticate: { realm: 'example' }, utf8: true)", async t => { t.plan(12) const fastify = Fastify() From ec0c27a3fdcabfcbeba0e759e192f635b3692e35 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Thu, 24 Apr 2025 14:47:36 +0100 Subject: [PATCH 12/12] Update README.md Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> Signed-off-by: Frazer Smith --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f50c6d6..9bff716 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ fastify.register(require('@fastify/basic-auth'), { fastify.register(require('@fastify/basic-auth'), { validate, - proxymode: true, + proxyMode: true, authenticate: true // Proxy-Authenticate: Basic })