storage-upload/test/jwt/jwt.test.mjs

375 lines
11 KiB
JavaScript

import { Eltro as t, assert} from 'eltro'
import encode from '../../api/jwt/encode.mjs'
import decode from '../../api/jwt/decode.mjs'
import defaults from '../../api/defaults.mjs'
const unixNow = Math.floor(Date.now() / 1000)
const jwtHeader = {
typ: 'JWT',
alg: 'HS256',
kid: '2'
}
const jwtBody = {
aud: 'https://host/oauth/token',
iss: 'test@test.com',
iat: unixNow,
exp: unixNow + 600,
scope: ['http://stuff', 'http://stuff2']
}
const pubKeys = {
'test@test.com': {
'default@HS256': 'sharedkey',
'2@HS256': 'sharedkey',
'2@HS384': 'sharedkey',
'2@HS512': 'sharedkey',
'5@HS256': 'wrongkey'
},
'test@custom.com': {
'2@HS256': {
key: 'sharedkey',
expiresSkew: 600,
expiresMax: 86400
},
'3@HS256': {
},
'4@HS256': [],
'5@HS256': { key: 'sharedkey' },
},
}
t.describe('encode/decode', function() {
['HS256', 'HS384', 'HS512'].forEach(function(algo) {
t.test('success with ' + algo, function() {
let customJwtHeader = defaults(jwtHeader)
customJwtHeader.kid = '2'
customJwtHeader.alg = algo
let jwt = encode(customJwtHeader, jwtBody, 'sharedkey')
let decodedJwtBody = decode(jwt, pubKeys, [
'https://host/oauth/token'
])
assert.deepStrictEqual(jwtBody, decodedJwtBody)
})
})
t.test('success without kid', function() {
let customJwtHeader = defaults(jwtHeader)
delete customJwtHeader.kid
let jwt = encode(customJwtHeader, jwtBody, 'sharedkey')
let decodedJwtBody = decode(jwt, pubKeys, [
'https://host/oauth/token'
])
assert.deepStrictEqual(jwtBody, decodedJwtBody)
})
t.test('success without iat or iss', function() {
let customJwtBody = defaults(jwtBody)
delete customJwtBody.iat
delete customJwtBody.exp
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
let decodedJwtBody = decode(jwt, pubKeys, [
'https://host/oauth/token'
])
assert.ok(decodedJwtBody.iat)
assert.ok(decodedJwtBody.iat >= unixNow)
assert.ok(decodedJwtBody.iat < unixNow + 2)
assert.ok(decodedJwtBody.exp)
assert.ok(decodedJwtBody.exp >= unixNow)
assert.ok(decodedJwtBody.exp > unixNow + 200)
assert.ok(decodedJwtBody.exp < unixNow + 400)
})
t.test('success with object key', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.kid = '5'
customJwtBody.iss = 'test@custom.com'
let jwt = encode(jwtHeader, jwtBody, 'sharedkey')
let decodedJwtBody = decode(jwt, pubKeys, [
'https://host/oauth/token'
])
assert.deepStrictEqual(jwtBody, decodedJwtBody)
})
t.test('success with array aud', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.aud = [
'https://myhost/oauth/token',
'https://host/oauth/token'
]
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
let decodedJwtBody = decode(jwt, pubKeys, [
'https://host/oauth/token'
])
assert.deepStrictEqual(customJwtBody, decodedJwtBody)
})
t.test('success with no aud and empty audience list', function() {
let customJwtBody = defaults(jwtBody)
delete customJwtBody.aud
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
let decodedJwtBody = decode(jwt, pubKeys, [])
assert.deepStrictEqual(customJwtBody, decodedJwtBody)
})
t.test('success with expired token', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.iss = 'test@custom.com'
customJwtBody.exp -= 600
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
let decodedJwtBody = decode(jwt, pubKeys, [
'https://host/oauth/token'
])
assert.deepStrictEqual(customJwtBody, decodedJwtBody)
})
t.test('token outside maximum expires', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.iss = 'test@custom.com'
customJwtBody.exp += 172800
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Expires in the future by more than 86400 seconds/
)
})
t.test('token outside maximum expires using decode options', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.exp += 172800
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'], {
expiresMax: 600
})
},
/Expires in the future by more than 600 seconds/
)
})
t.test('token outside maximum expires using nbf', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.exp += 172800
customJwtBody.nbf = customJwtBody.iat
delete customJwtBody.iat
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'], {
expiresMax: 600
})
},
/Expires in the future by more than 600 seconds/
)
})
t.test('token outside maximum expires using unixNow', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.exp += 172800
delete customJwtBody.iat
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'], {
expiresMax: 600
})
},
/Expires in the future by more than 600 seconds/
)
})
t.test('unknown aud', function() {
let jwt = encode(jwtHeader, jwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://myhost/oauth/token'])
},
/Unknown audience 'https:\/\/host\/oauth\/token'/
)
})
t.test('expired', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.iat -= 1200
customJwtBody.exp -= 800
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Token has expired/
)
})
t.test('invalid', function() {
assert.throws(
function() {
decode('asdfasdgassdga.asdfasdg.sadfsadfas', pubKeys, ['https://host/oauth/token'])
},
/Token was invalid/
)
})
t.test('missing exp', function() {
let customJwtBody = defaults(jwtBody)
delete customJwtBody.exp
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey', true)
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/No expires set on token/
)
})
t.test('missing iss', function() {
let customJwtBody = defaults(jwtBody)
delete customJwtBody.iss
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/No issuer set/
)
})
t.test('iat invalid', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.iat += 1200
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Issued at in the future by more than 300 seconds/
)
})
t.test('nbf invalid', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.nbf = customJwtBody.iat + 1200
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Not before in the future by more than 300 seconds/
)
})
t.test('unknown issuer', function() {
let customJwtBody = defaults(jwtBody)
customJwtBody.iss = 'unknown@test.com'
let jwt = encode(jwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Unknown issuer 'unknown@test.com'/
)
})
t.test('wrong alg', function() {
let customJwtHeader = defaults(jwtHeader)
customJwtHeader.alg = 'HS128'
assert.throws(
function() {
encode(customJwtHeader, jwtBody, 'sharedkey')
},
/Only alg HS256, HS384 and HS512 are supported/
)
})
t.test('unknown kid', function() {
let customJwtHeader = defaults(jwtHeader)
customJwtHeader.kid = '3'
let jwt = encode(customJwtHeader, jwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Unknown pubkey id '3'/
)
})
t.test('invalid signature', function() {
let customJwtHeader = defaults(jwtHeader)
customJwtHeader.kid = '2'
let jwt = encode(customJwtHeader, jwtBody, 'sharedkey')
let backup = jwt
if (jwt[jwt.length - 2] === 'A') {
jwt = jwt.slice(0, -2) + 'BB'
} else {
jwt = jwt.slice(0, -2) + 'AA'
}
try {
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Verification failed with alg 'HS256'/
)
} catch (err) {
console.log('-----')
console.log(backup)
console.log(jwt)
throw err
}
jwt = encode(customJwtHeader, jwtBody, 'sharedkey')
jwt += 'a'
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Verification failed with alg 'HS256'/
)
})
t.test('invalid shared key', function() {
let customJwtHeader = defaults(jwtHeader)
customJwtHeader.kid = '5'
customJwtHeader.alg = 'HS256'
let jwt = encode(customJwtHeader, jwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/Verification failed with alg 'HS256'/
)
})
t.test('invalid pubkey', function() {
let customJwtHeader = defaults(jwtHeader)
customJwtHeader.kid = '4'
let jwt = encode(customJwtHeader, jwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
}
)
let customJwtBody = defaults(jwtBody)
customJwtBody.iss = 'test@custom.com'
customJwtHeader.kid = '3'
jwt = encode(customJwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/'3'.+misconfigured/
)
customJwtHeader.kid = '4'
jwt = encode(customJwtHeader, customJwtBody, 'sharedkey')
assert.throws(
function() {
decode(jwt, pubKeys, ['https://host/oauth/token'])
},
/'4'.+misconfigured/
)
})
t.test('success with broken token', function() {
let expectedJwtBody = {
id: 1,
exp: 1519802991,
iat: 1519802691,
iss: 'test@test.com',
aud: 'https://host/oauth/token'
}
let decodedJwtBody = decode(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTE5ODAyNjkxfQ.p6t378Ri2JpOCm9WtC36ttyH8ILzG9-OWT_kgMrrRfo',
pubKeys,
['https://host/oauth/token'],
{
fixup: (header, body) => {
header.kid = '2'
body.iss = 'test@test.com'
body.aud = 'https://host/oauth/token'
body.exp = body.iat + 300
},
expiresSkew: 307584000
}
)
assert.deepStrictEqual(decodedJwtBody, expectedJwtBody)
})
})