diff --git a/api-specs/openrpc-user-api.json b/api-specs/openrpc-user-api.json index 7e7aeeb99..9b16cb999 100644 --- a/api-specs/openrpc-user-api.json +++ b/api-specs/openrpc-user-api.json @@ -138,9 +138,14 @@ "title": "clientId", "type": "string", "description": "Client ID used as the JWT subject" + }, + "clientSecret": { + "title": "clientSecret", + "type": "string", + "description": "Client secret that must match the selected network's auth.clientSecret" } }, - "required": ["networkId", "clientId"] + "required": ["networkId", "clientId", "clientSecret"] } } ], diff --git a/core/wallet-test-utils/src/wallet-gateway.ts b/core/wallet-test-utils/src/wallet-gateway.ts index c9becd51a..4a900256c 100644 --- a/core/wallet-test-utils/src/wallet-gateway.ts +++ b/core/wallet-test-utils/src/wallet-gateway.ts @@ -54,6 +54,11 @@ export type WalletGatewayArgs = page: Page } +export type LoginCredentials = { + clientId?: string + clientSecret?: string +} + export class WalletGateway { private readonly isPopup: boolean private readonly dappPage: Page | undefined @@ -105,6 +110,7 @@ export class WalletGateway { async connect(args: { network: string customURL?: string + credentials?: LoginCredentials }): Promise { await test.step(`wallet gateway: connect to ${args.network}`, async () => { const dapp = this.requireDapp() @@ -127,6 +133,7 @@ export class WalletGateway { 'the wallet gateway has a network select' ).toBeVisible() await selectNetwork.selectOption({ label: args.network }) + await this.fillLoginCredentials(popup, args.credentials) const confirmConnectButton = popup.getByRole('button', { name: 'Connect', }) @@ -533,8 +540,36 @@ export class WalletGateway { throw new Error('wallet connect form popup did not appear') } + private async fillLoginCredentials( + page: Page, + credentials?: LoginCredentials + ): Promise { + const clientId = credentials?.clientId + if (clientId !== undefined) { + const clientIdInput = page.getByLabel('Client ID') + await expect( + clientIdInput, + 'Client ID was provided but the login form has no Client ID input' + ).toBeVisible() + await clientIdInput.fill(clientId) + } + + const clientSecret = credentials?.clientSecret + if (clientSecret !== undefined) { + const clientSecretInput = page.getByLabel('Client Secret') + await expect( + clientSecretInput, + 'Client Secret was provided but the login form has no Client Secret input' + ).toBeVisible() + await clientSecretInput.fill(clientSecret) + } + } + // Logs in to a gateway that was opened directly, without a dApp. - async login(network: string): Promise { + async login( + network: string, + credentials?: LoginCredentials + ): Promise { await test.step(`wallet gateway: log in to ${network}`, async () => { const page = await this.page() const selectNetwork = page.getByLabel('Select a network') @@ -543,6 +578,7 @@ export class WalletGateway { 'the wallet gateway has a network select' ).toBeVisible() await selectNetwork.selectOption({ label: network }) + await this.fillLoginCredentials(page, credentials) await page.getByRole('button', { name: 'Connect' }).click() // Wait for the OAuth redirect chain to complete and land on the parties page. await page.waitForURL(/\/parties/, { timeout: 30000 }) diff --git a/core/wallet-ui-components/src/components/login-form.test.ts b/core/wallet-ui-components/src/components/login-form.test.ts index cadaa7ec0..e58ca0fc4 100644 --- a/core/wallet-ui-components/src/components/login-form.test.ts +++ b/core/wallet-ui-components/src/components/login-form.test.ts @@ -178,6 +178,62 @@ describe('wg-login-form', () => { const event = listener.mock.calls[0][0] as LoginConnectEvent expect(event.selectedNetwork).toBe(network) expect(event.selectedIdp).toBe(idp) + expect(event.clientId).toBeUndefined() + expect(event.clientSecret).toBeUndefined() + }) + + it('emits the entered client secret for self-signed identity providers', async () => { + const network = makePublicNetwork({ + identityProviderId: 'idp-1', + clientId: 'client-id', + }) + const idp = makeIdp({ id: 'idp-1', type: 'self_signed' }) + + const el = await fixture( + html`` + ) + + const secretInput = + el.shadowRoot!.querySelector('#client-secret')! + expect(secretInput.type).toBe('password') + secretInput.value = 'network-secret' + + const listener = vi.fn() + el.addEventListener('login-connect', listener) + + el.shadowRoot!.querySelector('.connect-btn')!.click() + + expect(listener).toHaveBeenCalledOnce() + const event = listener.mock.calls[0][0] as LoginConnectEvent expect(event.clientId).toBe('client-id') + expect(event.clientSecret).toBe('network-secret') + }) + + it('submits on form submit event', async () => { + const network = makePublicNetwork({ + identityProviderId: 'idp-1', + clientId: 'client-id', + }) + const idp = makeIdp({ id: 'idp-1' }) + + const el = await fixture( + html`` + ) + + const listener = vi.fn() + el.addEventListener('login-connect', listener) + + el.shadowRoot!.querySelector('form')!.dispatchEvent( + new Event('submit', { bubbles: true, cancelable: true }) + ) + + expect(listener).toHaveBeenCalledOnce() + expect(listener.mock.calls[0][0]).toBeInstanceOf(LoginConnectEvent) }) }) diff --git a/core/wallet-ui-components/src/components/login-form.ts b/core/wallet-ui-components/src/components/login-form.ts index 2c9e051db..7e56634e4 100644 --- a/core/wallet-ui-components/src/components/login-form.ts +++ b/core/wallet-ui-components/src/components/login-form.ts @@ -14,7 +14,8 @@ export class LoginConnectEvent extends Event { constructor( public selectedNetwork: PublicNetwork, public selectedIdp: Idp, - public clientId: string + public clientId?: string, + public clientSecret?: string ) { super('login-connect', { bubbles: true, composed: true }) } @@ -99,7 +100,7 @@ export class WgLoginForm extends BaseElement { } .network-select, - .client-id-input { + .login-input { width: 100%; border: 1px solid #d4d4d8; border-radius: 4px; @@ -112,7 +113,7 @@ export class WgLoginForm extends BaseElement { } .network-select:focus, - .client-id-input:focus { + .login-input:focus { border-color: var(--wg-input-border-focus); box-shadow: 0 0 0 3px rgba(var(--wg-accent-rgb), 0.12); } @@ -215,18 +216,40 @@ export class WgLoginForm extends BaseElement { return } - const clientId = - ( - this.renderRoot.querySelector( - '#client-id' - ) as HTMLInputElement | null - )?.value || this.selectedNetwork.clientId + let clientId: string | undefined + let clientSecret: string | undefined + + if (idp.type === 'self_signed') { + clientId = + ( + this.renderRoot.querySelector( + '#client-id' + ) as HTMLInputElement | null + )?.value || this.selectedNetwork.clientId + + clientSecret = + ( + this.renderRoot.querySelector( + '#client-secret' + ) as HTMLInputElement | null + )?.value ?? '' + } this.dispatchEvent( - new LoginConnectEvent(this.selectedNetwork, idp, clientId || '') + new LoginConnectEvent( + this.selectedNetwork, + idp, + clientId, + clientSecret + ) ) } + private handleSubmit(e: Event) { + e.preventDefault() + this.handleConnect() + } + /** Set a status message on the form (e.g. "Redirecting...") */ setMessage(message: string, type: 'error' | 'info') { this.message = message @@ -241,7 +264,7 @@ export class WgLoginForm extends BaseElement { protected render() { return html` -
+
@@ -293,11 +316,24 @@ export class WgLoginForm extends BaseElement { > + + ` : null } @@ -329,8 +365,8 @@ export class WgLoginForm extends BaseElement { -
+ ` } } diff --git a/core/wallet-user-rpc-client/src/index.ts b/core/wallet-user-rpc-client/src/index.ts index add53d218..1b41ff715 100644 --- a/core/wallet-user-rpc-client/src/index.ts +++ b/core/wallet-user-rpc-client/src/index.ts @@ -511,6 +511,7 @@ export interface GetNetworkParams { export interface SelfSignedAccessTokenParams { networkId: NetworkId clientId: ClientId + clientSecret: ClientSecret } export interface AddIdpParams { idp: Idp diff --git a/core/wallet-user-rpc-client/src/openrpc.json b/core/wallet-user-rpc-client/src/openrpc.json index 7e7aeeb99..9b16cb999 100644 --- a/core/wallet-user-rpc-client/src/openrpc.json +++ b/core/wallet-user-rpc-client/src/openrpc.json @@ -138,9 +138,14 @@ "title": "clientId", "type": "string", "description": "Client ID used as the JWT subject" + }, + "clientSecret": { + "title": "clientSecret", + "type": "string", + "description": "Client secret that must match the selected network's auth.clientSecret" } }, - "required": ["networkId", "clientId"] + "required": ["networkId", "clientId", "clientSecret"] } } ], diff --git a/examples/portfolio/tests/allocation.spec.ts b/examples/portfolio/tests/allocation.spec.ts index 0c0213906..087656b09 100644 --- a/examples/portfolio/tests/allocation.spec.ts +++ b/examples/portfolio/tests/allocation.spec.ts @@ -6,6 +6,7 @@ import { expect, type Page, test } from '@playwright/test' import { OTCTrade } from '@canton-network/core-wallet-test-utils' import { createWalletGateway, + connectToLocalNet, gotoConnect, setupRegistry, switchWallet, @@ -69,7 +70,7 @@ const setupOtcTrade = async (page: Page) => { const wg = createWalletGateway(page) await gotoConnect(page) - await wg.connect({ network: 'LocalNet' }) + await connectToLocalNet(wg) const venueHint = `venue-${rnd}` const aliceHint = `alice-${rnd}` diff --git a/examples/portfolio/tests/preapprovals.spec.ts b/examples/portfolio/tests/preapprovals.spec.ts index 4437d37c7..9a7cb0dff 100644 --- a/examples/portfolio/tests/preapprovals.spec.ts +++ b/examples/portfolio/tests/preapprovals.spec.ts @@ -4,6 +4,7 @@ import { test } from '@playwright/test' import { createWalletGateway, + connectToLocalNet, expectTransferOfferGone, expectWalletBalance, fillAndSubmitTransfer, @@ -40,7 +41,7 @@ test('toggle preapproval', async ({ page: dappPage }) => { const wg = createWalletGateway(dappPage) await gotoConnect(dappPage) - await wg.connect({ network: 'LocalNet' }) + await connectToLocalNet(wg) const alice = await wg.createWalletIfNotExists({ partyHint: `alice-${rnd}`, @@ -69,7 +70,7 @@ test('one step transfer to preapproved receiver', async ({ const wg = createWalletGateway(dappPage) await gotoConnect(dappPage) - await wg.connect({ network: 'LocalNet' }) + await connectToLocalNet(wg) const alice = await wg.createWalletIfNotExists({ partyHint: `alice-${rnd}`, diff --git a/examples/portfolio/tests/settings.spec.ts b/examples/portfolio/tests/settings.spec.ts index f23f33dc5..b73c415ea 100644 --- a/examples/portfolio/tests/settings.spec.ts +++ b/examples/portfolio/tests/settings.spec.ts @@ -7,6 +7,7 @@ import { toPortfolioInstrument } from '../src/types/instruments' import { normalizeRegistryUrl } from '../src/utils/registry' import { createWalletGateway, + connectToLocalNet, expectWalletBalance, gotoConnect, setupRegistry, @@ -24,7 +25,7 @@ const connectToSettings = async (page: Page) => { const wg = createWalletGateway(page) await gotoConnect(page) - await wg.connect({ network: 'LocalNet' }) + await connectToLocalNet(wg) await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible({ timeout: 15000, }) @@ -250,7 +251,7 @@ test('tap via settings page', async ({ page: dappPage }) => { const wg = createWalletGateway(dappPage) await gotoConnect(dappPage) - await wg.connect({ network: 'LocalNet' }) + await connectToLocalNet(wg) const alice = await wg.createWalletIfNotExists({ partyHint: `alice-${rnd}`, diff --git a/examples/portfolio/tests/transaction-history.spec.ts b/examples/portfolio/tests/transaction-history.spec.ts index 266934f5f..2919451c9 100644 --- a/examples/portfolio/tests/transaction-history.spec.ts +++ b/examples/portfolio/tests/transaction-history.spec.ts @@ -5,6 +5,7 @@ import { expect, type Locator, type Page, test } from '@playwright/test' import { fundValidatorOperator } from './fund-validator' import { createWalletGateway, + connectToLocalNet, fillAndSubmitTransfer, gotoConnect, gotoDashboard, @@ -131,7 +132,7 @@ test('shows taps, direct transfers, and transfer offers for both parties', async const wg = createWalletGateway(dappPage) await gotoConnect(dappPage) - await wg.connect({ network: 'LocalNet' }) + await connectToLocalNet(wg) const alice = await wg.createWalletIfNotExists({ partyHint: aliceHint, diff --git a/examples/portfolio/tests/transfers.spec.ts b/examples/portfolio/tests/transfers.spec.ts index 56719f166..60cdf75a9 100644 --- a/examples/portfolio/tests/transfers.spec.ts +++ b/examples/portfolio/tests/transfers.spec.ts @@ -5,6 +5,7 @@ import { test, expect, Page } from '@playwright/test' import { WalletGateway } from '@canton-network/core-wallet-test-utils' import { createWalletGateway, + connectToLocalNet, expectOffersBadgeCount, expectTransferOfferGone, expectWalletBalance, @@ -39,7 +40,7 @@ const setupTransferTest = async (page: Page): Promise => { const wg = createWalletGateway(page) await gotoConnect(page) - await wg.connect({ network: 'LocalNet' }) + await connectToLocalNet(wg) const alice = await wg.createWalletIfNotExists({ partyHint: `alice-${rnd}`, diff --git a/examples/portfolio/tests/utils.ts b/examples/portfolio/tests/utils.ts index 33cdfcfaa..6f0992259 100644 --- a/examples/portfolio/tests/utils.ts +++ b/examples/portfolio/tests/utils.ts @@ -5,6 +5,7 @@ import { expect, Page } from '@playwright/test' import { WalletGateway } from '@canton-network/core-wallet-test-utils' const BASE_URL = 'http://localhost:8081' +const LOCALNET_CLIENT_SECRET = 'unsafe' export const createWalletGateway = (dappPage: Page): WalletGateway => new WalletGateway({ @@ -19,6 +20,12 @@ export const createWalletGateway = (dappPage: Page): WalletGateway => }), }) +export const connectToLocalNet = (wg: WalletGateway): Promise => + wg.connect({ + network: 'LocalNet', + credentials: { clientSecret: LOCALNET_CLIENT_SECRET }, + }) + export const gotoConnect = async (page: Page): Promise => { await page.goto(`${BASE_URL}/connect`) await expect(page).toHaveTitle(/dApp Portfolio/) diff --git a/examples/portfolio/tests/wallet-detail.spec.ts b/examples/portfolio/tests/wallet-detail.spec.ts index c35325283..203f9225f 100644 --- a/examples/portfolio/tests/wallet-detail.spec.ts +++ b/examples/portfolio/tests/wallet-detail.spec.ts @@ -4,6 +4,7 @@ import { test, expect } from '@playwright/test' import { createWalletGateway, + connectToLocalNet, gotoConnect, gotoDashboard, setupRegistry, @@ -21,7 +22,7 @@ test('wallet detail page - assets and transaction history', async ({ const wg = createWalletGateway(dappPage) await gotoConnect(dappPage) - await wg.connect({ network: 'LocalNet' }) + await connectToLocalNet(wg) const aliceHint = `alice-${rnd}` const alice = await wg.createWalletIfNotExists({ diff --git a/wallet-gateway/extension/entrypoints/background/user/rpc-gen/typings.ts b/wallet-gateway/extension/entrypoints/background/user/rpc-gen/typings.ts index 2c2fb5f1e..936730148 100644 --- a/wallet-gateway/extension/entrypoints/background/user/rpc-gen/typings.ts +++ b/wallet-gateway/extension/entrypoints/background/user/rpc-gen/typings.ts @@ -510,6 +510,7 @@ export interface GetNetworkParams { export interface SelfSignedAccessTokenParams { networkId: NetworkId clientId: ClientId + clientSecret: ClientSecret } export interface AddIdpParams { idp: Idp diff --git a/wallet-gateway/extension/entrypoints/login/login.ts b/wallet-gateway/extension/entrypoints/login/login.ts index 6d65c15e7..aa772fba1 100644 --- a/wallet-gateway/extension/entrypoints/login/login.ts +++ b/wallet-gateway/extension/entrypoints/login/login.ts @@ -86,7 +86,7 @@ export class LoginUI extends BaseElement { } private async handleConnect(e: LoginConnectEvent) { - const { selectedNetwork, selectedIdp, clientId } = e + const { selectedNetwork, selectedIdp, clientId, clientSecret } = e this.connecting = true this.connectingMessage = `Connecting to ${selectedNetwork.name}...` @@ -95,7 +95,11 @@ export class LoginUI extends BaseElement { try { if (selectedIdp.type === 'self_signed') { - await this.selfSign(selectedNetwork.id, clientId) + await this.selfSign( + selectedNetwork.id, + clientId ?? '', + clientSecret ?? '' + ) await redirectToIntendedOrDefault() return } @@ -163,14 +167,18 @@ export class LoginUI extends BaseElement { } } - protected async selfSign(networkId: string, clientId: string) { + protected async selfSign( + networkId: string, + clientId: string, + clientSecret: string + ) { const currentOrigin = await detectCurrentOrigin() const userClient = await createUserClient( (await stateManager.accessToken.get(currentOrigin)) || undefined ) const { accessToken } = await userClient.request({ method: 'selfSignedAccessToken', - params: { networkId, clientId }, + params: { networkId, clientId, clientSecret }, }) const payload = JSON.parse(atob(accessToken.split('.')[1]!)) diff --git a/wallet-gateway/remote/src/user-api/controller.test.ts b/wallet-gateway/remote/src/user-api/controller.test.ts index c8a024ae0..8d4510800 100644 --- a/wallet-gateway/remote/src/user-api/controller.test.ts +++ b/wallet-gateway/remote/src/user-api/controller.test.ts @@ -447,6 +447,79 @@ describe('userController', () => { }) }) + describe('selfSignedAccessToken', () => { + const selfSignedIdp: Idp = { + id: 'idp-self-signed', + type: 'self_signed', + issuer: 'unsafe-auth', + } + + const selfSignedNetwork: StoreNetwork = { + id: 'network-self-signed', + name: 'Self Signed', + description: 'Test', + identityProviderId: 'idp-self-signed', + ledgerApi: { baseUrl: 'http://ledger.test' }, + auth: { + method: 'self_signed', + issuer: 'self-signed', + audience: 'aud', + scope: 'scope', + clientId: 'operator', + clientSecret: 'network-secret', + }, + } + + it('mints a token when the client secret matches the network', async () => { + const store = new StoreInternal( + { idps: [selfSignedIdp], networks: [selfSignedNetwork] }, + getLogger('mock') + ) + const controller = createController( + store, + notificationService, + logger, + undefined + ) + + const result = await controller.selfSignedAccessToken({ + networkId: 'network-self-signed', + clientId: 'test-user', + clientSecret: 'network-secret', + }) + + expect(typeof result.accessToken).toBe('string') + const payload = JSON.parse( + Buffer.from( + result.accessToken.split('.')[1]!, + 'base64url' + ).toString() + ) + expect(payload.sub).toBe('test-user') + }) + + it('rejects when the client secret does not match', async () => { + const store = new StoreInternal( + { idps: [selfSignedIdp], networks: [selfSignedNetwork] }, + getLogger('mock') + ) + const controller = createController( + store, + notificationService, + logger, + undefined + ) + + await expect( + controller.selfSignedAccessToken({ + networkId: 'network-self-signed', + clientId: 'test-user', + clientSecret: 'wrong-secret', + }) + ).rejects.toThrow('Invalid client secret') + }) + }) + describe('idps', () => { it('adds and removes an idp for the admin user', async () => { const store = await createStore(logger, adminAuth) diff --git a/wallet-gateway/remote/src/user-api/controller.ts b/wallet-gateway/remote/src/user-api/controller.ts index 4f3d74fa6..a90f7e5fa 100644 --- a/wallet-gateway/remote/src/user-api/controller.ts +++ b/wallet-gateway/remote/src/user-api/controller.ts @@ -71,7 +71,7 @@ import type { MessageSignatureEvent, TxChangedFailedEvent, } from '../dapp-api/rpc-gen/typings.js' -import { rpcErrors } from '@canton-network/core-rpc-errors' +import { providerErrors, rpcErrors } from '@canton-network/core-rpc-errors' import crypto from 'crypto' import { assertTokenClaimsMatchNetwork } from './token-network-matching.js' @@ -203,6 +203,12 @@ export const userController = ( ) } + if (params.clientSecret !== auth.clientSecret) { + throw providerErrors.unauthorized({ + message: 'Invalid client secret', + }) + } + const idp = (await store.listIdps()).find( (idp) => idp.id === network.identityProviderId ) diff --git a/wallet-gateway/remote/src/user-api/rpc-gen/typings.ts b/wallet-gateway/remote/src/user-api/rpc-gen/typings.ts index 2c2fb5f1e..936730148 100644 --- a/wallet-gateway/remote/src/user-api/rpc-gen/typings.ts +++ b/wallet-gateway/remote/src/user-api/rpc-gen/typings.ts @@ -510,6 +510,7 @@ export interface GetNetworkParams { export interface SelfSignedAccessTokenParams { networkId: NetworkId clientId: ClientId + clientSecret: ClientSecret } export interface AddIdpParams { idp: Idp diff --git a/wallet-gateway/remote/src/user-api/server.test.ts b/wallet-gateway/remote/src/user-api/server.test.ts index e0c297ad7..2210abd94 100644 --- a/wallet-gateway/remote/src/user-api/server.test.ts +++ b/wallet-gateway/remote/src/user-api/server.test.ts @@ -92,6 +92,7 @@ test('selfSignedAccessToken rpc', async () => { params: { networkId: 'canton:local-self-signed', clientId: 'test-user', + clientSecret: 'unsafe', }, }) .set('Accept', 'application/json') @@ -134,6 +135,7 @@ test('selfSignedAccessToken token is accepted by jwt auth', async () => { params: { networkId: 'canton:local-self-signed', clientId: 'test-user', + clientSecret: 'unsafe', }, }) @@ -143,3 +145,39 @@ test('selfSignedAccessToken token is accepted by jwt auth', async () => { expect(context?.userId).toBe('test-user') expect(context?.accessToken).toBe(accessToken) }) + +test('selfSignedAccessToken rpc rejects a mismatched client secret', async () => { + const app = express() + app.use(cors()) + app.use(express.json()) + + const { publicUrl } = deriveUrls(config) + const response = await request( + user( + '/api/v0/user', + app, + pino(sink()), + config.kernel, + publicUrl, + notificationService, + {}, + store + ) + ) + .post('/api/v0/user') + .send({ + jsonrpc: '2.0', + id: 3, + method: 'selfSignedAccessToken', + params: { + networkId: 'canton:local-self-signed', + clientId: 'test-user', + clientSecret: 'wrong-secret', + }, + }) + .set('Accept', 'application/json') + + expect(response.statusCode).toBe(401) + expect(response.body.result).toBeUndefined() + expect(response.body.error).toBeDefined() +}) diff --git a/wallet-gateway/remote/src/web/frontend/login/login.test.ts b/wallet-gateway/remote/src/web/frontend/login/login.test.ts index bf1d94f67..f5f1cd016 100644 --- a/wallet-gateway/remote/src/web/frontend/login/login.test.ts +++ b/wallet-gateway/remote/src/web/frontend/login/login.test.ts @@ -112,11 +112,14 @@ function dispatchConnect( el: LoginUI, network = selfSignedNetwork, idp = selfSignedIdp, - clientId = 'client-id' + clientId = 'client-id', + clientSecret = 'client-secret' ) { el.shadowRoot ?.querySelector('wg-login-form') - ?.dispatchEvent(new LoginConnectEvent(network, idp, clientId)) + ?.dispatchEvent( + new LoginConnectEvent(network, idp, clientId, clientSecret) + ) } function getLoginForm(el: LoginUI): WgLoginForm | null { @@ -218,18 +221,16 @@ describe('LoginUI', () => { expect(mockRedirectToIntendedOrDefault).toHaveBeenCalled() }) - it('uses an empty client secret when the network omits one', async () => { + it('sends the client secret from the login form', async () => { await waitUntil(() => el.networks.length === 1) - const networkWithoutSecret = makePublicNetwork({ - id: 'net-1', - authMethod: 'client_credentials', - audience: 'aud', - scope: 'scope', - clientId: 'client-id', - }) - - dispatchConnect(el, networkWithoutSecret, selfSignedIdp, 'client-id') + dispatchConnect( + el, + selfSignedNetwork, + selfSignedIdp, + 'client-id', + 'network-secret' + ) await waitUntil( () => mockRedirectToIntendedOrDefault.mock.calls.length > 0 @@ -238,7 +239,11 @@ describe('LoginUI', () => { expect(mockRequest).toHaveBeenCalledWith( expect.objectContaining({ method: 'selfSignedAccessToken', - params: { networkId: 'net-1', clientId: 'client-id' }, + params: { + networkId: 'net-1', + clientId: 'client-id', + clientSecret: 'network-secret', + }, }) ) expect(mockRedirectToIntendedOrDefault).toHaveBeenCalled() diff --git a/wallet-gateway/remote/src/web/frontend/login/login.ts b/wallet-gateway/remote/src/web/frontend/login/login.ts index 34d168fba..dd24490f0 100644 --- a/wallet-gateway/remote/src/web/frontend/login/login.ts +++ b/wallet-gateway/remote/src/web/frontend/login/login.ts @@ -114,7 +114,7 @@ export class LoginUI extends BaseElement { } private async handleConnect(e: LoginConnectEvent) { - const { selectedNetwork, selectedIdp, clientId } = e + const { selectedNetwork, selectedIdp, clientId, clientSecret } = e this.connecting = true this.connectingMessage = `Connecting to ${selectedNetwork.name}...` @@ -123,7 +123,11 @@ export class LoginUI extends BaseElement { try { if (selectedIdp.type === 'self_signed') { - await this.selfSign(selectedNetwork.id, clientId) + await this.selfSign( + selectedNetwork.id, + clientId ?? '', + clientSecret ?? '' + ) await redirectToIntendedOrDefault() return } @@ -194,14 +198,18 @@ export class LoginUI extends BaseElement { } } - protected async selfSign(networkId: string, clientId: string) { + protected async selfSign( + networkId: string, + clientId: string, + clientSecret: string + ) { const currentOrigin = await detectCurrentOrigin() const userClient = await createUserClient( await stateManager.accessToken.get(currentOrigin) ) const { accessToken } = await userClient.request({ method: 'selfSignedAccessToken', - params: { networkId, clientId }, + params: { networkId, clientId, clientSecret }, }) const payload = JSON.parse(atob(accessToken.split('.')[1]))