Skip to content

Commit

Permalink
Add custom function parameter to Addon.prototype.auth for extracting …
Browse files Browse the repository at this point in the history
…token in addition to default locations
  • Loading branch information
vsaienko committed Mar 25, 2021
1 parent 2e2ef39 commit 0150ec6
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 41 deletions.
139 changes: 102 additions & 37 deletions __tests__/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,30 +58,36 @@ describe('Auth', () => {

test('Unknown issuer', async () => {
const loadCredentials = jest.fn()
const token = jwt.encode({
iss: jiraPayload.clientKey
}, jiraPayload.sharedSecret)
const token = jwt.encode(
{
iss: jiraPayload.clientKey
},
jiraPayload.sharedSecret
)

const req = {
body: jiraPayload,
headers: { authorization: `JWT ${token}` },
query: {}
}

await expect(jiraAddon.auth(req, {
loadCredentials
})).rejects.toMatchError(
new AuthError('Unknown issuer', 'UNKNOWN_ISSUER')
)
await expect(
jiraAddon.auth(req, {
loadCredentials
})
).rejects.toMatchError(new AuthError('Unknown issuer', 'UNKNOWN_ISSUER'))
expect(loadCredentials).toHaveBeenCalledWith(req.body.clientKey)
})

test('Invalid signature', async () => {
const loadCredentials = jest.fn().mockReturnValue(jiraPayload)

const token = jwt.encode({
iss: jiraPayload.clientKey
}, 'invalid-shared-secret')
const token = jwt.encode(
{
iss: jiraPayload.clientKey
},
'invalid-shared-secret'
)

const req = {
body: jiraPayload,
Expand All @@ -105,10 +111,13 @@ describe('Auth', () => {
const loadCredentials = jest.fn().mockReturnValue(jiraPayload)
const now = Math.floor(Date.now() / 1000)

const token = jwt.encode({
iss: jiraPayload.clientKey,
exp: now - 1000
}, jiraPayload.sharedSecret)
const token = jwt.encode(
{
iss: jiraPayload.clientKey,
exp: now - 1000
},
jiraPayload.sharedSecret
)

const req = {
body: jiraPayload,
Expand All @@ -124,10 +133,13 @@ describe('Auth', () => {

test('Invalid QSH', async () => {
const loadCredentials = jest.fn().mockReturnValue(jiraPayload)
const token = jwt.encode({
iss: jiraPayload.clientKey,
qsh: 'invalid-qsh'
}, jiraPayload.sharedSecret)
const token = jwt.encode(
{
iss: jiraPayload.clientKey,
qsh: 'invalid-qsh'
},
jiraPayload.sharedSecret
)

const req = {
body: jiraPayload,
Expand All @@ -136,19 +148,22 @@ describe('Auth', () => {
method: 'POST'
}

await expect(jiraAddon.auth(req, {
loadCredentials
})).rejects.toMatchError(
new AuthError('Invalid QSH', 'INVALID_QSH')
)
await expect(
jiraAddon.auth(req, {
loadCredentials
})
).rejects.toMatchError(new AuthError('Invalid QSH', 'INVALID_QSH'))
expect(loadCredentials).toHaveBeenCalledWith(req.body.clientKey)
})

test('No "qsh" in JWT token provided', async () => {
const loadCredentials = jest.fn().mockReturnValue(jiraPayload)
const token = jwt.encode({
iss: jiraPayload.clientKey
}, jiraPayload.sharedSecret)
const token = jwt.encode(
{
iss: jiraPayload.clientKey
},
jiraPayload.sharedSecret
)

const req = {
body: jiraPayload,
Expand All @@ -157,19 +172,27 @@ describe('Auth', () => {
method: 'POST'
}

await expect(jiraAddon.auth(req, {
loadCredentials
})).rejects.toMatchError(
new AuthError('JWT did not contain the query string hash (qsh) claim', 'MISSED_QSH')
await expect(
jiraAddon.auth(req, {
loadCredentials
})
).rejects.toMatchError(
new AuthError(
'JWT did not contain the query string hash (qsh) claim',
'MISSED_QSH'
)
)
expect(loadCredentials).toHaveBeenCalledWith(req.body.clientKey)
})

test('No "qsh" in JWT token provided for Bitbucket add-on', async () => {
const loadCredentials = jest.fn().mockReturnValue(jiraPayload)
const token = jwt.encode({
iss: bitbucketPayload.clientKey
}, bitbucketPayload.sharedSecret)
const token = jwt.encode(
{
iss: bitbucketPayload.clientKey
},
bitbucketPayload.sharedSecret
)

const req = {
body: bitbucketPayload,
Expand All @@ -189,9 +212,12 @@ describe('Auth', () => {

test('"skipQsh" passed', async () => {
const loadCredentials = jest.fn().mockReturnValue(jiraPayload)
const token = jwt.encode({
iss: jiraPayload.clientKey
}, jiraPayload.sharedSecret)
const token = jwt.encode(
{
iss: jiraPayload.clientKey
},
jiraPayload.sharedSecret
)

const req = {
body: jiraPayload,
Expand Down Expand Up @@ -308,4 +334,43 @@ describe('Auth', () => {
}
`)
})

test('Extract token from a custom place', async () => {
const token = jwt.encode(
{
iss: jiraPayload.clientKey,
sub: 'test:account-id'
},
jiraPayload.sharedSecret
)

const req = {
headers: {},
body: jiraPayload,
query: { state: `JWT ${token}` },
pathname: '/account',
originalUrl: '/api/account',
method: 'POST'
}

const result = await jiraAddon.auth(req, {
loadCredentials: () => jiraPayload,
customExtractToken: () => req.query.state,
skipQsh: true
})

expect(result).toMatchInlineSnapshot(`
Object {
"credentials": Object {
"baseUrl": "https://test.atlassian.net",
"clientKey": "jira-client-key",
"sharedSecret": "shh-secret-cat",
},
"payload": Object {
"iss": "jira-client-key",
"sub": "test:account-id",
},
}
`)
})
})
19 changes: 17 additions & 2 deletions lib/Addon.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,23 @@ class Addon {
throw new AuthError('Unauthorized update request', 'UNAUTHORIZED_REQUEST')
}

async auth (req, { skipQsh, loadCredentials }) {
const token = util.extractToken(req)
/**
* Callback for extracting token.
*
* @callback loadCredentialsCallback
*/

/**
*
* @param {*} req
* @param {Object} options
* @param {boolean} options.skipQsh
* @param {() => {}} options.loadCredentials
* @param {() => {}} options.customExtractToken - custom function to extract
* token in addition to default `req.headers.authorization` and `req.query.jwt`
*/
async auth (req, { skipQsh, loadCredentials, customExtractToken }) {
const token = util.extractToken(req, customExtractToken)

if (!token) {
throw new AuthError('Missed token', 'MISSED_TOKEN')
Expand Down
6 changes: 4 additions & 2 deletions lib/util.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const jwt = require('atlassian-jwt')
const AuthError = require('./AuthError')

function extractToken (req) {
const token = req.headers.authorization || req.query.jwt || ''
const noop = () => {}

function extractToken (req, customExtractToken = noop) {
const token = req.headers.authorization || req.query.jwt || customExtractToken() || ''
return token.replace(/^JWT /, '')
}

Expand Down

0 comments on commit 0150ec6

Please sign in to comment.