From 2fff139cd7e6153a5baac38c37717ad51e671a95 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Fri, 4 Feb 2022 14:40:59 +0000 Subject: [PATCH] feat: allow passing device context to interact call OKTA-452538 <<>> Artifact: okta-auth-js Files changed count: 5 PR Link: "https://github.com/okta/okta-auth-js/pull/1093" --- CHANGELOG.md | 1 + lib/idx/interact.ts | 11 ++++-- package.json | 2 +- test/spec/idx/interact.ts | 76 ++++++++++++++++++++++++++++++++++++++- yarn.lock | 8 ++--- 5 files changed, 90 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1952a70f..49c9e7d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#1036](https://github.com/okta/okta-auth-js/pull/1036) Adds `webauthn` authenticator support in idx module - [#1075](https://github.com/okta/okta-auth-js/pull/1075) Adds top level `invokeApiMethod` method as an escape hatch to make arbitrary OKTA API request +- [#1093](https://github.com/okta/okta-auth-js/pull/1093) Allows passing device context headers (`X-Forwarded-For`, `User-Agent`, `X-Okta-User-Agent-Extended` and `X-Device-Token`) to `idx.interact`. Follow [setHeaders](README.md#setheaders) section to add headers to http requests. ### Fixes diff --git a/lib/idx/interact.ts b/lib/idx/interact.ts index 787d44c33..e11e9cf50 100644 --- a/lib/idx/interact.ts +++ b/lib/idx/interact.ts @@ -26,6 +26,7 @@ export interface InteractOptions { codeChallengeMethod?: string; activationToken?: string; recoveryToken?: string; + clientSecret?: string; } export interface InteractResponse { @@ -65,8 +66,9 @@ export async function interact (authClient: OktaAuth, options: InteractOptions = codeChallenge, codeChallengeMethod, activationToken, - recoveryToken + recoveryToken, } = meta as IdxTransactionMeta; + const clientSecret = options.clientSecret || authClient.options.clientSecret; const interactionHandle = await idx.interact({ withCredentials, @@ -86,7 +88,12 @@ export async function interact (authClient: OktaAuth, options: InteractOptions = activationToken, // Recovery - recoveryToken + recoveryToken, + + // X-Device-Token header need to pair with `client_secret` + // eslint-disable-next-line max-len + // https://oktawiki.atlassian.net/wiki/spaces/eng/pages/2445902453/Support+Device+Binding+in+interact#Scenario-1%3A-Non-User-Agent-with-Confidential-Client-(top-priority) + clientSecret }); const newMeta = { ...meta, diff --git a/package.json b/package.json index c4d26780c..050311c49 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ }, "dependencies": { "@babel/runtime": "^7.12.5", - "@okta/okta-idx-js": "0.24.0", + "@okta/okta-idx-js": "0.25.0", "@peculiar/webcrypto": "1.1.6", "Base64": "1.1.0", "atob": "^2.1.2", diff --git a/test/spec/idx/interact.ts b/test/spec/idx/interact.ts index 6bc966950..dd4f21830 100644 --- a/test/spec/idx/interact.ts +++ b/test/spec/idx/interact.ts @@ -22,8 +22,9 @@ jest.mock('@okta/okta-idx-js', () => { }); jest.mock('../../../lib/idx/transactionMeta', () => { + const actual = jest.requireActual('../../../lib/idx/transactionMeta'); return { - createTransactionMeta: () => {}, + ...actual, getSavedTransactionMeta: () => {}, saveTransactionMeta: () => {} }; @@ -391,6 +392,79 @@ describe('idx/interact', () => { }); }); }); + + describe('clientSecret', () => { + beforeEach(() => { + // use original createTransactionMeta implementation + jest.spyOn(mocked.transactionMeta, 'createTransactionMeta').mockRestore(); + jest.spyOn(mocked.transactionMeta, 'saveTransactionMeta'); + }); + + it('uses clientSecret from sdk options', async () => { + const { authClient } = testContext; + authClient.options.clientSecret = 'sdk-clientSecret'; + await interact(authClient, {}); + expect(mocked.idx.interact).toHaveBeenCalled(); + const interactArg = mocked.idx.interact.mock.calls[0][0]; + expect(interactArg).toMatchObject({ + 'clientId': 'authClient-clientId', + 'baseUrl': 'authClient-issuer/oauth2', + 'codeChallenge': 'tp-codeChallenge', + 'codeChallengeMethod': 'tp-codeChallengeMethod', + 'redirectUri': 'authClient-redirectUri', + 'scopes': ['tp-scopes'], + 'state': 'tp-state', + 'clientSecret': 'sdk-clientSecret' + }); + expect(mocked.transactionMeta.saveTransactionMeta).toHaveBeenCalledWith(authClient, { + 'clientId': 'authClient-clientId', + 'codeChallenge': 'tp-codeChallenge', + 'codeChallengeMethod': 'tp-codeChallengeMethod', + 'codeVerifier': 'tp-codeVerifier', + 'flow': 'default', + 'interactionHandle': 'idx-interactionHandle', + 'issuer': 'authClient-issuer', + 'redirectUri': 'authClient-redirectUri', + 'responseType': 'tp-responseType', + 'scopes': ['tp-scopes'], + 'state': 'tp-state', + 'urls': expect.any(Object), + 'withCredentials': true, + }); + }); + it('uses clientSecret from function options (overrides sdk option)', async () => { + const { authClient } = testContext; + authClient.options.clientSecret = 'sdk-clientSecret'; + await interact(authClient, { clientSecret: 'fn-clientSecret' }); + expect(mocked.idx.interact).toHaveBeenCalled(); + const functionArg = mocked.idx.interact.mock.calls[0][0]; + expect(functionArg).toMatchObject({ + 'clientId': 'authClient-clientId', + 'baseUrl': 'authClient-issuer/oauth2', + 'codeChallenge': 'tp-codeChallenge', + 'codeChallengeMethod': 'tp-codeChallengeMethod', + 'redirectUri': 'authClient-redirectUri', + 'scopes': ['tp-scopes'], + 'state': 'tp-state', + 'clientSecret': 'fn-clientSecret' + }); + expect(mocked.transactionMeta.saveTransactionMeta).toHaveBeenCalledWith(authClient, { + 'clientId': 'authClient-clientId', + 'codeChallenge': 'tp-codeChallenge', + 'codeChallengeMethod': 'tp-codeChallengeMethod', + 'codeVerifier': 'tp-codeVerifier', + 'flow': 'default', + 'interactionHandle': 'idx-interactionHandle', + 'issuer': 'authClient-issuer', + 'redirectUri': 'authClient-redirectUri', + 'responseType': 'tp-responseType', + 'scopes': ['tp-scopes'], + 'state': 'tp-state', + 'urls': expect.any(Object), + 'withCredentials': true, + }); + }); + }); it('saves returned interactionHandle', async () => { const { authClient } = testContext; diff --git a/yarn.lock b/yarn.lock index b8083bb53..60a06eb6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2364,10 +2364,10 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@okta/okta-idx-js@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@okta/okta-idx-js/-/okta-idx-js-0.24.0.tgz#f7c048a866add49f40fde0e25734fe61998be7c3" - integrity sha512-ut4LPYvsXKKQm+PVgsGuoeU1RzVteNHrZPYvhz7REJEaNkY7pKUPBmYxsq9zyjRyzEEkAH7YrWeyhMcW8Lpt9w== +"@okta/okta-idx-js@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@okta/okta-idx-js/-/okta-idx-js-0.25.0.tgz#4ca1d1a9b5617cbdc5374965caea9949b5b22cbc" + integrity sha512-cNOYSHCzLR5NKqiBsvPYYuWYPerLIVva6SNF7+/vb4zH1/hBjznlj/W6guVl0m5Tycz/ZUuVSQzHtEQoock/uQ== dependencies: "@babel/runtime" "^7.12.5" "@babel/runtime-corejs3" "^7.12.5"