ravel
Version:
Ravel Rapid Application Development Framework
277 lines (243 loc) • 8.95 kB
JavaScript
describe('Authentication Integration Test', () => {
let Ravel, app;
const profile = {
id: 1234,
name: 'Sean McIntyre',
password: 'abcd'
};
beforeEach(async () => {
Ravel = require('../../lib/ravel');
app = new Ravel();
app.set('log level', app.$log.NONE);
app.set('keygrip keys', ['mysecret']);
app.set('session secure', false);
});
describe('Simulated Local Auth Provider', () => {
beforeEach(async () => {
const LocalStrategy = require('passport-local').Strategy;
const bodyParser = require('koa-bodyparser');
// test provider wrapping LocalStrategy
class LocalProvider extends Ravel.AuthenticationProvider {
get name () {
return 'local';
}
init (koaRouter, passport, verify) {
passport.use(new LocalStrategy(verify));
// login route. Expects credentials in JSON.
koaRouter.post('/auth/local', bodyParser(), function (ctx, next) {
return passport.authenticate('local', function (err, user, info, status) {
if (err || !user) {
ctx.status = err && err.status ? err.status : 401;
ctx.message = err && err.message ? err.message : '';
} else {
ctx.body = user;
ctx.status = 200;
return ctx.login(user);
}
})(ctx, next);
});
// verify session route
koaRouter.get('/auth/local', function (ctx) {
if (ctx.isAuthenticated()) {
ctx.body = ctx.state.user;
ctx.status = 200;
} else {
ctx.status = 401;
}
});
}
handlesClient (client) {
return client === 'local' || client === 'token';
}
credentialToProfile (session, client) {
return new Promise((resolve, reject) => {
if (client === 'token') {
if (session === '123456789') {
return resolve({ expiry: 60, profile: profile });
} else {
return reject(new Ravel.$err.Authentication('Incorrect API token'));
}
} else {
return reject(new Ravel.$err.IllegalValue(`LocalProvider does not support token auth for clients of type ${client}`));
}
});
}
}
.Module.authconfig
.Module('authconfig')
.inject('$err')
class AuthConfig {
constructor ($err) {
this.$err = $err;
}
serializeUser (profile) {
return Promise.resolve(profile.id);
}
deserializeUser (userId) {
if (userId === profile.id) {
return Promise.resolve(profile);
} else {
return Promise.reject(new this.$err.Authentication('User session cannot be found.'));
}
}
deserializeOrCreateUser (userId) {
if (userId !== profile.id) {
const newProfile = {
id: userId,
name: profile.name,
password: profile.password
};
return Promise.resolve(newProfile);
}
}
verify (provider, username, password) {
if (username === profile.name && password === profile.password) {
return Promise.resolve(profile);
} else {
return Promise.reject(new this.$err.Authentication('Username or password is incorrect'));
}
}
}
// stub Routes (miscellaneous routes, such as templated HTML content)
const mapping = Ravel.Routes.mapping;
const authenticated = Ravel.Routes.authenticated;
.Routes('/')
.autoinject('$err', '$log')
class TestRoutes {
async appHandler (ctx) {
ctx.body = '<!DOCTYPE html><html></html>';
ctx.status = 200;
}
async deprecatedHandler (ctx) {
ctx.body = ctx.passport.user;
ctx.status = 200;
}
async redirectHandler (ctx) {
ctx.body = 'hello';
}
async autoRegisterHandler (ctx) {
ctx.body = 'hello';
}
async errorHandler (ctx) {
throw new this.$err.IllegalValue();
}
}
app.registerProvider(LocalProvider);
app.load(AuthConfig, TestRoutes);
await app.init();
});
afterEach(async () => {
await app.close();
});
it('should support local auth on login route', async () => {
await request(app.callback)
.post('/auth/local')
.type('application/json')
.send({ username: profile.name, password: profile.password })
.expect(200);
});
it('should reject incorrect passwords on login route', async () => {
await request(app.callback)
.post('/auth/local')
.type('application/json')
.send({ username: profile.name, password: 'wrongpassword' })
.expect(401);
});
it('should reject unknown users on login route', async () => {
await request(app.callback)
.post('/auth/local')
.type('application/json')
.send({ username: 'wrongname', password: 'wrongpassword' })
.expect(401);
});
it('should reject unauthenticated users on an @authenticated route', async () => {
await request(app.callback)
.get('/app')
.expect(401);
});
it('should redirect unauthenticated users on an @authenticated route with redirect:true', async () => {
await request(app.callback)
.get('/redirect')
.expect(302)
.expect('Location', app.get('login route'));
});
it('should reject tokenauth users on an @authenticated route when they have the wrong token', async () => {
await request(app.callback)
.get('/app')
.set('x-auth-token', 'bad-token')
.set('x-auth-client', 'token')
.expect(401);
});
it('should reject tokenauth users on an @authenticated route when their client type is not supported', async () => {
await request(app.callback)
.get('/app')
.set('x-auth-token', '123456789')
.set('x-auth-client', 'bad-type')
.expect(401);
});
it('should allow access to authenticated users on @authenticated routes', async () => {
// TODO remove workaround.
// cookies are busted in jest due to bugs:
// https://github.com/visionmedia/supertest/issues/336
// https://github.com/facebook/jest/issues/3547
// https://github.com/visionmedia/supertest/issues/460
// https://github.com/facebook/jest/issues/2549
const agent = request.agent(app.callback);
const res = await agent
.post('/auth/local')
.type('application/json')
.send({ username: profile.name, password: profile.password })
.expect(200);
// trying cookiejar workaround
res.headers['set-cookie'][0]
.split(',')
.map(item => item.split(';')[0])
.forEach(c => agent.jar.setCookie(c));
await agent
.get('/app')
.expect(200, '<!DOCTYPE html><html></html>');
});
it('should allow access to token-authenticated users on @authenticated routes', async () => {
const agent = request.agent(app.callback);
await agent
.get('/app')
.set('x-auth-token', '123456789')
.set('x-auth-client', 'token')
.expect(200, '<!DOCTYPE html><html></html>');
});
it('should allow auto registration when allowRegistration:true', async () => {
await request(app.callback)
.get('/autoregister')
.set('x-auth-token', '123456789')
.set('x-auth-client', 'token')
.expect(200, 'hello');
});
it('should allow rethrow non-auth errors from handler', async () => {
await request(app.callback)
.get('/err')
.set('x-auth-token', '123456789')
.set('x-auth-client', 'token')
.expect(400);
});
it('should log a deprecation message for use of ctx.passport', async () => {
const spy = jest.spyOn(app.$log, 'warn');
await request(app.callback)
.get('/deprecated')
.set('x-auth-token', '123456789')
.set('x-auth-client', 'token')
.expect(200);
expect(spy).toHaveBeenCalledWith('ctx.passport is deprecated. Please use ctx.state instead.');
});
});
});