Gero Posmyk-Leinemann d42b06d3b0
[CLC-2032] Block login and workspace operations for Classic PAYG sunset (#21100)
* [CLC-2032] Block login and workspace operations for Classic PAYG sunset

Implement feature flag-based blocking for Gitpod Classic PAYG users:

Backend:
- Add utility functions to check if user is blocked by sunset
- Block login attempts in /login route handler, redirect to app.ona.com
- Block workspace creation and start operations in workspace-service-api
- Exempt users with roles/permissions and users in exempted organizations

Frontend:
- Update login page to show 'Login with Ona' button when sunset is enabled
- Keep SSO login form visible for exempted organizations
- Hide sunset notice banner when flag is enabled
- Update heading to 'Gitpod Classic has sunset'

Feature flag: classic_payg_sunset_enabled (JSON with enabled boolean and exemptedOrganizations array)

Co-authored-by: Ona <no-reply@ona.com>

* Add oldLogin parameter to show full login UI for exempted orgs

When sunset is enabled on gitpod.io, users now see a simplified UI:
- 'Continue with Ona' button (default)
- Link to show all login options (?oldLogin=true)

With ?oldLogin=true parameter:
- Shows all OAuth provider buttons
- Shows SSO login form
- Full functionality for exempted organizations

The link preserves returnToPath parameter if present.

Co-authored-by: Ona <no-reply@ona.com>

* Refactor backend sunset checks into separate functions

Split sunset blocking logic into two functions:
- isUserLoginBlockedBySunset: checks roles/permissions exemption for login
- isWorkspaceStartBlockedBySunset: checks org-level exemption for workspace ops

Move ClassicPaygSunsetConfig interface to gitpod-protocol for reusability.
Pass organizationId explicitly to workspace blocking checks.

Co-authored-by: Ona <no-reply@ona.com>

* Use typed ClassicPaygSunsetConfig in frontend feature flag

Import ClassicPaygSunsetConfig type from gitpod-protocol and use it
as the default value for classic_payg_sunset_enabled feature flag.

This leverages TypeScript's generic type inference in useFeatureFlag:
- useFeatureFlag<K extends keyof FeatureFlags> returns FeatureFlags[K]
- For classic_payg_sunset_enabled, it now returns ClassicPaygSunsetConfig
- Other flags continue to return their respective types (boolean, string, etc.)

Updated Login.tsx to access .enabled property with type guard to handle
the union type (ClassicPaygSunsetConfig | boolean) during loading state.

This ensures type safety and consistency between frontend and backend.

Co-authored-by: Ona <no-reply@ona.com>

* Parse JSON string for classic_payg_sunset_enabled feature flag

ConfigCat text flags return strings, so we need to parse JSON on both
frontend and backend.

Backend (featureflags.ts):
- Send JSON.stringify(defaultConfig) to ConfigCat
- Parse returned string with JSON.parse()
- Handle errors gracefully with fallback to default

Frontend (featureflag-query.ts):
- Add parseFeatureFlagValue() helper for JSON flags
- Send stringified default for classic_payg_sunset_enabled
- Parse returned string value
- Maintain type safety with generic return types

This allows ConfigCat to store the flag as text while maintaining
the typed object structure in our code.

Co-authored-by: Ona <no-reply@ona.com>

* Exempt dedicated installations from sunset blocking

Add isDedicatedInstallation parameter to sunset check functions.
Dedicated installations always return false (not blocked) regardless
of feature flag state.

Changes:
- isUserLoginBlockedBySunset: add isDedicatedInstallation param
- isWorkspaceStartBlockedBySunset: add isDedicatedInstallation param
- UserController: pass config.isDedicatedInstallation to login check
- WorkspaceServiceAPI: inject Config and pass isDedicatedInstallation

This ensures the sunset only affects gitpod.io (PAYG) and not
dedicated installations.

Co-authored-by: Ona <no-reply@ona.com>

* update login page for Gitpod classic `gitpod.io` users

* fix

* Improve sunset UI: use primary button and remove redundant subheading

Co-authored-by: Ona <no-reply@ona.com>

* Update sunset UI heading to focus on Ona value proposition

Co-authored-by: Ona <no-reply@ona.com>

---------

Co-authored-by: Ona <no-reply@ona.com>
Co-authored-by: Siddhant Khare <siddhant@gitpod.io>
Co-authored-by: Cornelius A. Ludmann <github@cornelius-ludmann.de>
2025-10-15 09:06:36 -04:00
..
2023-02-10 09:24:15 +01:00
2022-12-08 13:05:19 -03:00
2020-08-25 09:25:15 +00:00
2023-08-24 13:41:50 +02:00
2021-03-22 18:32:12 +01:00
2025-10-09 06:32:42 -04:00
2024-12-12 08:55:33 -05:00
2020-08-25 09:25:15 +00:00
2020-08-25 09:25:15 +00:00

Dashboard

The dashboard is written in TypeScript and React. For styling it uses TailwindCSS which is a bit nicer than inlining CSS as it supports pseudo classes and a is a little more abstract/reusable.

The App.tsx is the entry point for the SPA and it uses React-Router to register all pages.

<Switch>
    <Route path="/" exact component={Workspaces} />
    <Route path="/profile" exact component={Profile} />
    <Route path="/notifications" exact component={Notifications} />
    <Route path="/subscriptions" exact component={Subscriptions} />
    <Route path="/env-vars" exact component={EnvVars} />
    <Route path="/git-integration" exact component={GitIntegration} />
    <Route path="/feature-preview" exact component={FeaturePreview} />
    <Route path="/default-ide" exact component={DefaultIDE} />
</Switch>

Pages are loaded lazily using React.lazy so that not everything needs to be loaded up-front but only when needed:

const Notifications = React.lazy(() => import("./account/Notifications"));
const Profile = React.lazy(() => import("./account/Profile"));
const Subscriptions = React.lazy(() => import("./account/Subscriptions"));
const DefaultIDE = React.lazy(() => import("./settings/DefaultIDE"));
const EnvVars = React.lazy(() => import("./settings/EnvVars"));
const FeaturePreview = React.lazy(() => import("./settings/FeaturePreview"));
const GitIntegration = React.lazy(() => import("./settings/GitIntegration"));

Global state is passed through React.Context.

After creating a new component, run the following to update the license header: leeway run components:update-license-header

How to develop in gitpod.io

Against any* Gitpod installation

Gitpod installations have a feature that - if you are authorized - allow different versions of the dashboard. This allows for front-end development with live data and super-quick turnarounds.

Preconditions

  1. logged in user on the respective Gitpod installation (e.g. gitpod.example.org)
  2. user has the "developer" role

Steps

  1. Start a workspace (on any installation), and start the dev-server with yarn start-local
  2. Configure your browser to always send header X-Frontend-Dev-URL with value set to the result of gp url 3000 to the Gitpod installation you want to modify (gitpod.example.org)
  3. Visit https://gitpod.example.org, start modifying your dashboard in your workspace, and experience the effect live (incl. hot reloading)

*: This feature is not enabled on all installations, and requires special user privileges.

Outdated, in-workspace (?)

All the commands in this section are meant to be executed from the components/dashboard directory.

1. Environment variables

Set the following 2 environment variables either via your account settings or via the command line.

You are not expected to update the values of these variables for a long time after you first set them.

🚨 Heads up! Be careful when using the command line, as the gp CLI will restrict the scope of the variables to the current project, meaning if you are not already working from your own personal fork you'll end up having variables you can't access when you do.

You can always go to your account settings and edit the scope for each variable to something like */gitpod.

# Use "gitpod.io" for the SaaS version of Gitpod, or specify the host of your self-hosted gitpod
GP_DEV_HOST=gitpod.io

# Notice the cookie name (_gitpod_io_v2_) may be different if self-hosted.
# Read below for how to get the actual value to use instead of "AUTHENTICATION_COOKIE_VALUE"
GP_DEV_COOKIE="_gitpod_io_v2_=AUTHENTICATION_COOKIE_VALUE"

Replace AUTHENTICATION_COOKIE_VALUE with the value of your auth cookie taken from your browser's dev tools while visiting your target Gitpod host (e.g. s%3Axxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.XXXXXXXXXXXXXXX).

How to get the cookie name and value
Where to get the auth cookie name and value from

2. Start the dashboard app

🚀 After following the above steps, run yarn run start to start developing. You can view the dashboard at https://PORT_NUMBER-GITPOD_WORKSPACE_URL (PORT_NUMBER is usually 3000).

How to run tests in watch mode

Open a terminal, launch the dashboard app (see instructions above):

yarn start

When the dashboard app is up and running, open another terminal using Bash as shell (this is mandatory at the moment)