Skip to content

Commit 5b1b7d6

Browse files
authored
Make realm dynamic (#39)
* Make realm dynamic * 100% code coverage * validate can return a custom realm
1 parent 7605ae4 commit 5b1b7d6

File tree

3 files changed

+200
-27
lines changed

3 files changed

+200
-27
lines changed

README.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const authenticate = {realm: 'Westeros'}
5555
fastify.register(require('fastify-basic-auth'), { validate, authenticate })
5656
async function validate (username, password, req, reply) {
5757
if (username !== 'Tyrion' || password !== 'wine') {
58-
return new Error('Winter is coming')
58+
throw new Error('Winter is coming')
5959
}
6060
}
6161

@@ -79,7 +79,7 @@ fastify.register(require('fastify-auth'))
7979
fastify.register(require('fastify-basic-auth'), { validate, authenticate })
8080
async function validate (username, password, req, reply) {
8181
if (username !== 'Tyrion' || password !== 'wine') {
82-
return new Error('Winter is coming')
82+
throw new Error('Winter is coming')
8383
}
8484
}
8585

@@ -132,7 +132,33 @@ the `validate` function may return a promise, resolving for valid
132132
requests and rejecting for invalid. This can also be achieved using
133133
an `async/await` function, and throwing for invalid requests.
134134

135-
See code above for examples.
135+
It is also possible to override set the `realm` dynamically by returning it
136+
as the first argument.
137+
138+
```js
139+
const fastify = require('fastify')()
140+
const authenticate = {realm: 'Westeros'}
141+
fastify.register(require('fastify-basic-auth'), { validate, authenticate })
142+
async function validate (username, password, req, reply) {
143+
if (username !== 'Tyrion' || password !== 'Wine') {
144+
throw new Error('Winter is coming')
145+
}
146+
147+
// custom realm
148+
return 'Lannister'
149+
}
150+
151+
fastify.after(() => {
152+
fastify.route({
153+
method: 'GET',
154+
url: '/',
155+
onRequest: fastify.basicAuth,
156+
handler: async (req, reply) => {
157+
return { hello: 'world' }
158+
}
159+
})
160+
})
161+
```
136162

137163
### `authenticate` <Boolean|Object> (optional, default: false)
138164

@@ -166,7 +192,6 @@ fastify.register(require('fastify-basic-auth'), {
166192
})
167193
```
168194

169-
170195
## License
171196

172197
Licensed under [MIT](./LICENSE).

index.js

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,15 @@ const fp = require('fastify-plugin')
44
const auth = require('basic-auth')
55
const { Unauthorized } = require('http-errors')
66

7-
function basicPlugin (fastify, opts, next) {
7+
async function basicPlugin (fastify, opts) {
88
if (typeof opts.validate !== 'function') {
9-
return next(new Error('Basic Auth: Missing validate function'))
9+
throw new Error('Basic Auth: Missing validate function')
1010
}
11-
const authenticateHeader = getAuthenticateHeader(opts.authenticate, next)
11+
const authenticateHeader = getAuthenticateHeader(opts.authenticate)
1212
const validate = opts.validate.bind(fastify)
1313
fastify.decorate('basicAuth', basicAuth)
1414

15-
next()
16-
1715
function basicAuth (req, reply, next) {
18-
if (authenticateHeader) {
19-
reply.header(authenticateHeader.key, authenticateHeader.value)
20-
}
2116
const credentials = auth(req)
2217
if (credentials == null) {
2318
done(new Unauthorized('Missing or bad formatted authorization header'))
@@ -28,39 +23,51 @@ function basicPlugin (fastify, opts, next) {
2823
}
2924
}
3025

31-
function done (err) {
32-
if (err !== undefined) {
26+
function done (err, realm) {
27+
// TODO remove in the next major
28+
if (typeof err === 'string') {
29+
realm = err
30+
err = undefined
31+
}
32+
if (err) {
3333
// We set the status code to be 401 if it is not set
3434
if (!err.statusCode) {
3535
err.statusCode = 401
3636
}
3737
next(err)
3838
} else {
39+
const header = realm ? formatRealm(realm) : authenticateHeader
40+
reply.header('WWW-Authenticate', header)
3941
next()
4042
}
4143
}
4244
}
4345
}
4446

45-
function getAuthenticateHeader (authenticate, next) {
47+
function getAuthenticateHeader (authenticate) {
4648
if (!authenticate) return false
4749
if (authenticate === true) {
48-
return {
49-
key: 'WWW-Authenticate',
50-
value: 'Basic'
51-
}
50+
return 'Basic'
5251
}
5352
if (typeof authenticate === 'object') {
54-
const realm = (authenticate.realm && typeof authenticate.realm === 'string')
55-
? authenticate.realm
56-
: ''
57-
return {
58-
key: 'WWW-Authenticate',
59-
value: 'Basic' + (realm ? ` realm="${realm}"` : '')
53+
const realm = formatRealm(authenticate.realm)
54+
if (realm) {
55+
return realm
6056
}
6157
}
6258

63-
next(new Error('Basic Auth: Invalid authenticate option'))
59+
throw new Error('Basic Auth: Invalid authenticate option')
60+
}
61+
62+
function formatRealm (realm) {
63+
switch (typeof realm) {
64+
case 'undefined':
65+
return 'Basic'
66+
case 'boolean':
67+
return 'Basic'
68+
case 'string':
69+
return `Basic realm="${realm}"`
70+
}
6471
}
6572

6673
module.exports = fp(basicPlugin, {

test.js

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ test('WWW-Authenticate Realm (authenticate: {realm: "example"})', t => {
236236
authorization: basicAuthHeader('user', 'pwd')
237237
}
238238
}, (err, res) => {
239-
t.equal(res.headers['www-authenticate'], 'Basic realm="example"')
240239
t.error(err)
240+
t.equal(res.headers['www-authenticate'], 'Basic realm="example"')
241241
t.equal(res.statusCode, 200)
242242
})
243243
})
@@ -571,6 +571,26 @@ test('Invalid options (authenticate)', t => {
571571
})
572572
})
573573

574+
test('Invalid options (realm is a number)', t => {
575+
t.plan(1)
576+
577+
const fastify = Fastify()
578+
fastify
579+
.register(basicAuth, { validate, authenticate: { realm: 42 } })
580+
581+
function validate (username, password, req, res, done) {
582+
if (username === 'user' && password === 'pwd') {
583+
done()
584+
} else {
585+
done(new Error('Unauthorized'))
586+
}
587+
}
588+
589+
fastify.ready(function (err) {
590+
t.equal(err.message, 'Basic Auth: Invalid authenticate option')
591+
})
592+
})
593+
574594
test('Invalid options (authenticate realm)', t => {
575595
t.plan(3)
576596

@@ -604,8 +624,129 @@ test('Invalid options (authenticate realm)', t => {
604624
authorization: basicAuthHeader('user', 'pwd')
605625
}
606626
}, (err, res) => {
627+
t.error(err)
607628
t.equal(res.headers['www-authenticate'], 'Basic')
629+
t.equal(res.statusCode, 200)
630+
})
631+
})
632+
633+
test('Invalid options (authenticate realm = undefined)', t => {
634+
t.plan(3)
635+
636+
const fastify = Fastify()
637+
fastify
638+
.register(basicAuth, { validate, authenticate: { realm: undefined } })
639+
640+
function validate (username, password, req, res, done) {
641+
if (username === 'user' && password === 'pwd') {
642+
done()
643+
} else {
644+
done(new Error('Unauthorized'))
645+
}
646+
}
647+
648+
fastify.after(() => {
649+
fastify.route({
650+
method: 'GET',
651+
url: '/',
652+
preHandler: fastify.basicAuth,
653+
handler: (req, reply) => {
654+
reply.send({ hello: 'world' })
655+
}
656+
})
657+
})
658+
659+
fastify.inject({
660+
url: '/',
661+
method: 'GET',
662+
headers: {
663+
authorization: basicAuthHeader('user', 'pwd')
664+
}
665+
}, (err, res) => {
666+
t.error(err)
667+
t.equal(res.headers['www-authenticate'], 'Basic')
668+
t.equal(res.statusCode, 200)
669+
})
670+
})
671+
672+
test('WWW-Authenticate Realm dynamic realm', t => {
673+
t.plan(3)
674+
675+
const fastify = Fastify()
676+
const authenticate = {
677+
realm: true
678+
}
679+
fastify.register(basicAuth, { validate, authenticate })
680+
681+
function validate (username, password, req, res, done) {
682+
if (username === 'user' && password === 'pwd') {
683+
done(null, 'root')
684+
} else {
685+
done(new Error('Unauthorized'))
686+
}
687+
}
688+
689+
fastify.after(() => {
690+
fastify.route({
691+
method: 'GET',
692+
url: '/',
693+
preHandler: fastify.basicAuth,
694+
handler: (req, reply) => {
695+
reply.send({ hello: 'world' })
696+
}
697+
})
698+
})
699+
700+
fastify.inject({
701+
url: '/',
702+
method: 'GET',
703+
headers: {
704+
authorization: basicAuthHeader('user', 'pwd')
705+
}
706+
}, (err, res) => {
707+
t.error(err)
708+
t.equal(res.headers['www-authenticate'], 'Basic realm="root"')
709+
t.equal(res.statusCode, 200)
710+
})
711+
})
712+
713+
test('WWW-Authenticate Realm dynamic realm promise', t => {
714+
t.plan(3)
715+
716+
const fastify = Fastify()
717+
const authenticate = {
718+
realm: true
719+
}
720+
fastify.register(basicAuth, { validate, authenticate })
721+
722+
function validate (username, password, req, res) {
723+
if (username === 'user' && password === 'pwd') {
724+
return Promise.resolve('root')
725+
} else {
726+
return Promise.reject(new Error('Unauthorized'))
727+
}
728+
}
729+
730+
fastify.after(() => {
731+
fastify.route({
732+
method: 'GET',
733+
url: '/',
734+
preHandler: fastify.basicAuth,
735+
handler: (req, reply) => {
736+
reply.send({ hello: 'world' })
737+
}
738+
})
739+
})
740+
741+
fastify.inject({
742+
url: '/',
743+
method: 'GET',
744+
headers: {
745+
authorization: basicAuthHeader('user', 'pwd')
746+
}
747+
}, (err, res) => {
608748
t.error(err)
749+
t.equal(res.headers['www-authenticate'], 'Basic realm="root"')
609750
t.equal(res.statusCode, 200)
610751
})
611752
})

0 commit comments

Comments
 (0)