-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: passport strategy adapter must support oauth2 flows
fixes: #4902
- Loading branch information
1 parent
8339c2e
commit ae56e93
Showing
11 changed files
with
1,324 additions
and
7 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
146 changes: 146 additions & 0 deletions
146
...c/__tests__/acceptance/authentication-with-passport-strategy-oauth2-adapter.acceptance.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}); |
Oops, something went wrong.