authentic-service
Version:
This is the service component of Authentic. This will help decode tokens so that you can authenticate users within a microservice.
233 lines (206 loc) • 6.14 kB
JavaScript
const fs = require('fs')
const http = require('http')
const jwt = require('jsonwebtoken')
const path = require('path')
const servertest = require('dg-servertest')
const tape = require('tape')
const crypto = require('crypto')
const publicKey = fs.readFileSync(
path.join(__dirname, '/rsa-public.pem'),
'utf-8'
)
const privateKey = fs.readFileSync(
path.join(__dirname, '/rsa-private.pem'),
'utf-8'
)
const Authentic = require('../')
const EXPIRED_EMAIL = 'expired@scalehaus.io'
const EXPIRED_HASH = crypto
.createHash('sha256')
.update(EXPIRED_EMAIL)
.digest('hex')
let server = null
let auth = null
const payload = { email: 'chet@scalehaus.io', expiresIn: '30d' }
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' })
tape('init', function (t) {
server = http.createServer(function (req, res) {
if (req.url === '/auth/public-key') {
return res.end(
JSON.stringify({
success: true,
data: { publicKey }
})
)
}
if (req.url === '/auth/expired') {
return res.end(JSON.stringify({
[EXPIRED_HASH]: Math.floor((Date.now() + (60 * 60 * 1000)) / 1000)
}))
}
})
server.listen(0, function (err) {
if (err) return console.error(err)
auth = Authentic({
server: 'http://localhost:' + this.address().port,
checkExpiredList: true
})
t.end()
})
})
tape('should handle anonymous request', function (t) {
const opts = { method: 'GET' }
servertest(createService(auth), '/', opts, function (err, res) {
t.ifError(err, 'should not error')
const data = JSON.parse(res.body)
t.equal(data, null, 'should not have authData')
t.end()
})
})
tape('should handle bad jwt', function (t) {
const opts = {
method: 'GET',
headers: {
Authorization: 'Bearer ' + 'not a jwt'
}
}
servertest(createService(auth), '/', opts, function (err, res) {
t.ifErr(err, 'should not error on bad token')
const parsed = JSON.parse(res.body.toString())
t.deepEqual(
parsed,
{
message: 'jwt malformed',
name: 'JsonWebTokenError',
statusCode: 401
},
'should have correct error'
)
t.end()
})
})
tape('should handle missing token error', function (t) {
const opts = {
method: 'GET',
headers: {
Authorization: 'Bearer ' + ''
}
}
servertest(createService(auth), '/', opts, function (err, res) {
t.ifErr(err, 'should not error on bad token')
const parsed = JSON.parse(res.body.toString())
t.deepEqual(
parsed,
{
message: 'jwt must be provided',
name: 'JsonWebTokenError',
statusCode: 401
},
'should have correct error'
)
t.end()
})
})
tape("should handle 'TokenExpiredError'", function (t) {
const payload = { email: 'chet@scalehaus.io' }
const soonToExpireToken = jwt.sign(payload, privateKey, {
algorithm: 'RS256',
expiresIn: '1'
})
const opts = {
method: 'GET',
headers: {
Authorization: 'Bearer ' + soonToExpireToken
}
}
const serviceInstance = createService(auth)
setTimeout(function test () {
servertest(serviceInstance, '/', opts, function (err, res) {
t.ifErr(err, 'should not error on expired jwt')
const parsed = JSON.parse(res.body.toString())
t.equal(parsed.statusCode, 401, 'status code matches')
t.equal(parsed.message, 'jwt expired', 'should have correct message')
t.equal(parsed.name, 'TokenExpiredError', 'should have correct name')
t.end()
})
}, 5)
})
tape("should handle 'NotBeforeError'", function (t) {
const nbf = new Date().getTime() + 10000
const payload = { email: 'chet@scalehaus.io', nbf }
const soonToExpireToken = jwt.sign(payload, privateKey, {
algorithm: 'RS256'
})
const opts = {
method: 'GET',
headers: {
Authorization: 'Bearer ' + soonToExpireToken
}
}
const serviceInstance = createService(auth)
setTimeout(function test () {
servertest(serviceInstance, '/', opts, function (err, res) {
t.ifErr(err, 'should not error on expired jwt')
const parsed = JSON.parse(res.body.toString())
t.equal(parsed.statusCode, 401, 'status code matches')
t.equal(parsed.message, 'jwt not active', 'should have correct message')
t.equal(parsed.name, 'NotBeforeError', 'should have correct name')
t.end()
})
}, 5)
})
tape('should handle auth token', function (t) {
const opts = {
method: 'GET',
headers: {
Authorization: 'Bearer ' + token
}
}
servertest(createService(auth), '/', opts, function (err, res) {
t.ifError(err, 'should not error')
const data = JSON.parse(res.body)
t.equal(data.email, 'chet@scalehaus.io', 'should have correct email')
t.equal(data.expiresIn, '30d', 'should have correct expiresIn')
t.ok(data.iat, 'chet@scalehaus.io', 'should have iat')
t.end()
})
})
tape(
'should reject token with issue time before expiry list time',
function (t) {
const payload = { email: EXPIRED_EMAIL }
const earlyIssuedToken = jwt.sign(payload, privateKey, {
algorithm: 'RS256'
})
const opts = {
method: 'GET',
headers: {
Authorization: 'Bearer ' + earlyIssuedToken
}
}
servertest(createService(auth), '/', opts, function (err, res) {
t.ifErr(err, 'should not error due to early issue time')
const data = JSON.parse(res.body)
t.equal(data.statusCode, 401, 'should return 401 Unauthorized')
t.equal(data.message, 'jwt expired by remote', 'should have correct error message')
t.end()
})
}
)
tape('cleanup', function (t) {
server.close()
t.end()
})
function createService (auth) {
return http.createServer(function (req, res) {
auth(req, res, function (err, authData) {
if (err) {
err.stack = undefined
res.writeHead(err.statusCode, { 'Content-Type': 'application/json' })
return res.end(JSON.stringify(err))
}
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(authData || null))
})
})
}