Skip to content

Commit

Permalink
fix: passport strategy adapter must support oauth2 flows
Browse files Browse the repository at this point in the history
fixes: #4902
  • Loading branch information
deepakrkris committed Mar 20, 2020
1 parent 8339c2e commit ae56e93
Show file tree
Hide file tree
Showing 11 changed files with 1,324 additions and 7 deletions.
732 changes: 732 additions & 0 deletions extensions/authentication-passport/package-lock.json

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion extensions/authentication-passport/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@
"@types/node": "^10.17.17",
"@types/passport": "^1.0.3",
"@types/passport-http": "^0.3.8",
"passport-http": "^0.3.0"
"@types/passport-oauth2": "^1.4.8",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.15",
"passport-http": "^0.3.0",
"passport-oauth2": "^1.5.0",
"supertest": "^4.0.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Node module: @loopback/authentication-passport
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {
UserProfileFactory, authenticate,
} from '@loopback/authentication';
import {Strategy as Oauth2Strategy, StrategyOptions, VerifyFunction, VerifyCallback} from 'passport-oauth2';
import {MyUser, userRepository} from './fixtures/user-repository';
import {simpleRestApplication, configureApplication} from './fixtures/simple-rest-app';
import {securityId, UserProfile} from '@loopback/security';
import {StrategyAdapter} from '../../strategy-adapter';
import {get, param} from '@loopback/openapi-v3';
import {
Client,
createClientForHandler,
expect,
supertest
} from '@loopback/testlab';
import {RestApplication, RedirectRoute} from '@loopback/rest';
import {startApp as startMockProvider, stopApp as stopMockProvider} from './fixtures/oauth2-provider';
import * as url from 'url';

let oauth2Options: StrategyOptions = {
clientID: '1111',
clientSecret: '1917e2b73a87fd0c9a92afab64f6c8d4',
callbackURL: 'http://localhost:8080/auth/thirdparty/callback',
authorizationURL: 'http://localhost:9000/oauth/dialog',
tokenURL: 'http://localhost:9000/oauth/token',
}

let verify: VerifyFunction = function (accessToken: string, refreshToken: string, profile: any, done: VerifyCallback) {
let userProfile: MyUser = profile as MyUser;
let user: MyUser = userRepository.findUser(userProfile.id);
return done(null, user);
}

const myUserProfileFactory: UserProfileFactory<MyUser> = function(
user: MyUser,
): UserProfile {
const userProfile = {[securityId]: user.id};
return userProfile;
};

/**
* login controller for third party oauth provider
*/
export class Oauth2Controller {
constructor() {}

@authenticate('oauth2')
@get('/auth/thirdparty')
handleFacebookLogin(@param.query.string('x-loopback-authentication-redirect-url') redirectUrl: string,
@param.query.number('x-loopback-authentication-redirect-status') status: number) {
return new RedirectRoute('/', redirectUrl, status);
}

@authenticate('oauth2')
@get('/auth/thirdparty/callback')
handleFacebookAuthCallback(@param.query.string('code') code: string) {
console.log(`Inside /auth/thirdparty/callback`, code);
}
}

describe.only('Oauth2 authorization flow', () => {
let app: RestApplication;
let oauth2Strategy: StrategyAdapter<MyUser>;
let client: Client;

before(startMockProvider);
after(stopMockProvider);

before(givenLoopBackApp);
before(givenOauth2Strategy);
before(setupAuthentication);
before(givenControllerInApp);
before(givenClient);

let oauthProviderUrl: string;
let providerLoginUrl: string;
let callbackToLbApp: string;

context('when client invokes oauth flow', () => {

it('call is redirected to third party authorization url', async () => {
let response = await client.get('/auth/thirdparty').expect(303);
oauthProviderUrl = response.get('Location');
expect(url.parse(response.get('Location')).pathname).to.equal(url.parse(oauth2Options.authorizationURL).pathname);
});

it('call to authorization url is redirected to oauth providers login page', async () => {
let response = await supertest('').get(oauthProviderUrl).expect(302);
providerLoginUrl = response.get('Location');
expect(url.parse(response.get('Location')).pathname).to.equal('/login');
});
});

context('when user logs into provider login page', () => {
it('login page redirects to authorization app callback endpoint', async () => {
let params = url.parse(providerLoginUrl).query;
params = params + '&&username=user1&&password=abc';
console.log('http://localhost:9000/login_submit?' + params);
let response = await supertest('').post('http://localhost:9000/login_submit?' + params).expect(302);
callbackToLbApp = response.get('Location');
expect(url.parse(response.get('Location')).pathname).to.equal('/auth/thirdparty/callback');
});

it('callback url contains access code', async () => {
console.log(callbackToLbApp);
expect(url.parse(callbackToLbApp).query).to.containEql('code');
});
});

context('Invoking call back url returns access token', () => {
it('access code can be exchanged for token', async () => {
let response = await client.post(url.parse(callbackToLbApp).path).expect(200);
expect(response.body.access_token).to.not.be.Null;
});
});

function givenLoopBackApp() {
app = simpleRestApplication();
}

function givenOauth2Strategy() {
let passport = new Oauth2Strategy(oauth2Options, verify);
oauth2Strategy = new StrategyAdapter(
passport,
'oauth2',
myUserProfileFactory,
);
}

function setupAuthentication() {
configureApplication(oauth2Strategy, 'oauth2');
}

function givenControllerInApp() {
return app.controller(Oauth2Controller);
}

function givenClient() {
client = createClientForHandler(app.requestHandler);
}
});
Loading

0 comments on commit ae56e93

Please sign in to comment.