Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Authentication v3 Express integration #1218

Merged
merged 2 commits into from
Feb 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions packages/express/lib/authentication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const { flatten, merge } = require('lodash');
const { BadRequest } = require('@feathersjs/errors');

const normalizeStrategy = (_settings = [], ..._strategies) =>
typeof _settings === 'string'
? { strategies: flatten([ _settings, ..._strategies ]) }
: _settings;
const getService = (settings, app) => {
const path = settings.service || app.get('defaultAuthentication');
const service = app.service(path);

if (!service) {
throw new BadRequest(`Could not find authentication service '${path}'`);
}

return service;
};

exports.parseAuthentication = (...strategies) => {
const settings = normalizeStrategy(...strategies);

if (!Array.isArray(settings.strategies) || settings.strategies.length === 0) {
throw new Error(`'parseAuthentication' middleware requires at least one strategy name`);
}

return function (req, res, next) {
const { app } = req;
const service = getService(settings, app);

service.parse(req, res, ...settings.strategies)
.then(authentication => {
merge(req, {
authentication,
feathers: { authentication }
});

next();
}).catch(next);
};
};

exports.authenticate = (...strategies) => {
const settings = normalizeStrategy(...strategies);

if (!Array.isArray(settings.strategies) || settings.strategies.length === 0) {
throw new Error(`'authenticate' middleware requires at least one strategy name`);
}

return function (req, res, next) {
const { app, authentication } = req;
const service = getService(settings, app);

service.authenticate(authentication, req.feathers, ...settings.strategies)
.then(authResult => {
merge(req, authResult);

next();
}).catch(next);
};
};
3 changes: 2 additions & 1 deletion packages/express/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const errorHandler = require('@feathersjs/errors/handler');
const notFound = require('@feathersjs/errors/not-found');
const debug = require('debug')('@feathersjs/express');

const authentication = require('./authentication');
const rest = require('./rest');

function feathersExpress (feathersApp) {
Expand Down Expand Up @@ -83,7 +84,7 @@ function feathersExpress (feathersApp) {

module.exports = feathersExpress;

Object.assign(module.exports, express, {
Object.assign(module.exports, express, authentication, {
default: feathersExpress,
original: express,
rest,
Expand Down
2 changes: 1 addition & 1 deletion packages/express/lib/rest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function rest (handler = formatter) {
app.rest = wrappers;

app.use(function (req, res, next) {
req.feathers = { provider: 'rest' };
req.feathers = Object.assign({ provider: 'rest' }, req.feathers);
next();
});

Expand Down
3 changes: 3 additions & 0 deletions packages/express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@
"uberproto": "^2.0.4"
},
"devDependencies": {
"@feathersjs/authentication": "^2.1.16",
"@feathersjs/authentication-local": "^1.2.9",
"@feathersjs/feathers": "^3.3.1",
"axios": "^0.18.0",
"chai": "^4.2.0",
"lodash": "^4.17.11",
"mocha": "^5.2.0"
}
}
195 changes: 195 additions & 0 deletions packages/express/test/authentication.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
const assert = require('assert');
const _axios = require('axios');
const feathers = require('@feathersjs/feathers');
const getApp = require('@feathersjs/authentication-local/test/fixture');
const { authenticate } = require('@feathersjs/authentication');

const expressify = require('../lib');
const axios = _axios.create({
baseURL: 'http://localhost:9876/'
});

describe('@feathersjs/express/authentication', () => {
const email = 'expresstest@authentication.com';
const password = 'superexpress';

let app, server, user, authResult;

before(() => {
const expressApp = expressify(feathers())
.use(expressify.json())
.use(expressify.parseAuthentication('jwt'))
.configure(expressify.rest());

app = getApp(expressApp);
server = app.listen(9876);

app.use('/dummy', {
get (id, params) {
return Promise.resolve({ id, params });
}
});

app.use('/protected', expressify.authenticate('jwt'), (req, res) => {
res.json(req.user);
});

app.use(expressify.errorHandler({
logger: false
}));

app.service('dummy').hooks({
before: [ authenticate('jwt') ]
});

return app.service('users').create({ email, password })
.then(result => {
user = result;

return axios.post('/authentication', {
strategy: 'local',
password,
email
});
}).then(res => {
authResult = res.data;
});
});

after(done => server.close(done));

it('middleware needs strategies ', () => {
try {
expressify.parseAuthentication();
assert.fail('Should never get here');
} catch (error) {
assert.strictEqual(error.message,
`'parseAuthentication' middleware requires at least one strategy name`
);
}

try {
expressify.authenticate();
assert.fail('Should never get here');
} catch(error) {
assert.strictEqual(error.message,
`'authenticate' middleware requires at least one strategy name`
);
}
});

describe('service authentication', () => {
it('successful local authentication', () => {
assert.ok(authResult.accessToken);
assert.deepStrictEqual(authResult.authentication, {
strategy: 'local'
});
assert.strictEqual(authResult.user.email, email);
assert.strictEqual(authResult.user.password, undefined);
});

it('local authentication with wrong password fails', () => {
return axios.post('/authentication', {
strategy: 'local',
password: 'wrong',
email
}).then(() => {
assert.fail('Should never get here');
}).catch(error => {
const { data } = error.response;
assert.strictEqual(data.name, 'NotAuthenticated');
assert.strictEqual(data.message, 'Invalid login');
});
});

it('authenticating with JWT works but returns same accessToken', () => {
const { accessToken } = authResult;

return axios.post('/authentication', {
strategy: 'jwt',
accessToken
}).then(res => {
const { data } = res;

assert.strictEqual(data.accessToken, accessToken);
assert.strictEqual(data.authentication.strategy, 'jwt');
assert.strictEqual(data.authentication.payload.sub, user.id.toString());
assert.strictEqual(data.user.email, email);
});
});

it('can make a protected request with Authorization header', () => {
const { accessToken } = authResult;

return axios.get('/dummy/dave', {
headers: {
Authorization: accessToken
}
}).then(res => {
const { data, data: { params } } = res;

assert.strictEqual(data.id, 'dave');
assert.deepStrictEqual(params.user, user);
assert.strictEqual(params.authentication.accessToken, accessToken);
});
});

it('can make a protected request with Authorization header and bearer scheme', () => {
const { accessToken } = authResult;

return axios.get('/dummy/dave', {
headers: {
Authorization: ` Bearer: ${accessToken}`
}
}).then(res => {
const { data, data: { params } } = res;

assert.strictEqual(data.id, 'dave');
assert.deepStrictEqual(params.user, user);
assert.strictEqual(params.authentication.accessToken, accessToken);
});
});
});

describe('authenticate middleware', () => {
it('protected endpoint fails when JWT is not present', () => {
return axios.get('/protected').then(() => {
assert.fail('Should never get here');
}).catch(error => {
const { data } = error.response;

assert.strictEqual(data.name, 'NotAuthenticated');
assert.strictEqual(data.message, 'No valid authentication strategy available');
});
});

it.skip('protected endpoint fails with invalid Authorization header', () => {
return axios.get('/protected', {
headers: {
Authorization: 'Bearer: something wrong'
}
}).then(() => {
assert.fail('Should never get here');
}).catch(error => {
const { data } = error.response;

assert.strictEqual(data.name, 'NotAuthenticated');
assert.strictEqual(data.message, 'Not authenticated');
});
});

it('can request protected endpoint with JWT present', () => {
return axios.get('/protected', {
headers: {
Authorization: `Bearer ${authResult.accessToken}`
}
}).then(res => {
const { data } = res;

assert.strictEqual(data.email, user.email);
assert.strictEqual(data.id, user.id);
assert.strictEqual(data.password, undefined, 'Passed provider information');
});
});
});
});
9 changes: 4 additions & 5 deletions packages/express/test/rest/index.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const assert = require('assert');
const axios = require('axios');
const bodyParser = require('body-parser');

const feathers = require('@feathersjs/feathers');
const { Service } = require('@feathersjs/commons/lib/test/fixture');
Expand Down Expand Up @@ -92,7 +91,7 @@ describe('@feathersjs/express/rest provider', () => {
before(function () {
app = expressify(feathers())
.configure(rest(rest.formatter))
.use(bodyParser.json())
.use(expressify.json())
.use('codes', {
get (id, params) {
return Promise.resolve({ id });
Expand Down Expand Up @@ -297,7 +296,7 @@ describe('@feathersjs/express/rest provider', () => {
next();
})
.configure(rest(rest.formatter))
.use(bodyParser.json())
.use(expressify.json())
.use('/todo', {
create (data) {
return Promise.resolve(data);
Expand Down Expand Up @@ -325,7 +324,7 @@ describe('@feathersjs/express/rest provider', () => {
const app = expressify(feathers());

app.configure(rest())
.use(bodyParser.json())
.use(expressify.json())
.use('/todo', function (req, res, next) {
req.body.before = [ 'before first' ];
next();
Expand Down Expand Up @@ -371,7 +370,7 @@ describe('@feathersjs/express/rest provider', () => {
res.status(200).json(res.data);
}];
app.configure(rest())
.use(bodyParser.json())
.use(expressify.json())
.use('/array-middleware', middlewareArray);

const server = app.listen(4776);
Expand Down