-
-
Notifications
You must be signed in to change notification settings - Fork 25
/
index.js
180 lines (156 loc) · 5.26 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
'use strict'
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
* client to provide authentication information. It uses a case-
* insensitive token as a means to identify the authentication scheme,
* followed by additional information necessary for achieving
* authentication via that scheme.
*
* @see https://datatracker.ietf.org/doc/html/rfc7235#section-2.1
*
* The scheme name is "Basic".
* @see https://datatracker.ietf.org/doc/html/rfc7617#section-2
*/
const authScheme = '(?:basic)'
/**
* The BWS rule is used where the grammar allows optional whitespace
* only for historical reasons. A sender MUST NOT generate BWS in
* messages. A recipient MUST parse for such bad whitespace and remove
* it before interpreting the protocol element.
*
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.3
*/
const BWS = '[ \t]'
/**
* The token68 syntax allows the 66 unreserved URI characters
* ([RFC3986]), plus a few others, so that it can hold a base64,
* base64url (URL and filename safe alphabet), base32, or base16 (hex)
* encoding, with or without padding, but excluding whitespace
* ([RFC4648]).
* @see https://datatracker.ietf.org/doc/html/rfc7235#section-2.1
*/
const token68 = '([\\w.~+/-]+=*)'
/**
* @see https://datatracker.ietf.org/doc/html/rfc7235#appendix-C
*/
const credentialsStrictRE = new RegExp(`^${authScheme} ${token68}$`, 'i')
const credentialsLaxRE = new RegExp(`^${BWS}*${authScheme}${BWS}+${token68}${BWS}*$`, 'i')
/**
* @see https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
*/
// eslint-disable-next-line no-control-regex
const controlRE = /[\x00-\x1F\x7F]/
/**
* RegExp for basic auth user/pass
*
* user-pass = userid ":" password
* userid = *<TEXT excluding ":">
* password = *TEXT
*/
const userPassRE = /^([^:]*):(.*)$/
async function fastifyBasicAuth (fastify, opts) {
if (typeof opts.validate !== 'function') {
throw new Error('Basic Auth: Missing validate function')
}
const strictCredentials = opts.strictCredentials ?? true
const useUtf8 = opts.utf8 ?? true
const charset = useUtf8 ? 'utf-8' : 'ascii'
const authenticateHeader = getAuthenticateHeader(opts.authenticate, useUtf8)
const header = opts.header?.toLowerCase() || 'authorization'
const credentialsRE = strictCredentials
? credentialsStrictRE
: credentialsLaxRE
const validate = opts.validate.bind(fastify)
fastify.decorate('basicAuth', basicAuth)
function basicAuth (req, reply, next) {
const credentials = req.headers[header]
if (typeof credentials !== 'string') {
done(new MissingOrBadAuthorizationHeader())
return
}
// parse header
const match = credentialsRE.exec(credentials)
if (match === null) {
done(new MissingOrBadAuthorizationHeader())
return
}
// decode user pass
const credentialsDecoded = Buffer.from(match[1], 'base64').toString(charset)
/**
* The user-id and password MUST NOT contain any control characters (see
* "CTL" in Appendix B.1 of [RFC5234]).
* @see https://datatracker.ietf.org/doc/html/rfc7617#section-2
*/
if (controlRE.test(credentialsDecoded)) {
done(new MissingOrBadAuthorizationHeader())
return
}
const userPass = userPassRE.exec(credentialsDecoded)
if (userPass === null) {
done(new MissingOrBadAuthorizationHeader())
return
}
const result = validate(userPass[1], userPass[2], req, reply, done)
if (result && typeof result.then === 'function') {
result.then(done, done)
}
function done (err) {
if (err !== undefined) {
// We set the status code to be 401 if it is not set
if (!err.statusCode) {
err.statusCode = 401
}
if (err.statusCode === 401) {
const header = authenticateHeader(req)
if (header) {
reply.header('WWW-Authenticate', header)
}
}
next(err)
} else {
next()
}
}
}
}
function getAuthenticateHeader (authenticate, useUtf8) {
if (!authenticate) return () => false
if (authenticate === true) {
return useUtf8
? () => 'Basic charset="UTF-8"'
: () => 'Basic'
}
if (typeof authenticate === 'object') {
const realm = authenticate.realm
switch (typeof realm) {
case 'undefined':
case 'boolean':
return useUtf8
? () => 'Basic charset="UTF-8"'
: () => 'Basic'
case 'string':
return useUtf8
? () => `Basic realm="${realm}", charset="UTF-8"`
: () => `Basic realm="${realm}"`
case 'function':
return useUtf8
? (req) => `Basic realm="${realm(req)}", charset="UTF-8"`
: (req) => `Basic realm="${realm(req)}"`
}
}
throw new Error('Basic Auth: Invalid authenticate option')
}
module.exports = fp(fastifyBasicAuth, {
fastify: '5.x',
name: '@fastify/basic-auth'
})
module.exports.default = fastifyBasicAuth
module.exports.fastifyBasicAuth = fastifyBasicAuth