added tests, fixed some services

This commit is contained in:
Wyatt Johnson
2017-06-19 08:55:08 -06:00
parent b78d4f29ea
commit 393ad8530a
5 changed files with 165 additions and 24 deletions
+1
View File
@@ -12,6 +12,7 @@ program
.command('assets', 'interact with assets')
.command('setup', 'setup the application')
.command('jobs', 'work with the job queues')
.command('token', 'work with the access tokens')
.command('users', 'work with the application auth')
.command('migration', 'provides utilities for migrating the database')
.command('plugins', 'provides utilities for interacting with the plugin system')
Regular → Executable
+2
View File
@@ -49,6 +49,8 @@ async function revokeToken(tokenID) {
await TokensService.revoke(null, tokenID);
console.log(`Revoked Token[${tokenID}]`);
util.shutdown();
} catch (e) {
console.error(e);
+16 -22
View File
@@ -1,6 +1,7 @@
const passport = require('passport');
const UsersService = require('./users');
const SettingsService = require('./settings');
const TokensService = require('./tokens');
const fetch = require('node-fetch');
const FormData = require('form-data');
const JWT = require('jsonwebtoken');
@@ -34,23 +35,6 @@ const GenerateToken = (user) => JWT.sign({}, JWT_SECRET, {
audience: JWT_AUDIENCE
});
// GeneratePersonalAccessToken will sign a token to include all the
// authorization information needed for the front end for headless access.
const GeneratePersonalAccessToken = (userID) => {
const payload = {
jti: uuid.v4(),
iss: JWT_ISSUER,
aud: JWT_AUDIENCE,
sub: userID,
pat: true
};
// Sign the payload.
const jwt = JWT.sign(payload, JWT_SECRET, {});
return {payload, jwt};
};
// SetTokenForSafari sends the token in a cookie for Safari clients.
const SetTokenForSafari = (req, res, token) => {
const browser = bowser._detect(req.headers['user-agent']);
@@ -174,10 +158,7 @@ const HandleLogout = (req, res, next) => {
});
};
/**
* Check if the given token is already blacklisted, throw an error if it is.
*/
const CheckBlacklisted = (jwt) => new Promise((resolve, reject) => {
const checkGeneralTokenBlacklist = (jwt) => new Promise((resolve, reject) => {
client.get(`jtir[${jwt.jti}]`, (err, expiry) => {
if (err) {
return reject(err);
@@ -191,6 +172,20 @@ const CheckBlacklisted = (jwt) => new Promise((resolve, reject) => {
});
});
/**
* Check if the given token is already blacklisted, throw an error if it is.
*/
const CheckBlacklisted = async (jwt) => {
// Check to see if this is a PAT.
if (jwt.pat) {
return TokensService.validate(jwt.sub, jwt.jti);
}
// It wasn't a PAT! Check to see if it is valid anyways.
return checkGeneralTokenBlacklist(jwt);
};
const jwt = require('jsonwebtoken');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
@@ -491,6 +486,5 @@ module.exports = {
HandleAuthPopupCallback,
HandleGenerateCredentials,
HandleLogout,
GeneratePersonalAccessToken,
CheckBlacklisted
};
+47 -2
View File
@@ -1,5 +1,13 @@
const errors = require('../errors');
const UserModel = require('../models/user');
const {GeneratePersonalAccessToken} = require('./passport');
const JWT = require('jsonwebtoken');
const uuid = require('uuid');
const {
JWT_SECRET,
JWT_ISSUER,
JWT_AUDIENCE
} = require('../config');
/**
* TokenService manages Personal Access Tokens for users. These tokens are
@@ -16,7 +24,16 @@ module.exports = class TokenService {
static async create(userID, tokenName) {
// Create the token.
let {payload, jwt} = GeneratePersonalAccessToken(userID);
const payload = {
jti: uuid.v4(),
iss: JWT_ISSUER,
aud: JWT_AUDIENCE,
sub: userID,
pat: true
};
// Sign the payload.
const jwt = JWT.sign(payload, JWT_SECRET, {});
// Create the PAT.
let pat = {
@@ -63,6 +80,34 @@ module.exports = class TokenService {
});
}
/**
* Validate that a given Token is valid.
*
* @param {String} userID the user's id that owns the token
* @param {String} tokenID the id of the token
*/
static async validate(userID, tokenID) {
// Find the user.
let user = await UserModel.findOne({
id: userID
}).select('tokens');
if (!user || !user.tokens) {
throw new errors.ErrAuthentication('user does not exist');
}
// Extract the token from the user.
let token = user.tokens.find(({id}) => id === tokenID);
if (!token) {
throw new errors.ErrAuthentication('token does not exist');
}
// Check to see if it is active.
if (!token.active) {
throw new errors.ErrAuthentication('token is not active');
}
}
/**
* Lists the tokens owned by the user.
*
+99
View File
@@ -0,0 +1,99 @@
const TokensService = require('../../../services/tokens');
const UsersService = require('../../../services/users');
const SettingsService = require('../../../services/settings');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
describe('services.TokensService', () => {
let user;
beforeEach(async () => {
await SettingsService.init();
user = await UsersService.createLocalUser('sockmonster@gmail.com', '2Coral!!', 'Sockmonster');
});
describe('#create', () => {
it('can create the token without error', async () => {
let token = await TokensService.create(user.id, 'Github Token');
expect(token).to.be.an.object;
expect(token.jwt).to.be.a.string;
expect(token.pat).to.be.an.object;
let pat = token.pat;
let tokens = await TokensService.list(user.id);
expect(tokens).to.have.length(1);
expect(tokens[0]).to.have.property('id', pat.id);
expect(tokens[0]).to.have.property('name', pat.name);
});
});
describe('#revoke', () => {
it('can revoke a token', async () => {
let {pat: {id}} = await TokensService.create(user.id, 'Github Token');
let tokens = await TokensService.list(user.id);
expect(tokens).to.have.length(1);
expect(tokens[0]).to.have.property('id', id);
expect(tokens[0]).to.have.property('active', true);
await TokensService.revoke(user.id, id);
tokens = await TokensService.list(user.id);
expect(tokens).to.have.length(1);
expect(tokens[0]).to.have.property('id', id);
expect(tokens[0]).to.have.property('active', false);
});
});
describe('#validate', () => {
it('will allow a valid token', async () => {
// Create a token.
let {pat: {id}} = await TokensService.create(user.id, 'Github Token');
// Validate it.
await TokensService.validate(user.id, id);
});
it('will not allow an invalid token', async () => {
// Create a token.
let {pat: {id}} = await TokensService.create(user.id, 'Github Token');
// Revoke it.
await TokensService.revoke(user.id, id);
// Validate it.
return TokensService.validate(user.id, id).should.eventually.be.rejected;
});
});
describe('#list', () => {
it('lists the tokens for a user', async () => {
let tokens = await TokensService.list(user.id);
expect(tokens).to.have.length(0);
// Create a token.
let {pat: {id}} = await TokensService.create(user.id, 'Github Token');
tokens = await TokensService.list(user.id);
expect(tokens).to.have.length(1);
expect(tokens[0]).to.have.property('id', id);
});
});
});