diff --git a/index.js b/index.js index 4732f50..f9ec675 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,8 @@ const kGenerateCallbackUriParams = Symbol.for('fastify-oauth2.generate-callback- const { promisify, callbackify } = require('util') +const USER_AGENT = 'fastify-oauth2' + function defaultGenerateStateFunction () { return randomBytes(16).toString('base64url') } @@ -69,6 +71,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')) @@ -76,7 +81,6 @@ function fastifyOauth2 (fastify, options, next) { const { name, - credentials, callbackUri, callbackUriParams = {}, tokenRequestParams = {}, @@ -88,6 +92,19 @@ function fastifyOauth2 (fastify, options, next) { schema = { tags } } = options + const userAgent = options.userAgent === false + ? undefined + : (options.userAgent || USER_AGENT) + const credentials = { + ...options.credentials, + http: { + ...options.credentials.http, + headers: { + 'User-Agent': userAgent, + ...options.credentials.http?.headers + } + } + } const generateCallbackUriParams = (credentials.auth && credentials.auth[kGenerateCallbackUriParams]) || defaultGenerateCallbackUriParams const cookieOpts = Object.assign({ httpOnly: true, sameSite: 'lax' }, options.cookie) diff --git a/package.json b/package.json index 0f6a964..8f8c071 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "lint": "standard | snazzy", "lint:fix": "standard --fix", "test": "npm run test:unit && npm run test:typescript", - "test:coverage": "npm run unit -- --cov --coverage-report=html", + "test:coverage": "npm run test:unit -- --cov --coverage-report=html", "test:typescript": "tsd", "test:unit": "tap" }, diff --git a/test/index.test.js b/test/index.test.js index f1bbd4a..635c7f9 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -8,7 +8,7 @@ const fastifyOauth2 = require('..') nock.disableNetConnect() -function makeRequests (t, fastify) { +function makeRequests (t, fastify, userAgentHeaderMatcher) { fastify.listen({ port: 0 }, function (err) { t.error(err) @@ -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', userAgentHeaderMatcher || 'fastify-oauth2') + .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({ @@ -192,6 +186,128 @@ 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') + }) + + 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.test('disabled 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: false + }) + + 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, userAgent => userAgent === undefined) + }) + t.end() }) @@ -624,6 +740,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 }) diff --git a/types/index.d.ts b/types/index.d.ts index eef2b11..5ed0543 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -33,6 +33,7 @@ declare namespace fastifyOauth2 { tags?: string[]; schema?: object; cookie?: CookieSerializeOptions; + userAgent?: string | false; } export type TToken = 'access_token' | 'refresh_token'