-
-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathindex.js
183 lines (159 loc) · 5.72 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
181
182
183
'use strict'
const fp = require('fastify-plugin')
const createError = require('@fastify/error')
/**
* 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 = 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',
'Missing or bad formatted authorization header',
errorResponseCode
)
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 `errorResponseCode` (normally 401) if it is not set
if (!err.statusCode) {
err.statusCode = errorResponseCode
}
if (err.statusCode === errorResponseCode) {
const header = authenticateHeader(req)
if (header) {
reply.header(header[0], header[1])
}
}
next(err)
} else {
next()
}
}
}
}
function getAuthenticateHeaders (authenticate, useUtf8, proxyMode) {
const defaultHeaderName = proxyMode ? 'Proxy-Authenticate' : 'WWW-Authenticate'
if (!authenticate) return () => false
if (authenticate === true) {
return useUtf8
? () => [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
? () => [headerName, 'Basic charset="UTF-8"']
: () => [headerName, 'Basic']
case 'string':
return useUtf8
? () => [headerName, `Basic realm="${realm}", charset="UTF-8"`]
: () => [headerName, `Basic realm="${realm}"`]
case 'function':
return useUtf8
? (req) => [headerName, `Basic realm="${realm(req)}", charset="UTF-8"`]
: (req) => [headerName, `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