From 4d86c112dcb5dd7ad6d2213f70ceb27922e4bd73 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 4 Dec 2024 09:51:44 +0100 Subject: [PATCH 01/11] Add `AddMetadataToRetryableError` to `AuthenticateWebDevice` --- lib/teleterm/daemon/daemon.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/teleterm/daemon/daemon.go b/lib/teleterm/daemon/daemon.go index 19724daceb6b3..13f12f4dfa253 100644 --- a/lib/teleterm/daemon/daemon.go +++ b/lib/teleterm/daemon/daemon.go @@ -1131,10 +1131,14 @@ func (s *Service) AuthenticateWebDevice(ctx context.Context, rootClusterURI uri. } devicesClient := proxyClient.CurrentCluster().DevicesClient() - ceremony := dtauthn.NewCeremony() - confirmationToken, err := ceremony.RunWeb(ctx, devicesClient, &devicepb.DeviceWebToken{ - Id: req.DeviceWebToken.Id, - Token: req.DeviceWebToken.Token, + var confirmationToken *devicepb.DeviceConfirmationToken + err = clusters.AddMetadataToRetryableError(ctx, func() error { + ceremony := dtauthn.NewCeremony() + confirmationToken, err = ceremony.RunWeb(ctx, devicesClient, &devicepb.DeviceWebToken{ + Id: req.DeviceWebToken.Id, + Token: req.DeviceWebToken.Token, + }) + return trace.Wrap(err) }) if err != nil { return nil, trace.Wrap(err) From 76fd02d429a6c311a760cbd73de575df6fc92fc2 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 4 Dec 2024 09:53:23 +0100 Subject: [PATCH 02/11] Add a document for web session authorization --- .../src/services/tshd/fixtures/mocks.ts | 8 +- .../DocumentAuthorizeWebSession.story.tsx | 90 +++++++++ .../DocumentAuthorizeWebSession.test.tsx | 105 ++++++++++ .../DocumentAuthorizeWebSession.tsx | 188 ++++++++++++++++++ .../ui/DocumentAuthorizeWebSession/index.ts | 19 ++ .../src/ui/Documents/DocumentsRenderer.tsx | 3 + .../documentsService/documentsService.ts | 17 ++ .../documentsService/documentsUtils.ts | 2 + .../documentsService/types.ts | 22 +- .../workspacesService/workspacesService.ts | 9 +- 10 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.story.tsx create mode 100644 web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.test.tsx create mode 100644 web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.tsx create mode 100644 web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/index.ts diff --git a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts index 8696b36ee532d..8d0fa6999711d 100644 --- a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts +++ b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts @@ -101,7 +101,13 @@ export class MockTshClient implements TshdClient { getSuggestedAccessLists = () => new MockedUnaryCall({ accessLists: [] }); promoteAccessRequest = () => new MockedUnaryCall({}); updateTshdEventsServerAddress = () => new MockedUnaryCall({}); - authenticateWebDevice = () => new MockedUnaryCall({}); + authenticateWebDevice = () => + new MockedUnaryCall({ + confirmationToken: { + id: '123456789', + token: '7c8e7438-abe1-4cbc-b3e6-bd233bba967c', + }, + }); startHeadlessWatcher = () => new MockedUnaryCall({}); } diff --git a/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.story.tsx b/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.story.tsx new file mode 100644 index 0000000000000..7b697092f7c62 --- /dev/null +++ b/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.story.tsx @@ -0,0 +1,90 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { wait } from 'shared/utils/wait'; + +import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; +import { + makeRootCluster, + makeLoggedInUser, + rootClusterUri, +} from 'teleterm/services/tshd/testHelpers'; +import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; +import { MockWorkspaceContextProvider } from 'teleterm/ui/fixtures/MockWorkspaceContextProvider'; +import * as types from 'teleterm/ui/services/workspacesService'; +import { MockedUnaryCall } from 'teleterm/services/tshd/cloneableClient'; + +import { DocumentAuthorizeWebSession } from './DocumentAuthorizeWebSession'; + +export default { + title: 'Teleterm/DocumentAuthorizeWebSession', +}; + +const doc: types.DocumentAuthorizeWebSession = { + uri: '/docs/e2hyt5', + rootClusterUri: rootClusterUri, + kind: 'doc.authorize_web_session', + title: 'Authorize Web Session', + webSessionRequest: { + redirectUri: '', + token: '', + id: '', + }, +}; + +export function DeviceNotTrusted() { + const rootCluster = makeRootCluster(); + const appContext = new MockAppContext(); + appContext.clustersService.setState(draftState => { + draftState.clusters.set(rootCluster.uri, rootCluster); + }); + return ( + + + + + + ); +} + +export function DeviceTrusted() { + const rootCluster = makeRootCluster({ + loggedInUser: makeLoggedInUser({ isDeviceTrusted: true }), + }); + const appContext = new MockAppContext(); + appContext.clustersService.setState(draftState => { + draftState.clusters.set(rootCluster.uri, rootCluster); + }); + appContext.clustersService.authenticateWebDevice = async () => { + await wait(2_000); + return new MockedUnaryCall({ + confirmationToken: { + id: '123456789', + token: '7c8e7438-abe1-4cbc-b3e6-bd233bba967c', + }, + }); + }; + + return ( + + + + + + ); +} diff --git a/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.test.tsx b/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.test.tsx new file mode 100644 index 0000000000000..c06ff109ff93c --- /dev/null +++ b/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.test.tsx @@ -0,0 +1,105 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { render, screen } from 'design/utils/testing'; +import userEvent from '@testing-library/user-event'; + +import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; +import { MockWorkspaceContextProvider } from 'teleterm/ui/fixtures/MockWorkspaceContextProvider'; +import { + makeRootCluster, + makeLoggedInUser, + rootClusterUri, +} from 'teleterm/services/tshd/testHelpers'; +import * as types from 'teleterm/ui/services/workspacesService'; +import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; + +import { DocumentAuthorizeWebSession } from './DocumentAuthorizeWebSession'; + +const doc: types.DocumentAuthorizeWebSession = { + uri: '/docs/e2hyt5', + rootClusterUri: rootClusterUri, + kind: 'doc.authorize_web_session', + title: 'Authorize Web Session', + webSessionRequest: { + redirectUri: '', + token: '', + id: '', + }, +}; + +test('authorize button is disabled when device is not trusted', async () => { + const rootCluster = makeRootCluster({ + loggedInUser: makeLoggedInUser({ isDeviceTrusted: false }), + }); + const appContext = new MockAppContext(); + appContext.clustersService.setState(draftState => { + draftState.clusters.set(rootCluster.uri, rootCluster); + }); + + render( + + + + + + ); + + expect(await screen.findByText(/This device is not trusted/)).toBeVisible(); + expect(await screen.findByText(/Authorize Session/)).toBeDisabled(); +}); + +test('authorizing a session opens its URL and closes document', async () => { + jest.spyOn(window, 'open').mockImplementation(); + const rootCluster = makeRootCluster({ + loggedInUser: makeLoggedInUser({ isDeviceTrusted: true }), + }); + const appContext = new MockAppContext(); + appContext.clustersService.setState(draftState => { + draftState.clusters.set(rootCluster.uri, rootCluster); + }); + appContext.workspacesService.setState(draftState => { + draftState.workspaces[rootCluster.uri] = { + localClusterUri: rootCluster.uri, + documents: [doc], + location: undefined, + accessRequests: undefined, + }; + }); + + render( + + + + + + ); + + const button = await screen.findByText(/Authorize Session/); + await userEvent.click(button); + + expect(await screen.findByText(/Session Authorized/)).toBeVisible(); + expect(window.open).toHaveBeenCalledWith( + 'https://teleport-local:3080/webapi/devices/webconfirm?id=123456789&token=7c8e7438-abe1-4cbc-b3e6-bd233bba967c' + ); + expect( + appContext.workspacesService + .getWorkspaceDocumentService(rootCluster.uri) + .getDocuments() + ).toHaveLength(0); +}); diff --git a/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.tsx b/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.tsx new file mode 100644 index 0000000000000..e2127bd7f7aca --- /dev/null +++ b/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/DocumentAuthorizeWebSession.tsx @@ -0,0 +1,188 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Text, Alert, ButtonPrimary, H1, ButtonText } from 'design'; +import Flex from 'design/Flex'; +import { useAsync, Attempt } from 'shared/hooks/useAsync'; +import { processRedirectUri } from 'shared/redirects'; +import { Cluster } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; +import { DeviceConfirmationToken } from 'gen-proto-ts/teleport/devicetrust/v1/device_confirmation_token_pb'; + +import Document from 'teleterm/ui/Document'; +import { routing } from 'teleterm/ui/uri'; +import { retryWithRelogin } from 'teleterm/ui/utils'; +import { useAppContext } from 'teleterm/ui/appContextProvider'; +import * as types from 'teleterm/ui/services/workspacesService'; +import { WebSessionRequest } from 'teleterm/ui/services/workspacesService'; +import { useWorkspaceContext } from 'teleterm/ui/Documents'; + +export function DocumentAuthorizeWebSession(props: { + doc: types.DocumentAuthorizeWebSession; + visible: boolean; +}) { + const ctx = useAppContext(); + const { documentsService } = useWorkspaceContext(); + const rootCluster = ctx.clustersService.findCluster(props.doc.rootClusterUri); + const [attempt, authorize] = useAsync(async () => { + const { + response: { confirmationToken }, + } = await retryWithRelogin(ctx, props.doc.rootClusterUri, () => + ctx.clustersService.authenticateWebDevice( + props.doc.rootClusterUri, + props.doc.webSessionRequest + ) + ); + const url = buildAuthorizedSessionUrl( + rootCluster, + props.doc.webSessionRequest, + confirmationToken + ); + // This endpoint verifies the token and "upgrades" the web session and redirects to "/web". + window.open(url); + }); + const clusterName = routing.parseClusterName(props.doc.rootClusterUri); + const canAuthorize = rootCluster.loggedInUser?.isDeviceTrusted; + + async function authorizeAndCloseDocument() { + const [, error] = await authorize(); + if (!error) { + closeAndNotify(); + } + } + + function openUnauthorizedAndCloseDocument() { + const url = buildUnauthorizedSessionUrl( + rootCluster, + props.doc.webSessionRequest + ); + window.open(url); + closeAndNotify(); + } + + function closeAndNotify() { + documentsService.close(props.doc.uri); + ctx.notificationsService.notifyInfo( + 'Web session has been opened in the browser' + ); + } + + return ( + + +

Authorize Web Session

+ + {!canAuthorize && ( + + To authorize a web session, you must first{' '} + + enroll your device. + {' '} + Then log out of the app, log back in, and try again. + + } + > + This device is not trusted + + )} + {attempt.status === 'error' && ( + + Could not authorize the session + + )} + + Would you like to authorize a device trust web session for{' '} + {clusterName}? +
+ The session will automatically open in a new browser tab. +
+ + + {getButtonText(attempt)} + + + Open Session Without Device Trust + + +
+
+
+ ); +} + +const confirmPath = 'webapi/devices/webconfirm'; + +function buildAuthorizedSessionUrl( + rootCluster: Cluster, + webSessionRequest: WebSessionRequest, + confirmationToken: DeviceConfirmationToken +): string { + const { redirectUri } = webSessionRequest; + let url = `https://${rootCluster.proxyHost}/${confirmPath}?id=${confirmationToken.id}&token=${confirmationToken.token}`; + if (redirectUri) { + url = `${url}&redirect_uri=${redirectUri}`; + } + return url; +} + +function buildUnauthorizedSessionUrl( + rootCluster: Cluster, + webSessionRequest: WebSessionRequest +): string { + const processedRedirectUri = processRedirectUri( + webSessionRequest.redirectUri + ); + return `https://${rootCluster.proxyHost}${processedRedirectUri}`; +} + +function getButtonText(attempt: Attempt): string { + switch (attempt.status) { + case '': + case 'error': + return 'Authorize Session'; + case 'processing': + return 'Authorizing Session…'; + case 'success': + return 'Session Authorized'; + } +} diff --git a/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/index.ts b/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/index.ts new file mode 100644 index 0000000000000..ffd92d5d17b86 --- /dev/null +++ b/web/packages/teleterm/src/ui/DocumentAuthorizeWebSession/index.ts @@ -0,0 +1,19 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export * from './DocumentAuthorizeWebSession'; diff --git a/web/packages/teleterm/src/ui/Documents/DocumentsRenderer.tsx b/web/packages/teleterm/src/ui/Documents/DocumentsRenderer.tsx index 0be864d2748f6..d1d300f0d8428 100644 --- a/web/packages/teleterm/src/ui/Documents/DocumentsRenderer.tsx +++ b/web/packages/teleterm/src/ui/Documents/DocumentsRenderer.tsx @@ -41,6 +41,7 @@ import { } from 'teleterm/ui/ConnectMyComputer'; import { DocumentGatewayKube } from 'teleterm/ui/DocumentGatewayKube'; import { DocumentGatewayApp } from 'teleterm/ui/DocumentGatewayApp'; +import { DocumentAuthorizeWebSession } from 'teleterm/ui/DocumentAuthorizeWebSession'; import Document from 'teleterm/ui/Document'; import { RootClusterUri, isDatabaseUri, isAppUri } from 'teleterm/ui/uri'; @@ -153,6 +154,8 @@ function MemoizedDocument(props: { doc: types.Document; visible: boolean }) { return ; case 'doc.connect_my_computer': return ; + case 'doc.authorize_web_session': + return ; default: return ( diff --git a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts index ec3851550e84c..1edc82ed9c524 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts @@ -44,6 +44,8 @@ import { DocumentTshNodeWithServerId, DocumentClusterQueryParams, DocumentPtySession, + WebSessionRequest, + DocumentAuthorizeWebSession, } from './types'; import type { Shell } from 'teleterm/mainProcess/shell'; @@ -228,6 +230,21 @@ export class DocumentsService { }; } + createAuthorizeWebSessionDocument(params: { + rootClusterUri: string; + webSessionRequest: WebSessionRequest; + }): DocumentAuthorizeWebSession { + const uri = routing.getDocUri({ docId: unique() }); + + return { + uri, + kind: 'doc.authorize_web_session', + title: 'Authorize Web Session', + rootClusterUri: params.rootClusterUri, + webSessionRequest: params.webSessionRequest, + }; + } + openConnectMyComputerDocument(opts: { // URI of the root cluster could be passed to the `DocumentsService` // constructor and then to the document, instead of being taken from the parameter. diff --git a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsUtils.ts b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsUtils.ts index e71525ce99b95..6bd654e01d963 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsUtils.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsUtils.ts @@ -53,6 +53,8 @@ export function getResourceUri( }); case 'doc.connect_my_computer': return document.rootClusterUri; + case 'doc.authorize_web_session': + return document.rootClusterUri; case 'doc.blank': return undefined; default: diff --git a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts index cc95e377520ec..0c7fd36952285 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts @@ -221,6 +221,25 @@ export interface DocumentConnectMyComputer extends DocumentBase { status: '' | 'connecting' | 'connected' | 'error'; } +/** + * Document to authorize a web session with device trust. + * Unlike other documents, it is not persisted on disk. + */ +export interface DocumentAuthorizeWebSession extends DocumentBase { + kind: 'doc.authorize_web_session'; + // `DocumentAuthorizeWebSession` always operates on the root cluster, so in theory `rootClusterUri` is not needed. + // However, there are a few components in the system, such as `getResourceUri`, which need to determine the relation + // between a document and a cluster just by looking at the document fields. + rootClusterUri: uri.RootClusterUri; + webSessionRequest: WebSessionRequest; +} + +export interface WebSessionRequest { + id: string; + token: string; + redirectUri: string; +} + export type DocumentTerminal = | DocumentPtySession | DocumentGatewayCliClient @@ -234,7 +253,8 @@ export type Document = | DocumentGateway | DocumentCluster | DocumentTerminal - | DocumentConnectMyComputer; + | DocumentConnectMyComputer + | DocumentAuthorizeWebSession; export function isDocumentTshNodeWithLoginHost( doc: Document diff --git a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts index d2899b02df939..91984c5817025 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts @@ -570,10 +570,17 @@ export class WorkspacesService extends ImmutableStore { }; for (let w in this.state.workspaces) { const workspace = this.state.workspaces[w]; + // We don't persist 'doc.authorize_web_session' because we don't want to store + // a session token and id on disk. + // Moreover, the user would not be able to authorize a session at a later time anyway. + const documentsToPersist = ( + workspace.previous?.documents || workspace.documents + ).filter(d => d.kind !== 'doc.authorize_web_session'); + stateToSave.workspaces[w] = { localClusterUri: workspace.localClusterUri, location: workspace.previous?.location || workspace.location, - documents: workspace.previous?.documents || workspace.documents, + documents: documentsToPersist, connectMyComputer: workspace.connectMyComputer, unifiedResourcePreferences: workspace.unifiedResourcePreferences, }; From 6b1f8a8f6fd32cd9f0dedf8522d826d3d67b6005 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 4 Dec 2024 09:54:15 +0100 Subject: [PATCH 03/11] Replace `AuthenticateWebDevice` modal with the document --- .../teleterm/src/ui/ModalsHost/ModalsHost.tsx | 15 ---- .../AuthenticateWebDevice.story.tsx | 39 --------- .../AuthenticateWebDevice.tsx | 82 ------------------- .../ui/services/deepLinks/deepLinksService.ts | 38 +++------ .../src/ui/services/modals/modalsService.ts | 8 -- 5 files changed, 11 insertions(+), 171 deletions(-) delete mode 100644 web/packages/teleterm/src/ui/ModalsHost/modals/AuthenticateWebDevice/AuthenticateWebDevice.story.tsx delete mode 100644 web/packages/teleterm/src/ui/ModalsHost/modals/AuthenticateWebDevice/AuthenticateWebDevice.tsx diff --git a/web/packages/teleterm/src/ui/ModalsHost/ModalsHost.tsx b/web/packages/teleterm/src/ui/ModalsHost/ModalsHost.tsx index 3ccb6f6c164ad..ba276907d9037 100644 --- a/web/packages/teleterm/src/ui/ModalsHost/ModalsHost.tsx +++ b/web/packages/teleterm/src/ui/ModalsHost/ModalsHost.tsx @@ -32,7 +32,6 @@ import { assertUnreachable } from '../utils'; import { UsageData } from './modals/UsageData'; import { UserJobRole } from './modals/UserJobRole'; import { ReAuthenticate } from './modals/ReAuthenticate'; -import { AuthenticateWebDevice } from './modals/AuthenticateWebDevice/AuthenticateWebDevice'; import { ChangeAccessRequestKind } from './modals/ChangeAccessRequestKind'; import { AskPin, ChangePin, OverwriteSlot, Touch } from './modals/HardwareKeys'; @@ -87,20 +86,6 @@ function renderDialog({ } switch (dialog.kind) { - case 'device-trust-authorize': { - return ( -