Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: Always require strategy parameter in authentication #1327

Merged
merged 1 commit into from
May 4, 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
7 changes: 1 addition & 6 deletions packages/authentication-local/src/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,9 @@ export class LocalStrategy extends AuthenticationBaseStrategy {
}

async authenticate (data: AuthenticationRequest, params: Params) {
const { passwordField, usernameField, entity, errorMessage } = this.configuration;
const { passwordField, usernameField, entity } = this.configuration;
const username = data[usernameField];
const password = data[passwordField];

if (data.strategy && data.strategy !== this.name) {
throw new NotAuthenticated(errorMessage);
}

const result = await this.findEntity(username, omit(params, 'provider'));

await this.comparePassword(result, password);
Expand Down
5 changes: 0 additions & 5 deletions packages/authentication-oauth/src/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
AuthenticationRequest, AuthenticationBaseStrategy
} from '@feathersjs/authentication';
import { Params } from '@feathersjs/feathers';
import { NotAuthenticated } from '@feathersjs/errors';

const debug = Debug('@feathersjs/authentication-oauth/strategy');

Expand Down Expand Up @@ -97,10 +96,6 @@ export class OAuthStrategy extends AuthenticationBaseStrategy {
}

async authenticate (authentication: AuthenticationRequest, params: Params) {
if (authentication.strategy !== this.name) {
throw new NotAuthenticated('Not authenticated');
}

const entity: string = this.configuration.entity;
const profile = await this.getProfile(authentication, params);
const existingEntity = await this.findEntity(profile, params)
Expand Down
12 changes: 0 additions & 12 deletions packages/authentication-oauth/test/strategy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,6 @@ describe('@feathersjs/authentication-oauth/strategy', () => {
});

describe('authenticate', () => {
it('errors when strategy is not set', async () => {
try {
await strategy.authenticate({
id: 'newEntity'
}, {});
assert.fail('Should never get here');
} catch (error) {
assert.equal(error.name, 'NotAuthenticated');
assert.equal(error.message, 'Not authenticated');
}
});

it('with new user', async () => {
const authResult = await strategy.authenticate({
strategy: 'test',
Expand Down
35 changes: 7 additions & 28 deletions packages/authentication/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,41 +194,20 @@ export class AuthenticationBase {
* @param allowed A list of allowed strategy names
*/
async authenticate (authentication: AuthenticationRequest, params: Params, ...allowed: string[]) {
debug('Running authenticate for strategies', allowed);
const { strategy } = authentication || ({} as AuthenticationRequest);
const [ authStrategy ] = this.getStrategies(strategy);

const strategies = this.getStrategies(...allowed)
.filter(current => current && typeof current.authenticate === 'function');
debug('Running authenticate for strategy', strategy, allowed);

if (!authentication || strategies.length === 0) {
if (!authentication || !authStrategy || !allowed.includes(strategy)) {
// If there are no valid strategies or `authentication` is not an object
throw new NotAuthenticated(`No valid authentication strategy available`);
throw new NotAuthenticated(`Invalid authentication information` + (!strategy ? ' (no `strategy` set)' : ''));
}

const { strategy } = authentication;
const authParams = {
return authStrategy.authenticate(authentication, {
...params,
authenticated: true
};

// Throw an error is a `strategy` is indicated but not in the allowed strategies
if (strategy && !allowed.includes(strategy)) {
throw new NotAuthenticated(`Invalid authentication strategy '${strategy}'`);
}

let error: Error|null = null;

for (const authStrategy of strategies) {
try {
const authResult = await authStrategy.authenticate(authentication, authParams);
return authResult;
} catch (currentError) {
error = error || currentError;
}
}

debug('All strategies error. First error is', error);

throw error;
});
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/authentication/src/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ export class JWTStrategy extends AuthenticationBaseStrategy {
}

async authenticate (authentication: AuthenticationRequest, params: Params) {
const { accessToken, strategy } = authentication;
const { accessToken } = authentication;
const { entity } = this.configuration;

if (!accessToken || (strategy && strategy !== this.name)) {
if (!accessToken) {
throw new NotAuthenticated('Not authenticated');
}

Expand Down
54 changes: 13 additions & 41 deletions packages/authentication/test/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from 'assert';
import feathers, { Application } from '@feathersjs/feathers';
import jwt from 'jsonwebtoken';

import { AuthenticationBase } from '../src/core';
import { AuthenticationBase, AuthenticationRequest } from '../src/core';
import { Strategy1, Strategy2, MockRequest } from './fixtures';
import { ServerResponse } from 'http';

Expand All @@ -23,6 +23,11 @@ describe('authentication/core', () => {

auth.register('first', new Strategy1());
auth.register('second', new Strategy2());
auth.register('dummy', {
async authenticate (data: AuthenticationRequest) {
return data;
}
});
});

describe('configuration', () => {
Expand Down Expand Up @@ -80,7 +85,7 @@ describe('authentication/core', () => {

describe('strategies', () => {
it('strategyNames', () => {
assert.deepStrictEqual(auth.strategyNames, [ 'first', 'second' ]);
assert.deepStrictEqual(auth.strategyNames, [ 'first', 'second', 'dummy' ]);
});

it('getStrategies', () => {
Expand Down Expand Up @@ -176,14 +181,6 @@ describe('authentication/core', () => {
}, Strategy2.result));
});

it('returns first success when both strategies succeed', async () => {
const result = await auth.authenticate({
both: true
}, {}, ...auth.strategyNames);

assert.deepStrictEqual(result, Strategy1.result);
});

it('throws error when allowed and passed strategy does not match', async () => {
try {
await auth.authenticate({
Expand All @@ -193,43 +190,18 @@ describe('authentication/core', () => {
assert.fail('Should never get here');
} catch (error) {
assert.strictEqual(error.name, 'NotAuthenticated');
assert.strictEqual(error.message, `Invalid authentication strategy 'first'`);
assert.strictEqual(error.message, 'Invalid authentication information');
}
});
});

describe('with a list of strategies and strategy not set in params', () => {
it('returns first success in chain', async () => {
const authentication = {
v2: true,
password: 'supersecret'
};

const result = await auth.authenticate(authentication, {}, 'first', 'second');

assert.deepStrictEqual(result, Object.assign({
authentication,
params: { authenticated: true }
}, Strategy2.result));
});

it('returns first error when all strategies fail', async () => {
try {
await auth.authenticate({}, {}, 'first', 'second');
assert.fail('Should never get here');
} catch (error) {
assert.strictEqual(error.name, 'NotAuthenticated');
assert.strictEqual(error.message, 'Invalid Dave');
}
});

it('errors when there is no valid strategy', async () => {
it('throws error when strategy is not set', async () => {
try {
await auth.authenticate({}, {}, 'bla');
await auth.authenticate({
username: 'Dummy'
}, {}, 'second');
assert.fail('Should never get here');
} catch (error) {
assert.strictEqual(error.name, 'NotAuthenticated');
assert.strictEqual(error.message, 'No valid authentication strategy available');
assert.strictEqual(error.message, 'Invalid authentication information (no `strategy` set)');
}
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/authentication/test/hooks/authenticate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ describe('authentication/hooks/authenticate', () => {
try {
await app.service('users').get(1, {
authentication: {
strategy: 'first',
some: 'thing'
}
});
Expand Down
2 changes: 1 addition & 1 deletion packages/authentication/test/jwt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('authentication/jwt', () => {
assert.fail('Should never get here');
} catch (error) {
assert.strictEqual(error.name, 'NotAuthenticated');
assert.strictEqual(error.message, 'Not authenticated');
assert.strictEqual(error.message, 'Invalid authentication information (no `strategy` set)');
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/authentication/test/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ describe('authentication/service', () => {
await app.service('authentication').remove(null);
assert.fail('Should never get here');
} catch (error) {
assert.strictEqual(error.message, 'No valid authentication strategy available');
assert.strictEqual(error.message, 'Invalid authentication information (no `strategy` set)');
}
});
});
Expand Down
2 changes: 1 addition & 1 deletion packages/express/test/authentication.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe('@feathersjs/express/authentication', () => {
const { data } = error.response;

assert.strictEqual(data.name, 'NotAuthenticated');
assert.strictEqual(data.message, 'No valid authentication strategy available');
assert.strictEqual(data.message, 'Invalid authentication information (no `strategy` set)');
});
});

Expand Down