Skip to content

Commit

Permalink
Add default user-agent + enable custom one
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
voxpelli committed Aug 26, 2023
1 parent 21fb54f commit d54f877
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 12 deletions.
21 changes: 20 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const kGenerateCallbackUriParams = Symbol.for('fastify-oauth2.generate-callback-

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

const { homepage, version } = require('./package.json')

const USER_AGENT = `fastify-oauth2/${version} (${homepage})`

function defaultGenerateStateFunction () {
return randomBytes(16).toString('base64url')
}
Expand Down Expand Up @@ -69,6 +73,9 @@ function fastifyOauth2 (fastify, options, next) {
if (options.cookie && typeof options.cookie !== 'object') {
return next(new Error('options.cookie should be an object'))
}
if (options.userAgent && typeof options.userAgent !== 'string') {
return next(new Error('options.userAgent should be a string'))
}

if (!fastify.hasReplyDecorator('cookie')) {
fastify.register(require('@fastify/cookie'))
Expand All @@ -90,6 +97,9 @@ function fastifyOauth2 (fastify, options, next) {

const generateCallbackUriParams = (credentials.auth && credentials.auth[kGenerateCallbackUriParams]) || defaultGenerateCallbackUriParams
const cookieOpts = Object.assign({ httpOnly: true, sameSite: 'lax' }, options.cookie)
const userAgent = options.userAgent
? options.userAgent + ' ' + USER_AGENT
: USER_AGENT

function generateAuthorizationUri (request, reply) {
const state = generateStateFunction(request)
Expand Down Expand Up @@ -183,7 +193,16 @@ function fastifyOauth2 (fastify, options, next) {
revokeAllTokenCallbacked(token, params, callback)
}

const oauth2 = new AuthorizationCode(credentials)
const oauth2 = new AuthorizationCode({
...credentials,
http: {
...credentials.http,
headers: {
'User-Agent': userAgent,
...credentials.http?.headers
}
}
})

if (startRedirectPath) {
fastify.get(startRedirectPath, { schema }, startRedirectHandler)
Expand Down
121 changes: 110 additions & 11 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const fastifyOauth2 = require('..')

nock.disableNetConnect()

function makeRequests (t, fastify) {
function makeRequests (t, fastify, userAgentRegexp) {
fastify.listen({ port: 0 }, function (err) {
t.error(err)

Expand Down Expand Up @@ -39,17 +39,11 @@ function makeRequests (t, fastify) {
}

const githubScope = nock('https://github.com')
.post('/login/oauth/access_token', 'grant_type=authorization_code&code=my-code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback', {
reqheaders: {
authorization: 'Basic bXktY2xpZW50LWlkOm15LXNlY3JldA=='
}
})
.matchHeader('Authorization', 'Basic bXktY2xpZW50LWlkOm15LXNlY3JldA==')
.matchHeader('User-Agent', userAgentRegexp || /^fastify-oauth2\/[0-9.]+ \(http[^)]+\)$/)
.post('/login/oauth/access_token', 'grant_type=authorization_code&code=my-code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback')
.reply(200, RESPONSE_BODY)
.post('/login/oauth/access_token', 'grant_type=refresh_token&refresh_token=my-refresh-token', {
reqheaders: {
authorization: 'Basic bXktY2xpZW50LWlkOm15LXNlY3JldA=='
}
})
.post('/login/oauth/access_token', 'grant_type=refresh_token&refresh_token=my-refresh-token')
.reply(200, RESPONSE_BODY_REFRESHED)

fastify.inject({
Expand Down Expand Up @@ -192,6 +186,89 @@ t.test('fastify-oauth2', t => {
})
})

t.test('custom user-agent', t => {
const fastify = createFastify({ logger: { level: 'silent' } })

fastify.register(fastifyOauth2, {
name: 'githubOAuth2',
credentials: {
client: {
id: 'my-client-id',
secret: 'my-secret'
},
auth: fastifyOauth2.GITHUB_CONFIGURATION
},
startRedirectPath: '/login/github',
callbackUri: 'http://localhost:3000/callback',
scope: ['notifications'],
userAgent: 'test/1.2.3'
})

fastify.get('/', function (request, reply) {
return this.githubOAuth2.getAccessTokenFromAuthorizationCodeFlow(request)
.then(result => {
// attempts to refresh the token
return this.githubOAuth2.getNewAccessTokenUsingRefreshToken(result.token)
})
.then(token => {
return {
access_token: token.token.access_token,
refresh_token: token.token.refresh_token,
expires_in: token.token.expires_in,
token_type: token.token.token_type
}
})
})

t.teardown(fastify.close.bind(fastify))

makeRequests(t, fastify, /^test\/1\.2\.3 fastify-oauth2\/[0-9.]+ \(http[^)]+\)$/)
})

t.test('overridden user-agent', t => {
const fastify = createFastify({ logger: { level: 'silent' } })

fastify.register(fastifyOauth2, {
name: 'githubOAuth2',
credentials: {
client: {
id: 'my-client-id',
secret: 'my-secret'
},
auth: fastifyOauth2.GITHUB_CONFIGURATION,
http: {
headers: {
'User-Agent': 'foo/4.5.6'
}
}
},
startRedirectPath: '/login/github',
callbackUri: 'http://localhost:3000/callback',
scope: ['notifications'],
userAgent: 'test/1.2.3'
})

fastify.get('/', function (request, reply) {
return this.githubOAuth2.getAccessTokenFromAuthorizationCodeFlow(request)
.then(result => {
// attempts to refresh the token
return this.githubOAuth2.getNewAccessTokenUsingRefreshToken(result.token)
})
.then(token => {
return {
access_token: token.token.access_token,
refresh_token: token.token.refresh_token,
expires_in: token.token.expires_in,
token_type: token.token.token_type
}
})
})

t.teardown(fastify.close.bind(fastify))

makeRequests(t, fastify, /^foo\/4\.5\.6$/)
})

t.end()
})

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

t.test('options.userAgent should be a string', t => {
t.plan(1)

const fastify = createFastify({ logger: { level: 'silent' } })

fastify.register(fastifyOauth2, {
name: 'the-name',
credentials: {
client: {
id: 'my-client-id',
secret: 'my-secret'
},
auth: fastifyOauth2.GITHUB_CONFIGURATION
},
callbackUri: '/callback',
userAgent: 1
})
.ready(err => {
t.strictSame(err.message, 'options.userAgent should be a string')
})
})

t.test('options.schema', t => {
const fastify = createFastify({ logger: { level: 'silent' }, exposeHeadRoutes: false })

Expand Down
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ declare namespace fastifyOauth2 {
tags?: string[];
schema?: object;
cookie?: CookieSerializeOptions;
userAgent?: string;
}

export type TToken = 'access_token' | 'refresh_token'
Expand Down

0 comments on commit d54f877

Please sign in to comment.