diff --git a/lib/index.d.ts b/lib/index.d.ts index 9cef179..b12410d 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -88,7 +88,7 @@ export interface OptionalOptions { * * Twitter supports `force_login`, `screen_name`. * * Linkedin supports `fields`. */ - providerParams?: StringLikeMap | ((request: Request) => StringLikeMap) | undefined; + providerParams?: StringLikeMap | ((request: Request) => StringLikeMap | Promise) | undefined; /** * allows passing query parameters from a bell protected endpoint to the auth request. * It will merge the query params you pass along with the providerParams and any other predefined ones. @@ -129,7 +129,7 @@ export interface OptionalOptions { * It may be passed either as an object to merge into the query string, * or a function which takes the client's request and returns an object. */ - tokenParams?: StringLikeMap | ((request: Request) => StringLikeMap) | undefined; + tokenParams?: StringLikeMap | ((request: Request) => StringLikeMap | Promise) | undefined; /** * an object of key-value pairs that specify additional * URL query parameters to send with the profile request to the provider. @@ -307,4 +307,4 @@ export function simulate(credentialsFunc: RequestPassThrough): void; * [See docs](https://github.com/hapijs/bell/blob/master/API.md#simulated-authentication) * Disables simulation mode */ -export function simulate(state: false): void; \ No newline at end of file +export function simulate(state: false): void; diff --git a/lib/oauth.js b/lib/oauth.js index 408b349..c5ed55e 100755 --- a/lib/oauth.js +++ b/lib/oauth.js @@ -73,7 +73,7 @@ exports.v1 = function (settings) { h.state(cookie, state); const authQuery = { - ...internals.resolveProviderParams(request, settings.providerParams), + ...await internals.resolveProviderParams(request, settings.providerParams), oauth_token: temp.oauth_token, ...(settings.allowRuntimeProviderParams && request.query) }; @@ -178,7 +178,7 @@ exports.v2 = function (settings) { const nonce = Cryptiles.randomAlphanumString(internals.nonceLength); const query = { - ...internals.resolveProviderParams(request, settings.providerParams), + ...await internals.resolveProviderParams(request, settings.providerParams), ...(settings.allowRuntimeProviderParams && request.query), client_id: settings.clientId, response_type: 'code', @@ -249,7 +249,7 @@ exports.v2 = function (settings) { grant_type: 'authorization_code', code: request.query.code, redirect_uri: internals.location(request, protocol, settings.location), - ...internals.resolveProviderParams(request, settings.tokenParams) + ...await internals.resolveProviderParams(request, settings.tokenParams) }; if (settings.provider.pkce) { @@ -727,7 +727,7 @@ internals.getProtocol = function (request, settings) { }; -internals.resolveProviderParams = function (request, params) { +internals.resolveProviderParams = async function (request, params) { - return (typeof params === 'function' ? params(request) : params) ?? {}; + return (typeof params === 'function' ? await params(request) : params) ?? {}; }; diff --git a/test/oauth.js b/test/oauth.js index 0c8506f..9f321eb 100755 --- a/test/oauth.js +++ b/test/oauth.js @@ -1307,6 +1307,84 @@ describe('Bell', () => { expect(res3.result).to.include({ endpoint: 'https://test.com' }); }); + it('authenticates an endpoint token provider parameters as a sync function', async (flags) => { + + const mock = await Mock.v2(flags); + const server = Hapi.server({ host: 'localhost', port: 8080 }); + await server.register(Bell); + + server.auth.strategy('custom', 'bell', { + password: 'cookie_encryption_password_secure', + isSecure: false, + clientId: 'endpoint', + clientSecret: 'secret', + provider: mock.provider, + tokenParams: (_request) => ({ endpoint: 'https://sync.example.com' }) + }); + + server.route({ + method: '*', + path: '/login', + options: { + auth: 'custom', + handler: function (request, h) { + + return request.auth.artifacts; + } + } + }); + + const res1 = await server.inject('/login'); + const cookie = res1.headers['set-cookie'][0].split(';')[0] + ';'; + + const res2 = await mock.server.inject(res1.headers.location); + + const res3 = await server.inject({ url: res2.headers.location, headers: { cookie } }); + expect(res3.statusCode).to.equal(200); + expect(res3.result).to.include({ endpoint: 'https://sync.example.com' }); + }); + + it('authenticates an endpoint token provider parameters as an async function', async (flags) => { + + const mock = await Mock.v2(flags); + const server = Hapi.server({ host: 'localhost', port: 8080 }); + await server.register(Bell); + + server.auth.strategy('custom', 'bell', { + password: 'cookie_encryption_password_secure', + isSecure: false, + clientId: 'endpoint', + clientSecret: 'secret', + provider: mock.provider, + tokenParams: async (_request) => { + + await new Promise((resolve) => setTimeout(resolve, 1)); + return { endpoint: 'https://async.example.com' }; + } + }); + + server.route({ + method: '*', + path: '/login', + options: { + auth: 'custom', + handler: function (request, h) { + + return request.auth.artifacts; + } + } + }); + + const res1 = await server.inject('/login'); + const cookie = res1.headers['set-cookie'][0].split(';')[0] + ';'; + + const res2 = await mock.server.inject(res1.headers.location); + + const res3 = await server.inject({ url: res2.headers.location, headers: { cookie } }); + expect(res3.statusCode).to.equal(200); + expect(res3.result).to.include({ endpoint: 'https://async.example.com' }); + }); + it('authenticates an endpoint via oauth with plain PKCE', async (flags) => { const mock = await Mock.v2(flags);