From c4e9ef28680fad611b01ab3579cf4b6b3f19a8b8 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Thu, 3 Feb 2022 14:57:40 -0500 Subject: [PATCH 1/7] feat: allow passing device context to interact call --- lib/idx/interact.ts | 12 +++++++++--- package.json | 2 +- test/spec/idx/interact.ts | 33 +++++++++++++++++++++++++++++++++ yarn.lock | 8 ++++---- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/lib/idx/interact.ts b/lib/idx/interact.ts index 787d44c33..ec3c66e69 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 - } = meta as IdxTransactionMeta; + recoveryToken, + clientSecret + } = meta as IdxTransactionMeta & InteractOptions; const interactionHandle = await idx.interact({ withCredentials, @@ -86,7 +88,11 @@ export async function interact (authClient: OktaAuth, options: InteractOptions = activationToken, // Recovery - recoveryToken + recoveryToken, + + // X-Device-Token header need to paire with `client_secret` + // 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..b4a0cd343 100644 --- a/test/spec/idx/interact.ts +++ b/test/spec/idx/interact.ts @@ -391,6 +391,39 @@ describe('idx/interact', () => { }); }); }); + + describe('clientSecret', () => { + it('uses clientSecret from sdk options', async () => { + const { authClient } = testContext; + authClient.options.clientSecret = 'sdk-clientSecret'; + const res = await interact(authClient, {}); + expect(mocked.idx.interact).toHaveBeenCalledWith({ + 'clientId': 'authClient-clientId', + 'baseUrl': 'authClient-issuer/oauth2', + 'codeChallenge': 'tp-codeChallenge', + 'codeChallengeMethod': 'tp-codeChallengeMethod', + 'redirectUri': 'authClient-redirectUri', + 'scopes': ['authClient'], + 'state': 'authClient-state', + 'clientSecret': 'sdk-clientSecret' + }); + }); + it('uses clientSecret from function options (overrides sdk option)', async () => { + const { authClient } = testContext; + authClient.options.clientSecret = 'sdk-clientSecret'; + const res = await interact(authClient, { clientSecret: 'fn-clientSecret' }); + expect(mocked.idx.interact).toHaveBeenCalledWith({ + 'clientId': 'authClient-clientId', + 'baseUrl': 'authClient-issuer/oauth2', + 'codeChallenge': 'tp-codeChallenge', + 'codeChallengeMethod': 'tp-codeChallengeMethod', + 'redirectUri': 'authClient-redirectUri', + 'scopes': ['authClient'], + 'state': 'authClient-state', + 'clientSecret': 'fn-clientSecret' + }); + }); + }); it('saves returned interactionHandle', async () => { const { authClient } = testContext; diff --git a/yarn.lock b/yarn.lock index b8083bb53..9fa8c2df8 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", "@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" From 5fd62d1c2a4b705c3a35dec039633e84f1a699dc Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Thu, 3 Feb 2022 15:06:38 -0500 Subject: [PATCH 2/7] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 9f4e0b44d1283d622c28081c1902e7b3b4e8888d Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Thu, 3 Feb 2022 16:39:11 -0500 Subject: [PATCH 3/7] resolve comments --- lib/idx/interact.ts | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/idx/interact.ts b/lib/idx/interact.ts index ec3c66e69..1dad4f9c9 100644 --- a/lib/idx/interact.ts +++ b/lib/idx/interact.ts @@ -90,7 +90,7 @@ export async function interact (authClient: OktaAuth, options: InteractOptions = // Recovery recoveryToken, - // X-Device-Token header need to paire with `client_secret` + // X-Device-Token header need to pair with `client_secret` // 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 }); diff --git a/yarn.lock b/yarn.lock index 9fa8c2df8..60a06eb6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2364,7 +2364,7 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@okta/okta-idx-js@0.25.0", "@okta/okta-idx-js@^0.25.0": +"@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== From cdff81dff90702e0611ee585d0dd8ec74806bb77 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Thu, 3 Feb 2022 17:14:04 -0500 Subject: [PATCH 4/7] fix not passing clientSecret --- lib/idx/interact.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/idx/interact.ts b/lib/idx/interact.ts index 1dad4f9c9..56ff02a13 100644 --- a/lib/idx/interact.ts +++ b/lib/idx/interact.ts @@ -67,8 +67,8 @@ export async function interact (authClient: OktaAuth, options: InteractOptions = codeChallengeMethod, activationToken, recoveryToken, - clientSecret - } = meta as IdxTransactionMeta & InteractOptions; + } = meta as IdxTransactionMeta; + const clientSecret = options.clientSecret || authClient.options.clientSecret; const interactionHandle = await idx.interact({ withCredentials, From 6778250bd8612105ceaaf97bee3337a87136692a Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Thu, 3 Feb 2022 18:29:35 -0500 Subject: [PATCH 5/7] fix mock and add test against save meta --- lib/idx/interact.ts | 1 + test/spec/idx/interact.ts | 59 +++++++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/lib/idx/interact.ts b/lib/idx/interact.ts index 56ff02a13..e11e9cf50 100644 --- a/lib/idx/interact.ts +++ b/lib/idx/interact.ts @@ -91,6 +91,7 @@ export async function interact (authClient: OktaAuth, options: InteractOptions = 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 }); diff --git a/test/spec/idx/interact.ts b/test/spec/idx/interact.ts index b4a0cd343..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: () => {} }; @@ -393,35 +394,75 @@ 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'; - const res = await interact(authClient, {}); - expect(mocked.idx.interact).toHaveBeenCalledWith({ + 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': ['authClient'], - 'state': 'authClient-state', + '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'; - const res = await interact(authClient, { clientSecret: 'fn-clientSecret' }); - expect(mocked.idx.interact).toHaveBeenCalledWith({ + 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': ['authClient'], - 'state': 'authClient-state', + '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, + }); }); }); From 6f57d4bbbc4341eece06d1c4913b1aaba8819d6f Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Thu, 3 Feb 2022 18:36:33 -0500 Subject: [PATCH 6/7] catch async errors in sample app --- .../generated/express-embedded-auth-with-sdk/package.json | 1 + .../express-embedded-auth-with-sdk/web-server/server.js | 1 + .../templates/express-embedded-auth-with-sdk/package.json | 1 + .../express-embedded-auth-with-sdk/web-server/server.js | 1 + yarn.lock | 5 +++++ 5 files changed, 9 insertions(+) diff --git a/samples/generated/express-embedded-auth-with-sdk/package.json b/samples/generated/express-embedded-auth-with-sdk/package.json index 5721dcdb5..0c09f4e98 100644 --- a/samples/generated/express-embedded-auth-with-sdk/package.json +++ b/samples/generated/express-embedded-auth-with-sdk/package.json @@ -17,6 +17,7 @@ "cors": "^2.8.5", "js-yaml": "^4.1.0", "dotenv": "^10.0.0", + "express-async-errors": "^3.1.1", "@okta/okta-auth-js": "*" }, "devDependencies": { diff --git a/samples/generated/express-embedded-auth-with-sdk/web-server/server.js b/samples/generated/express-embedded-auth-with-sdk/web-server/server.js index 05ca016e6..8f49a9fd9 100644 --- a/samples/generated/express-embedded-auth-with-sdk/web-server/server.js +++ b/samples/generated/express-embedded-auth-with-sdk/web-server/server.js @@ -18,6 +18,7 @@ const express = require('express'); const session = require('express-session'); const mustacheExpress = require('mustache-express'); const path = require('path'); +require('express-async-errors'); const { userContext, authTransaction, diff --git a/samples/templates/express-embedded-auth-with-sdk/package.json b/samples/templates/express-embedded-auth-with-sdk/package.json index 7dea536b4..144da6a17 100644 --- a/samples/templates/express-embedded-auth-with-sdk/package.json +++ b/samples/templates/express-embedded-auth-with-sdk/package.json @@ -17,6 +17,7 @@ "cors": "^2.8.5", "js-yaml": "^4.1.0", "dotenv": "^10.0.0", + "express-async-errors": "^3.1.1", "@okta/okta-auth-js": "*" }, "devDependencies": { diff --git a/samples/templates/express-embedded-auth-with-sdk/web-server/server.js b/samples/templates/express-embedded-auth-with-sdk/web-server/server.js index 05ca016e6..8f49a9fd9 100644 --- a/samples/templates/express-embedded-auth-with-sdk/web-server/server.js +++ b/samples/templates/express-embedded-auth-with-sdk/web-server/server.js @@ -18,6 +18,7 @@ const express = require('express'); const session = require('express-session'); const mustacheExpress = require('mustache-express'); const path = require('path'); +require('express-async-errors'); const { userContext, authTransaction, diff --git a/yarn.lock b/yarn.lock index 60a06eb6b..32c89b5c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9052,6 +9052,11 @@ expect@^27.0.2: jest-message-util "^27.0.6" jest-regex-util "^27.0.6" +express-async-errors@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/express-async-errors/-/express-async-errors-3.1.1.tgz#6053236d61d21ddef4892d6bd1d736889fc9da41" + integrity sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng== + express-session@^1.17.1: version "1.17.2" resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.2.tgz#397020374f9bf7997f891b85ea338767b30d0efd" From a8827b8baee6d785c91e0366a29c19a87b04e058 Mon Sep 17 00:00:00 2001 From: Shuo Wu Date: Fri, 4 Feb 2022 09:20:10 -0500 Subject: [PATCH 7/7] Revert "catch async errors in sample app" This reverts commit 6f57d4bbbc4341eece06d1c4913b1aaba8819d6f. --- .../generated/express-embedded-auth-with-sdk/package.json | 1 - .../express-embedded-auth-with-sdk/web-server/server.js | 1 - .../templates/express-embedded-auth-with-sdk/package.json | 1 - .../express-embedded-auth-with-sdk/web-server/server.js | 1 - yarn.lock | 5 ----- 5 files changed, 9 deletions(-) diff --git a/samples/generated/express-embedded-auth-with-sdk/package.json b/samples/generated/express-embedded-auth-with-sdk/package.json index 0c09f4e98..5721dcdb5 100644 --- a/samples/generated/express-embedded-auth-with-sdk/package.json +++ b/samples/generated/express-embedded-auth-with-sdk/package.json @@ -17,7 +17,6 @@ "cors": "^2.8.5", "js-yaml": "^4.1.0", "dotenv": "^10.0.0", - "express-async-errors": "^3.1.1", "@okta/okta-auth-js": "*" }, "devDependencies": { diff --git a/samples/generated/express-embedded-auth-with-sdk/web-server/server.js b/samples/generated/express-embedded-auth-with-sdk/web-server/server.js index 8f49a9fd9..05ca016e6 100644 --- a/samples/generated/express-embedded-auth-with-sdk/web-server/server.js +++ b/samples/generated/express-embedded-auth-with-sdk/web-server/server.js @@ -18,7 +18,6 @@ const express = require('express'); const session = require('express-session'); const mustacheExpress = require('mustache-express'); const path = require('path'); -require('express-async-errors'); const { userContext, authTransaction, diff --git a/samples/templates/express-embedded-auth-with-sdk/package.json b/samples/templates/express-embedded-auth-with-sdk/package.json index 144da6a17..7dea536b4 100644 --- a/samples/templates/express-embedded-auth-with-sdk/package.json +++ b/samples/templates/express-embedded-auth-with-sdk/package.json @@ -17,7 +17,6 @@ "cors": "^2.8.5", "js-yaml": "^4.1.0", "dotenv": "^10.0.0", - "express-async-errors": "^3.1.1", "@okta/okta-auth-js": "*" }, "devDependencies": { diff --git a/samples/templates/express-embedded-auth-with-sdk/web-server/server.js b/samples/templates/express-embedded-auth-with-sdk/web-server/server.js index 8f49a9fd9..05ca016e6 100644 --- a/samples/templates/express-embedded-auth-with-sdk/web-server/server.js +++ b/samples/templates/express-embedded-auth-with-sdk/web-server/server.js @@ -18,7 +18,6 @@ const express = require('express'); const session = require('express-session'); const mustacheExpress = require('mustache-express'); const path = require('path'); -require('express-async-errors'); const { userContext, authTransaction, diff --git a/yarn.lock b/yarn.lock index 32c89b5c3..60a06eb6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9052,11 +9052,6 @@ expect@^27.0.2: jest-message-util "^27.0.6" jest-regex-util "^27.0.6" -express-async-errors@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/express-async-errors/-/express-async-errors-3.1.1.tgz#6053236d61d21ddef4892d6bd1d736889fc9da41" - integrity sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng== - express-session@^1.17.1: version "1.17.2" resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.2.tgz#397020374f9bf7997f891b85ea338767b30d0efd"