Skip to content

Commit

Permalink
update session.logout and add error page
Browse files Browse the repository at this point in the history
  • Loading branch information
rudream committed Jun 18, 2024
1 parent f8a06b1 commit f80e08f
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 24 deletions.
14 changes: 14 additions & 0 deletions web/packages/design/src/CardError/CardError.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ LoginFailed.propTypes = {
loginUrl: PropTypes.string.isRequired,
};

export const LogoutFailed = ({ message, loginUrl }) => (
<CardError>
<Header>Login Unsuccessful</Header>
<Content
message={message}
desc={
<Text typography="paragraph" textAlign="center">
<HyperLink href={loginUrl}>Return to login.</HyperLink>
</Text>
}
/>
</CardError>
);

const HyperLink = styled.a`
color: ${({ theme }) => theme.colors.buttons.link.default};
`;
3 changes: 2 additions & 1 deletion web/packages/design/src/CardError/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import CardError, {
LoginFailed,
AccessDenied,
NotFound,
LogoutFailed,
} from './CardError';

export default CardError;
export { Failed, LoginFailed, AccessDenied, NotFound, Offline };
export { Failed, LoginFailed, AccessDenied, NotFound, Offline, LogoutFailed };
5 changes: 4 additions & 1 deletion web/packages/teleport/src/Console/Console.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import useAttempt from 'shared/hooks/useAttemptNext';

import AjaxPoller from 'teleport/components/AjaxPoller';

import { useTeleport } from '..';

import { useConsoleContext, useStoreDocs } from './consoleContextProvider';
import * as stores from './stores/types';
import Tabs from './Tabs';
Expand All @@ -50,6 +52,7 @@ export default function Console() {
const activeDoc = documents.find(d => d.id === activeDocId);
const hasSshSessions = storeDocs.getSshDocuments().length > 0;
const { attempt, run } = useAttempt();
const teleportCtx = useTeleport();

React.useEffect(() => {
run(() => consoleCtx.initStoreUser());
Expand Down Expand Up @@ -78,7 +81,7 @@ export default function Console() {
}

function onLogout() {
consoleCtx.logout();
consoleCtx.logout(teleportCtx);
}

const disableNewTab = storeDocs.getNodeDocuments().length > 0;
Expand Down
6 changes: 4 additions & 2 deletions web/packages/teleport/src/Console/consoleContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import ClustersService from 'teleport/services/clusters';
import { StoreUserContext } from 'teleport/stores';
import usersService from 'teleport/services/user';

import TeleportContext from 'teleport/teleportContext';

import {
StoreParties,
StoreDocs,
Expand Down Expand Up @@ -208,8 +210,8 @@ export default class ConsoleContext {
});
}

logout() {
webSession.logout();
logout(ctx: TeleportContext) {
webSession.logout(false, ctx.storeUser);
}

createTty(session: Session, mode?: ParticipantMode): Tty {
Expand Down
5 changes: 4 additions & 1 deletion web/packages/teleport/src/Player/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { getUrlParameter } from 'teleport/services/history';

import { RecordingType } from 'teleport/services/recordings';

import { useTeleport } from '..';

import ActionBar from './ActionBar';
import { DesktopPlayer } from './DesktopPlayer';
import SshPlayer from './SshPlayer';
Expand All @@ -41,6 +43,7 @@ const validRecordingTypes = ['ssh', 'k8s', 'desktop'];
export function Player() {
const { sid, clusterId } = useParams<UrlPlayerParams>();
const { search } = useLocation();
const ctx = useTeleport();

const recordingType = getUrlParameter(
'recordingType',
Expand All @@ -54,7 +57,7 @@ export function Player() {
document.title = `Play ${sid}${clusterId}`;

function onLogout() {
session.logout();
session.logout(false, ctx.storeUser);
}

if (!validRecordingType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* 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 React from 'react';
import { useLocation } from 'react-router';
import { LogoutFailed } from 'design/CardError';

import { LogoHero } from 'teleport/components/LogoHero';
import cfg from 'teleport/config';

export function SingleLogoutFailed() {
const { search } = useLocation();
const params = new URLSearchParams(search);
const connectorName = params.get('connectorName');

const connectorNameText = connectorName
? connectorName
: 'your SAML identity provider.';
return (
<>
<LogoHero />
<LogoutFailed
loginUrl={cfg.routes.login}
message={`You have been logged out of Teleport, but we were unable to log you out of ${connectorNameText}. See the Teleport logs for details.`}
/>
</>
);
}
19 changes: 19 additions & 0 deletions web/packages/teleport/src/SingleLogoutFailed/index.ts
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

export { SingleLogoutFailed } from './SingleLogoutFailed';
7 changes: 7 additions & 0 deletions web/packages/teleport/src/Teleport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { LoginTerminalRedirect } from './Login/LoginTerminalRedirect';
import { LoginClose } from './Login/LoginClose';
import { Login } from './Login';
import { Welcome } from './Welcome';
import { SingleLogoutFailed } from './SingleLogoutFailed';

import { ConsoleWithContext as Console } from './Console';
import { Player } from './Player';
Expand Down Expand Up @@ -143,6 +144,12 @@ export function getSharedPublicRoutes() {
path={cfg.routes.userReset}
render={() => <Welcome NewCredentials={NewCredentials} />}
/>,
<Route
key="saml-slo-failed"
title="SAML Single Logout Failed"
path={cfg.routes.samlSloFailed}
component={SingleLogoutFailed}
/>,
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const Authenticated: React.FC<PropsWithChildren> = ({ children }) => {
const checkIfUserIsAuthenticated = async () => {
if (!session.isValid()) {
logger.warn('invalid session');
session.logout(true /* rememberLocation */);
session.logoutWithoutSlo(true /* rememberLocation */);
return;
}

Expand All @@ -66,7 +66,7 @@ const Authenticated: React.FC<PropsWithChildren> = ({ children }) => {
} catch (e) {
if (e instanceof ApiError && e.response?.status == 403) {
logger.warn('invalid session');
session.logout(true /* rememberLocation */);
session.logoutWithoutSlo(true /* rememberLocation */);
// No need to update attempt, as `logout` will
// redirect user to login page.
return;
Expand Down Expand Up @@ -125,7 +125,7 @@ function startActivityChecker(ttl = 0) {
// ie. browser still openend but all app tabs closed.
if (isInactive(adjustedTtl)) {
logger.warn('inactive session');
session.logout();
session.logoutWithoutSlo();
return;
}

Expand All @@ -135,7 +135,7 @@ function startActivityChecker(ttl = 0) {
const intervalId = setInterval(() => {
if (isInactive(adjustedTtl)) {
logger.warn('inactive session');
session.logout();
session.logoutWithoutSlo();
}
}, ACTIVITY_CHECKER_INTERVAL_MS);

Expand Down
15 changes: 3 additions & 12 deletions web/packages/teleport/src/components/UserMenuNav/UserMenuNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,6 @@ export function UserMenuNav({ username, iconSize }: UserMenuNavProps) {
transitionDelay += INCREMENT_TRANSITION_DELAY;
}

function logout() {
if (ctx.storeUser.getIsSamlSloEnabled()) {
const sloUrl = ctx.storeUser.getSamlSloUrl();

session.logoutWithoutRedirect();
window.open(sloUrl);
} else {
session.logout();
}
}

return (
<Container ref={ref}>
<UserInfo onClick={() => setOpen(!open)} open={open}>
Expand Down Expand Up @@ -202,7 +191,9 @@ export function UserMenuNav({ username, iconSize }: UserMenuNavProps) {
)}

<DropdownItem open={open} $transitionDelay={transitionDelay}>
<DropdownItemButton onClick={() => logout()}>
<DropdownItemButton
onClick={() => session.logout(false, ctx.storeUser)}
>
<DropdownItemIcon>
<LogoutIcon />
</DropdownItemIcon>
Expand Down
1 change: 1 addition & 0 deletions web/packages/teleport/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ const cfg = {
loginError: '/web/msg/error/login',
loginErrorCallback: '/web/msg/error/login/callback',
loginErrorUnauthorized: '/web/msg/error/login/auth',
samlSloFailed: '/web/msg/error/slo',
userInvite: '/web/invite/:tokenId',
userInviteContinue: '/web/invite/:tokenId/continue',
userReset: '/web/reset/:tokenId',
Expand Down
25 changes: 22 additions & 3 deletions web/packages/teleport/src/services/websession/websession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import history from 'teleport/services/history';
import api from 'teleport/services/api';
import { KeysEnum, storageService } from 'teleport/services/storageService';

import { StoreUserContext } from 'teleport/stores';

import makeBearerToken from './makeBearerToken';
import { RenewSessionRequest } from './types';

Expand All @@ -35,14 +37,31 @@ const logger = Logger.create('services/session');
let sesstionCheckerTimerId = null;

const session = {
logout(rememberLocation = false) {
/** logout logs the user out of Teleport, and if SAML SLO is enabled, also log them out of their IdP. */
logout(rememberLocation = false, userCtx: StoreUserContext) {
api.delete(cfg.api.webSessionPath).finally(() => {
history.goToLogin(rememberLocation);
// If SAML SLO (single logout) is enabled, the user will be redirected to the IdP's SLO URL, after which they will automatically be redirected back
// to the login page.
if (userCtx?.getIsSamlSloEnabled()) {
const sloUrl = userCtx.getSamlSloUrl();
window.open(sloUrl, '_self');
} else {
history.goToLogin(rememberLocation);
}
});

this.clear();
},

/** logoutWithoutSlo logs the user out of Teleport, but not their SAML IdP. */
logoutWithoutSlo(rememberLocation = false) {
api
.delete(cfg.api.webSessionPath)
.finally(() => history.goToLogin(rememberLocation));

this.clear();
},

logoutWithoutRedirect() {
api.delete(cfg.api.webSessionPath);

Expand Down Expand Up @@ -254,7 +273,7 @@ function receiveMessage(event) {

// check if logout was triggered from other tabs
if (storageService.getBearerToken() === null) {
session.logout();
session.logoutWithoutSlo();
}

// check if token is being renewed from another tab
Expand Down

0 comments on commit f80e08f

Please sign in to comment.