diff --git a/packages/authentication-oauth/src/express.ts b/packages/authentication-oauth/src/express.ts index 2e555fa92e..19d97d754e 100644 --- a/packages/authentication-oauth/src/express.ts +++ b/packages/authentication-oauth/src/express.ts @@ -20,6 +20,7 @@ declare module 'express-session' { accessToken: string; query: { [key: string]: any }; grant: { [key: string]: any }; + headers: { [key: string]: any }; } } @@ -54,13 +55,14 @@ export default (options: OauthSetupSettings) => { } req.session.redirect = redirect as string; req.session.query = query; + req.session.headers = req.headers; next() }); authApp.get('/:name/authenticate', async (req: Request, res: Response, next: NextFunction) => { const { name } = req.params ; - const { accessToken, grant, query = {}, redirect } = req.session; + const { accessToken, grant, query = {}, redirect, headers } = req.session; const service = app.defaultAuthentication(authService); const [ strategy ] = service.getStrategies(name) as OAuthStrategy[]; const params = { @@ -71,7 +73,8 @@ export default (options: OauthSetupSettings) => { accessToken } : null, query, - redirect + redirect, + headers }; const sendResponse = async (data: AuthenticationResult|Error) => { try { diff --git a/packages/authentication-oauth/src/strategy.ts b/packages/authentication-oauth/src/strategy.ts index 7a552a56c8..108eab585f 100644 --- a/packages/authentication-oauth/src/strategy.ts +++ b/packages/authentication-oauth/src/strategy.ts @@ -67,9 +67,26 @@ export class OAuthStrategy extends AuthenticationBaseStrategy { return null; } + async getAllowedOrigin (params?: Params) { + const { redirect, origins } = this.authentication.configuration.oauth; + + if (Array.isArray(origins)) { + const referer = params?.headers?.referer || ''; + const allowedOrigin = origins.find(current => referer.toLowerCase().startsWith(current.toLowerCase())); + + if(!allowedOrigin) { + throw new NotAuthenticated(`Referer "${referer || '[header not available]'}" not allowed.`); + } + + return allowedOrigin; + } + + return redirect; + } + async getRedirect (data: AuthenticationResult|Error, params?: Params): Promise { const queryRedirect = (params && params.redirect) || ''; - const { redirect } = this.authentication.configuration.oauth; + const redirect = await this.getAllowedOrigin(params); if (!redirect) { return null; diff --git a/packages/authentication-oauth/test/strategy.test.ts b/packages/authentication-oauth/test/strategy.test.ts index 9e950a0650..64d5ede3d5 100644 --- a/packages/authentication-oauth/test/strategy.test.ts +++ b/packages/authentication-oauth/test/strategy.test.ts @@ -51,6 +51,43 @@ describe('@feathersjs/authentication-oauth/strategy', () => { assert.equal(redirect, '/#dashboard?access_token=testing'); }); + it('getRedirect with referrer and allowed origins (#2430)', async () => { + app.get('authentication').oauth.origins = [ + 'https://feathersjs.com', + 'https://feathers.cloud' + ]; + + let redirect = await strategy.getRedirect({ accessToken: 'testing' }, { + headers: { + referer: 'https://feathersjs.com/somewhere' + } + }); + assert.equal(redirect, 'https://feathersjs.com#access_token=testing'); + + redirect = await strategy.getRedirect({ accessToken: 'testing' }, { + headers: { + referer: 'HTTPS://feathers.CLOUD' + } + }); + assert.equal(redirect, 'https://feathers.cloud#access_token=testing'); + + redirect = await strategy.getRedirect({ accessToken: 'testing' }, { + redirect: '/home', + headers: { + referer: 'https://feathersjs.com/somewhere' + } + }); + assert.equal(redirect, 'https://feathersjs.com/home#access_token=testing'); + + await assert.rejects(() => strategy.getRedirect({ accessToken: 'testing' }, { + headers: { + referer: 'https://example.com' + } + }), { + message: 'Referer "https://example.com" not allowed.' + }); + }); + describe('authenticate', () => { it('with new user', async () => { const authResult = await strategy.authenticate({ diff --git a/packages/authentication/src/core.ts b/packages/authentication/src/core.ts index 93704af35a..0a6e7da740 100644 --- a/packages/authentication/src/core.ts +++ b/packages/authentication/src/core.ts @@ -155,6 +155,16 @@ export class AuthenticationBase { .filter(current => !!current); } + /** + * Returns a single strategy by name + * + * @param name The strategy name + * @returns The authentication strategy or undefined + */ + getStrategy (name: string) { + return this.strategies[name]; + } + /** * Create a new access token with payload and options. * diff --git a/packages/authentication/test/core.test.ts b/packages/authentication/test/core.test.ts index 538103b2b6..3471a0caeb 100644 --- a/packages/authentication/test/core.test.ts +++ b/packages/authentication/test/core.test.ts @@ -96,6 +96,12 @@ describe('authentication/core', () => { assert.strictEqual(invalid.length, 2, 'Filtered out invalid strategies'); }); + it('getStrategy', () => { + const first = auth.getStrategy('first'); + + assert.ok(first); + }); + it('calls setName, setApplication and setAuthentication if available', () => { const [ first ] = auth.getStrategies('first') as [ Strategy1 ];