Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle an expired admin user link #17667

Merged
merged 3 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ repos:
exclude: |
(?x)^(
.*example-layer.tar.gz|
.*\.(png|svg|ico|gpg)|
.*\.(png|svg|ico|gpg|webp)|
dev/version-manifest/version-manifest|
.*gradle-wrapper\.jar
)$
Expand Down
1 change: 1 addition & 0 deletions components/dashboard/BUILD.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ packages:
- "src/**/*.css"
- "src/**/*.svg"
- "src/**/*.png"
- "src/**/*.webp"
- "typings/**"
- package.json
- tailwind.config.js
Expand Down
22 changes: 13 additions & 9 deletions components/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,16 @@
* See License.AGPL.txt in the project root for license information.
*/

import React, { FC, Suspense, useEffect } from "react";
import { FC, Suspense, useEffect } from "react";
import { AppLoading } from "./app/AppLoading";
import { AppRoutes } from "./app/AppRoutes";
import { useCurrentOrg } from "./data/organizations/orgs-query";
import { useAnalyticsTracking } from "./hooks/use-analytics-tracking";
import { useUserLoader } from "./hooks/use-user-loader";
import { Login } from "./Login";
import { AppBlockingFlows } from "./app/AppBlockingFlows";
import { useHistory } from "react-router";

// Wrap the App in an ErrorBoundary to catch User/Org loading errors
// This will also catch any errors that happen to bubble all the way up to the top
const AppWithErrorBoundary: FC = () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was leftover and not needed anymore.

return <App />;
};
import { Route, Switch, useHistory } from "react-router";
import { ErrorPages } from "./error-pages/ErrorPages";

export const StartWorkspaceModalKeyBinding = `${/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? "⌘" : "Ctrl﹢"}O`;

Expand Down Expand Up @@ -72,4 +67,13 @@ const App: FC = () => {
);
};

export default AppWithErrorBoundary;
// Routing level above main App component for any routes that don't need user/orgs loaded, such as addressable error pages
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a layer above our App to let us render things like addressable error pages w/ their own routes. They won't load any external data by default.

export const RootAppRouter: FC = () => {
return (
<Switch>
{/* Any route that starts w/ `/error` will render a specific error page if it matches a route, otherwise a generic error page */}
<Route path="/error" component={ErrorPages} />
<Route path="*" component={App} />
</Switch>
);
};
32 changes: 32 additions & 0 deletions components/dashboard/src/error-pages/DefaultError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import { FC } from "react";
import { Heading1, Subheading } from "../components/typography/headings";
import { ErrorPageLayout } from "./ErrorPageLayout";
import img404 from "../images/404.webp";
import img4042x from "../images/404@2x.webp";

const DefaultError: FC = () => {
return (
<ErrorPageLayout>
<img
className="mb-8"
src={img404}
srcSet={`${img404} 1x, ${img4042x} 2x`}
alt="404 illustration"
width="512"
height="422"
/>
<Heading1>Oops!</Heading1>
<Subheading className="mt-4">Something didn't work quite right.</Subheading>

<Subheading className="mt-4">Please contact Gitpod if you continue to have this issue.</Subheading>
</ErrorPageLayout>
);
};

export default DefaultError;
27 changes: 27 additions & 0 deletions components/dashboard/src/error-pages/ErrorPageLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import gitpodIcon from "../images/gitpod.svg";
import gitpodDarkIcon from "../images/gitpod-dark.svg";
import { useTheme } from "../theme-context";
import { FC } from "react";
import "../dedicated-setup/styles.css";

export const ErrorPageLayout: FC = ({ children }) => {
const { isDark } = useTheme();
return (
<div className="container">
<div className="app-container">
<div className="flex items-center justify-center items-center py-3">
<img src={isDark ? gitpodDarkIcon : gitpodIcon} className="h-8" alt="Gitpod's logo" />
</div>
<div className={`mt-24 max-w-lg mx-auto text-center`}>
<div>{children}</div>
</div>
</div>
</div>
);
};
29 changes: 29 additions & 0 deletions components/dashboard/src/error-pages/ErrorPages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import { FC, Suspense, lazy } from "react";
import { Route, Switch, useRouteMatch } from "react-router";
import { AppLoading } from "../app/AppLoading";

const ExpiredOTS = lazy(() => import("./ExpiredOTS"));
const DefaultError = lazy(() => import("./DefaultError"));

// Mounted under the `/error` path
// Intended to handle error pages we can redirect to w/ distinct urls
export const ErrorPages: FC = () => {
let match = useRouteMatch();

return (
<Suspense fallback={<AppLoading />}>
<Switch>
{/* Matching /error/expired-ots */}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add other addressable error pages here and use them for other cases where the api needs to redirect somewhere to show the user an error.

<Route path={`${match.path}/expired-ots`} exact component={ExpiredOTS} />
{/* Matching any error/* routes */}
<Route path={match.path} component={DefaultError} />
</Switch>
</Suspense>
);
};
32 changes: 32 additions & 0 deletions components/dashboard/src/error-pages/ExpiredOTS.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import { FC } from "react";
import { Heading1, Subheading } from "../components/typography/headings";
import { ErrorPageLayout } from "./ErrorPageLayout";
import img404 from "../images/404.webp";
import img4042x from "../images/404@2x.webp";

const ExpiredOTS: FC = () => {
return (
<ErrorPageLayout>
<img
className="mb-8"
src={img404}
srcSet={`${img404} 1x, ${img4042x} 2x`}
alt="404 illustration"
width="512"
height="422"
/>
<Heading1>Oops!</Heading1>
<Subheading className="mt-4">The setup link expired.</Subheading>

<Subheading className="mt-4">Please contact Gitpod to receive a new link.</Subheading>
</ErrorPageLayout>
);
};

export default ExpiredOTS;
Binary file added components/dashboard/src/images/404.webp
Binary file not shown.
Binary file added components/dashboard/src/images/404@2x.webp
Binary file not shown.
Binary file removed components/dashboard/src/images/cubicle-dark.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed components/dashboard/src/images/cubicle.png
Binary file not shown.
Binary file added components/dashboard/src/images/cubicle.webp
Binary file not shown.
Binary file removed components/dashboard/src/images/cubicle@2x.png
Binary file not shown.
Binary file added components/dashboard/src/images/cubicle@2x.webp
Binary file not shown.
4 changes: 2 additions & 2 deletions components/dashboard/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import utc from "dayjs/plugin/utc";
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import { RootAppRouter } from "./App";
import { AdminContextProvider } from "./admin-context";
import { QueryErrorBoundary } from "./components/error-boundaries/QueryErrorBoundary";
import { ReloadPageErrorBoundary } from "./components/error-boundaries/ReloadPageErrorBoundary";
Expand Down Expand Up @@ -68,7 +68,7 @@ const bootApp = () => {
<AdminContextProvider>
<PaymentContextProvider>
<ProjectContextProvider>
<App />
<RootAppRouter />
</ProjectContextProvider>
</PaymentContextProvider>
</AdminContextProvider>
Expand Down
8 changes: 4 additions & 4 deletions components/dashboard/src/login/SetupPending.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

import { FC } from "react";
import { Heading2, Subheading } from "../components/typography/headings";
import cubicleImg from "../images/cubicle.png";
import cubicleDarkImg from "../images/cubicle-dark.png";
import cubicleImg2x from "../images/cubicle@2x.png";
import cubicleDarkImg2x from "../images/cubicle-dark@2x.png";
import cubicleImg from "../images/cubicle.webp";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched all these png to webp cause they're waaaaay smaller.

import cubicleDarkImg from "../images/cubicle-dark.webp";
import cubicleImg2x from "../images/cubicle@2x.webp";
import cubicleDarkImg2x from "../images/cubicle-dark@2x.webp";
import gitpodIcon from "../images/gitpod.svg";
import gitpodDarkIcon from "../images/gitpod-dark.svg";
import classNames from "classnames";
Expand Down
6 changes: 2 additions & 4 deletions components/server/src/user/user-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,8 @@ export class UserController {
} catch (e) {
log.error("Failed to sign-in as admin with OTS Token", e);

// Default to unauthenticated, to not leak information.
// We do not send the error response to ensure we do not disclose information.
const code = e.code || 401;
res.sendStatus(code);
// Always redirect to an expired token page if there's an error
res.redirect("/error/expired-ots", 307);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsure if we want to try and classify different errors, i.e. expired vs. invalid. If so, we can have an error route for each one.

return;
}
});
Expand Down