Skip to content

Commit 6d3f637

Browse files
voxpelliEomm
andauthored
Add default user-agent + enable custom one (#226)
* Fix `test:coverage` command * Add default user-agent + enable custom one Every library appends their user-agent to the one that's been set at a level above them. Hence any provided user-agent comes before the default user agent. To provide context, the version number of `@fastify/oauth2` + the home page of it is included in the User-Agent. That way an API can diagnose possibly faulty consumers and file an issue. If someone wouldn't want to expose this, they can always set a custom User-Agent header directly in the http settings. Especially if #225 gets merged. * Update index.js Co-authored-by: Manuel Spigolon <[email protected]> * Fix feedback, allow disabling of user-agent * Simplify user-agent string * Move `credentials` initialization to the top * Naming tweak in test * Remove multi-`user-agent` use The RFC 9110 states: "The User-Agent field value consists of one or more product identifiers, each followed by zero or more comments (Section 5.6.5), which together identify the user agent software and its significant subproducts. By convention, the product identifiers are listed in decreasing order of their significance for identifying the user agent software." But since many are not aware of it, it may be better to remove it --------- Co-authored-by: Manuel Spigolon <[email protected]>
1 parent a73dd68 commit 6d3f637

File tree

4 files changed

+169
-13
lines changed

4 files changed

+169
-13
lines changed

index.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const kGenerateCallbackUriParams = Symbol.for('fastify-oauth2.generate-callback-
88

99
const { promisify, callbackify } = require('util')
1010

11+
const USER_AGENT = 'fastify-oauth2'
12+
1113
function defaultGenerateStateFunction () {
1214
return randomBytes(16).toString('base64url')
1315
}
@@ -69,14 +71,16 @@ function fastifyOauth2 (fastify, options, next) {
6971
if (options.cookie && typeof options.cookie !== 'object') {
7072
return next(new Error('options.cookie should be an object'))
7173
}
74+
if (options.userAgent && typeof options.userAgent !== 'string') {
75+
return next(new Error('options.userAgent should be a string'))
76+
}
7277

7378
if (!fastify.hasReplyDecorator('cookie')) {
7479
fastify.register(require('@fastify/cookie'))
7580
}
7681

7782
const {
7883
name,
79-
credentials,
8084
callbackUri,
8185
callbackUriParams = {},
8286
tokenRequestParams = {},
@@ -88,6 +92,19 @@ function fastifyOauth2 (fastify, options, next) {
8892
schema = { tags }
8993
} = options
9094

95+
const userAgent = options.userAgent === false
96+
? undefined
97+
: (options.userAgent || USER_AGENT)
98+
const credentials = {
99+
...options.credentials,
100+
http: {
101+
...options.credentials.http,
102+
headers: {
103+
'User-Agent': userAgent,
104+
...options.credentials.http?.headers
105+
}
106+
}
107+
}
91108
const generateCallbackUriParams = (credentials.auth && credentials.auth[kGenerateCallbackUriParams]) || defaultGenerateCallbackUriParams
92109
const cookieOpts = Object.assign({ httpOnly: true, sameSite: 'lax' }, options.cookie)
93110

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"lint": "standard | snazzy",
99
"lint:fix": "standard --fix",
1010
"test": "npm run test:unit && npm run test:typescript",
11-
"test:coverage": "npm run unit -- --cov --coverage-report=html",
11+
"test:coverage": "npm run test:unit -- --cov --coverage-report=html",
1212
"test:typescript": "tsd",
1313
"test:unit": "tap"
1414
},

test/index.test.js

+149-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const fastifyOauth2 = require('..')
88

99
nock.disableNetConnect()
1010

11-
function makeRequests (t, fastify) {
11+
function makeRequests (t, fastify, userAgentHeaderMatcher) {
1212
fastify.listen({ port: 0 }, function (err) {
1313
t.error(err)
1414

@@ -39,17 +39,11 @@ function makeRequests (t, fastify) {
3939
}
4040

4141
const githubScope = nock('https://github.com')
42-
.post('/login/oauth/access_token', 'grant_type=authorization_code&code=my-code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback', {
43-
reqheaders: {
44-
authorization: 'Basic bXktY2xpZW50LWlkOm15LXNlY3JldA=='
45-
}
46-
})
42+
.matchHeader('Authorization', 'Basic bXktY2xpZW50LWlkOm15LXNlY3JldA==')
43+
.matchHeader('User-Agent', userAgentHeaderMatcher || 'fastify-oauth2')
44+
.post('/login/oauth/access_token', 'grant_type=authorization_code&code=my-code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback')
4745
.reply(200, RESPONSE_BODY)
48-
.post('/login/oauth/access_token', 'grant_type=refresh_token&refresh_token=my-refresh-token', {
49-
reqheaders: {
50-
authorization: 'Basic bXktY2xpZW50LWlkOm15LXNlY3JldA=='
51-
}
52-
})
46+
.post('/login/oauth/access_token', 'grant_type=refresh_token&refresh_token=my-refresh-token')
5347
.reply(200, RESPONSE_BODY_REFRESHED)
5448

5549
fastify.inject({
@@ -192,6 +186,128 @@ t.test('fastify-oauth2', t => {
192186
})
193187
})
194188

189+
t.test('custom user-agent', t => {
190+
const fastify = createFastify({ logger: { level: 'silent' } })
191+
192+
fastify.register(fastifyOauth2, {
193+
name: 'githubOAuth2',
194+
credentials: {
195+
client: {
196+
id: 'my-client-id',
197+
secret: 'my-secret'
198+
},
199+
auth: fastifyOauth2.GITHUB_CONFIGURATION
200+
},
201+
startRedirectPath: '/login/github',
202+
callbackUri: 'http://localhost:3000/callback',
203+
scope: ['notifications'],
204+
userAgent: 'test/1.2.3'
205+
})
206+
207+
fastify.get('/', function (request, reply) {
208+
return this.githubOAuth2.getAccessTokenFromAuthorizationCodeFlow(request)
209+
.then(result => {
210+
// attempts to refresh the token
211+
return this.githubOAuth2.getNewAccessTokenUsingRefreshToken(result.token)
212+
})
213+
.then(token => {
214+
return {
215+
access_token: token.token.access_token,
216+
refresh_token: token.token.refresh_token,
217+
expires_in: token.token.expires_in,
218+
token_type: token.token.token_type
219+
}
220+
})
221+
})
222+
223+
t.teardown(fastify.close.bind(fastify))
224+
225+
makeRequests(t, fastify, 'test/1.2.3')
226+
})
227+
228+
t.test('overridden user-agent', t => {
229+
const fastify = createFastify({ logger: { level: 'silent' } })
230+
231+
fastify.register(fastifyOauth2, {
232+
name: 'githubOAuth2',
233+
credentials: {
234+
client: {
235+
id: 'my-client-id',
236+
secret: 'my-secret'
237+
},
238+
auth: fastifyOauth2.GITHUB_CONFIGURATION,
239+
http: {
240+
headers: {
241+
'User-Agent': 'foo/4.5.6'
242+
}
243+
}
244+
},
245+
startRedirectPath: '/login/github',
246+
callbackUri: 'http://localhost:3000/callback',
247+
scope: ['notifications'],
248+
userAgent: 'test/1.2.3'
249+
})
250+
251+
fastify.get('/', function (request, reply) {
252+
return this.githubOAuth2.getAccessTokenFromAuthorizationCodeFlow(request)
253+
.then(result => {
254+
// attempts to refresh the token
255+
return this.githubOAuth2.getNewAccessTokenUsingRefreshToken(result.token)
256+
})
257+
.then(token => {
258+
return {
259+
access_token: token.token.access_token,
260+
refresh_token: token.token.refresh_token,
261+
expires_in: token.token.expires_in,
262+
token_type: token.token.token_type
263+
}
264+
})
265+
})
266+
267+
t.teardown(fastify.close.bind(fastify))
268+
269+
makeRequests(t, fastify, /^foo\/4\.5\.6$/)
270+
})
271+
272+
t.test('disabled user-agent', t => {
273+
const fastify = createFastify({ logger: { level: 'silent' } })
274+
275+
fastify.register(fastifyOauth2, {
276+
name: 'githubOAuth2',
277+
credentials: {
278+
client: {
279+
id: 'my-client-id',
280+
secret: 'my-secret'
281+
},
282+
auth: fastifyOauth2.GITHUB_CONFIGURATION
283+
},
284+
startRedirectPath: '/login/github',
285+
callbackUri: 'http://localhost:3000/callback',
286+
scope: ['notifications'],
287+
userAgent: false
288+
})
289+
290+
fastify.get('/', function (request, reply) {
291+
return this.githubOAuth2.getAccessTokenFromAuthorizationCodeFlow(request)
292+
.then(result => {
293+
// attempts to refresh the token
294+
return this.githubOAuth2.getNewAccessTokenUsingRefreshToken(result.token)
295+
})
296+
.then(token => {
297+
return {
298+
access_token: token.token.access_token,
299+
refresh_token: token.token.refresh_token,
300+
expires_in: token.token.expires_in,
301+
token_type: token.token.token_type
302+
}
303+
})
304+
})
305+
306+
t.teardown(fastify.close.bind(fastify))
307+
308+
makeRequests(t, fastify, userAgent => userAgent === undefined)
309+
})
310+
195311
t.end()
196312
})
197313

@@ -624,6 +740,28 @@ t.test('options.cookie should be an object', t => {
624740
})
625741
})
626742

743+
t.test('options.userAgent should be a string', t => {
744+
t.plan(1)
745+
746+
const fastify = createFastify({ logger: { level: 'silent' } })
747+
748+
fastify.register(fastifyOauth2, {
749+
name: 'the-name',
750+
credentials: {
751+
client: {
752+
id: 'my-client-id',
753+
secret: 'my-secret'
754+
},
755+
auth: fastifyOauth2.GITHUB_CONFIGURATION
756+
},
757+
callbackUri: '/callback',
758+
userAgent: 1
759+
})
760+
.ready(err => {
761+
t.strictSame(err.message, 'options.userAgent should be a string')
762+
})
763+
})
764+
627765
t.test('options.schema', t => {
628766
const fastify = createFastify({ logger: { level: 'silent' }, exposeHeadRoutes: false })
629767

types/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ declare namespace fastifyOauth2 {
3333
tags?: string[];
3434
schema?: object;
3535
cookie?: CookieSerializeOptions;
36+
userAgent?: string | false;
3637
}
3738

3839
export type TToken = 'access_token' | 'refresh_token'

0 commit comments

Comments
 (0)