Skip to content

Commit 9441c5d

Browse files
committed
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 fastify#225 gets merged.
1 parent 72b9bb2 commit 9441c5d

File tree

3 files changed

+131
-12
lines changed

3 files changed

+131
-12
lines changed

index.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ const kGenerateCallbackUriParams = Symbol.for('fastify-oauth2.generate-callback-
88

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

11+
const { homepage, version } = require('./package.json')
12+
13+
const USER_AGENT = `fastify-oauth2/${version} (${homepage})`
14+
1115
function defaultGenerateStateFunction () {
1216
return randomBytes(16).toString('base64url')
1317
}
@@ -69,6 +73,9 @@ function fastifyOauth2 (fastify, options, next) {
6973
if (options.cookie && typeof options.cookie !== 'object') {
7074
return next(new Error('options.cookie should be an object'))
7175
}
76+
if (options.userAgent && typeof options.userAgent !== 'string') {
77+
return next(new Error('options.userAgent should be a string'))
78+
}
7279

7380
if (!fastify.hasReplyDecorator('cookie')) {
7481
fastify.register(require('@fastify/cookie'))
@@ -87,6 +94,9 @@ function fastifyOauth2 (fastify, options, next) {
8794
const tags = options.tags || []
8895
const schema = options.schema || { tags }
8996
const cookieOpts = Object.assign({ httpOnly: true, sameSite: 'lax' }, options.cookie)
97+
const userAgent = options.userAgent
98+
? options.userAgent + ' ' + USER_AGENT
99+
: USER_AGENT
90100

91101
function generateAuthorizationUri (request, reply) {
92102
const state = generateStateFunction(request)
@@ -180,7 +190,16 @@ function fastifyOauth2 (fastify, options, next) {
180190
revokeAllTokenCallbacked(token, params, callback)
181191
}
182192

183-
const oauth2 = new AuthorizationCode(credentials)
193+
const oauth2 = new AuthorizationCode({
194+
...credentials,
195+
http: {
196+
...credentials.http,
197+
headers: {
198+
'User-Agent': userAgent,
199+
...credentials.http?.headers
200+
}
201+
}
202+
})
184203

185204
if (startRedirectPath) {
186205
fastify.get(startRedirectPath, { schema }, startRedirectHandler)

test/index.test.js

Lines changed: 110 additions & 11 deletions
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, userAgentRegexp) {
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', userAgentRegexp || /^fastify-oauth2\/[0-9.]+ \(http[^)]+\)$/)
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,89 @@ 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 fastify-oauth2\/[0-9.]+ \(http[^)]+\)$/)
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+
195272
t.end()
196273
})
197274

@@ -624,6 +701,28 @@ t.test('options.cookie should be an object', t => {
624701
})
625702
})
626703

704+
t.test('options.userAgent should be a string', t => {
705+
t.plan(1)
706+
707+
const fastify = createFastify({ logger: { level: 'silent' } })
708+
709+
fastify.register(fastifyOauth2, {
710+
name: 'the-name',
711+
credentials: {
712+
client: {
713+
id: 'my-client-id',
714+
secret: 'my-secret'
715+
},
716+
auth: fastifyOauth2.GITHUB_CONFIGURATION
717+
},
718+
callbackUri: '/callback',
719+
userAgent: 1
720+
})
721+
.ready(err => {
722+
t.strictSame(err.message, 'options.userAgent should be a string')
723+
})
724+
})
725+
627726
t.test('options.schema', t => {
628727
const fastify = createFastify({ logger: { level: 'silent' }, exposeHeadRoutes: false })
629728

types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ declare namespace fastifyOauth2 {
3232
tags?: string[];
3333
schema?: object;
3434
cookie?: CookieSerializeOptions;
35+
userAgent?: string;
3536
}
3637

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

0 commit comments

Comments
 (0)