diff --git a/README.md b/README.md index ccd22f2..3b28afd 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,18 @@ fastify.register(require('@fastify/basic-auth'), { }) ``` +The `authenticate` object can also have an optional `header` key allowing to customise the name of the header used instead of the default `WWW-Authenticate`: + +```js +fastify.register(require('@fastify/basic-auth'), { + validate, + authenticate: { + header: 'Proxy-Authenticate' // Proxy-Authenticate: Basic + } +}) +``` + + ### `header` String (optional) The `header` option specifies the header name to get credentials from for validation. diff --git a/index.js b/index.js index a4da647..9a37da2 100644 --- a/index.js +++ b/index.js @@ -132,7 +132,7 @@ async function fastifyBasicAuth (fastify, opts) { if (err.statusCode === 401) { const header = authenticateHeader(req) if (header) { - reply.header('WWW-Authenticate', header) + reply.header(...header) } } next(err) @@ -144,28 +144,30 @@ async function fastifyBasicAuth (fastify, opts) { } function getAuthenticateHeader (authenticate, useUtf8) { + const defaultHeaderName = 'WWW-Authenticate' if (!authenticate) return () => false if (authenticate === true) { return useUtf8 - ? () => 'Basic charset="UTF-8"' - : () => 'Basic' + ? () => [defaultHeaderName, 'Basic charset="UTF-8"'] + : () => [defaultHeaderName, 'Basic'] } if (typeof authenticate === 'object') { const realm = authenticate.realm + const headerName = authenticate.header || defaultHeaderName switch (typeof realm) { case 'undefined': case 'boolean': return useUtf8 - ? () => 'Basic charset="UTF-8"' - : () => 'Basic' + ? () => [headerName, 'Basic charset="UTF-8"'] + : () => [headerName, 'Basic'] case 'string': return useUtf8 - ? () => `Basic realm="${realm}", charset="UTF-8"` - : () => `Basic realm="${realm}"` + ? () => [headerName, `Basic realm="${realm}", charset="UTF-8"`] + : () => [headerName, `Basic realm="${realm}"`] case 'function': return useUtf8 - ? (req) => `Basic realm="${realm(req)}", charset="UTF-8"` - : (req) => `Basic realm="${realm(req)}"` + ? (req) => [headerName, `Basic realm="${realm(req)}", charset="UTF-8"`] + : (req) => [headerName, `Basic realm="${realm(req)}"`] } } diff --git a/test/index.test.js b/test/index.test.js index 7a2b59b..3f35aad 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -589,6 +589,102 @@ test('WWW-Authenticate Realm (authenticate: {realm: "example"}, utf8: true)', as t.assert.strictEqual(res2.statusCode, 200) }) +test('WWW-Authenticate Custom Header (authenticate: {realm: "example", header: "x-custom-authenticate" }, utf8: false)', async t => { + t.plan(8) + + const fastify = Fastify() + const authenticate = { realm: 'example', header: 'x-custom-authenticate' } + fastify.register(basicAuth, { validate, authenticate, utf8: false }) + + 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['x-custom-authenticate'], 'Basic realm="example"') + t.assert.strictEqual(res1.headers['www-authenticate'], undefined) + t.assert.strictEqual(res1.statusCode, 401) + + const res2 = await fastify.inject({ + url: '/', + method: 'GET', + headers: { + authorization: basicAuthHeader('user', 'pwd') + } + }) + t.assert.ok(res2.body) + t.assert.strictEqual(res2.headers['x-custom-authenticate'], undefined) + t.assert.strictEqual(res2.headers['www-authenticate'], undefined) + t.assert.strictEqual(res2.statusCode, 200) +}) + +test('WWW-Authenticate Custom Header (authenticate: {realm: "example", header: "x-custom-authenticate" }, utf8: true)', async t => { + t.plan(8) + + const fastify = Fastify() + const authenticate = { realm: 'example', header: 'x-custom-authenticate' } + fastify.register(basicAuth, { validate, authenticate, utf8: true }) + + 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['x-custom-authenticate'], 'Basic realm="example", charset="UTF-8"') + t.assert.strictEqual(res1.headers['www-authenticate'], undefined) + t.assert.strictEqual(res1.statusCode, 401) + + const res2 = await fastify.inject({ + url: '/', + method: 'GET', + headers: { + authorization: basicAuthHeader('user', 'pwd') + } + }) + t.assert.ok(res2.body) + t.assert.strictEqual(res2.headers['x-custom-authenticate'], undefined) + t.assert.strictEqual(res2.headers['www-authenticate'], undefined) + t.assert.strictEqual(res2.statusCode, 200) +}) + test('Header option specified', async t => { t.plan(2) diff --git a/types/index.d.ts b/types/index.d.ts index b9d183e..f1911df 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) }; + authenticate?: boolean | { realm?: string | ((req: FastifyRequest) => string); header?: string }; header?: string; strictCredentials?: boolean | undefined; utf8?: boolean | undefined; diff --git a/types/index.test-d.ts b/types/index.test-d.ts index a04440c..aed580a 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -69,6 +69,12 @@ app.register(fastifyBasicAuth, { } }) +// authenticate with custom header +app.register(fastifyBasicAuth, { + validate: () => {}, + authenticate: { header: 'x-custom-authenticate' } +}) + app.register(fastifyBasicAuth, { validate: () => {}, strictCredentials: true