Skip to content

Commit

Permalink
Convert dialog to authorize web session to regular tab and handle exp…
Browse files Browse the repository at this point in the history
…ired certs (#49754)

* Add `AddMetadataToRetryableError` to `AuthenticateWebDevice`

* Add a document for web session authorization

* Replace `AuthenticateWebDevice` modal with the document

* Remove dot from anchor

* Move more logic to `authorizeAndCloseDocument`

* Add a comment about `canAuthorize`

* Filter out documents before restoring session

* Add a comment about `processedRedirectUri`

* Restore filtering out documents before saving them on disk

* Fix test

* Refer to Teleport Connect instead of 'the app'
  • Loading branch information
gzdunek authored Dec 9, 2024
1 parent 0feb6fe commit e2f3daf
Show file tree
Hide file tree
Showing 16 changed files with 604 additions and 163 deletions.
12 changes: 8 additions & 4 deletions lib/teleterm/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion web/packages/teleterm/src/services/tshd/fixtures/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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 (
<MockAppContextProvider appContext={appContext}>
<MockWorkspaceContextProvider rootClusterUri={rootCluster.uri}>
<DocumentAuthorizeWebSession doc={doc} visible={true} />
</MockWorkspaceContextProvider>
</MockAppContextProvider>
);
}

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 (
<MockAppContextProvider appContext={appContext}>
<MockWorkspaceContextProvider rootClusterUri={rootCluster.uri}>
<DocumentAuthorizeWebSession doc={doc} visible={true} />
</MockWorkspaceContextProvider>
</MockAppContextProvider>
);
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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(
<MockAppContextProvider appContext={appContext}>
<MockWorkspaceContextProvider rootClusterUri={rootCluster.uri}>
<DocumentAuthorizeWebSession doc={doc} visible={true} />
</MockWorkspaceContextProvider>
</MockAppContextProvider>
);

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(
<MockAppContextProvider appContext={appContext}>
<MockWorkspaceContextProvider rootClusterUri={rootCluster.uri}>
<DocumentAuthorizeWebSession doc={doc} visible={true} />
</MockWorkspaceContextProvider>
</MockAppContextProvider>
);

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);
});
Loading

0 comments on commit e2f3daf

Please sign in to comment.