Path: blob/main/components/dashboard/src/provider-utils.tsx
2498 views
/**1* Copyright (c) 2021 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";7import bitbucket from "./images/bitbucket.svg";8import github from "./images/github.svg";9import gitlab from "./images/gitlab.svg";10import azuredevops from "./images/azuredevops.svg";11import { gitpodHostUrl } from "./service/service";1213function iconForAuthProvider(type: string | AuthProviderType) {14switch (type) {15case "GitHub":16case AuthProviderType.GITHUB:17return <img className="fill-current dark:filter-invert w-5 h-5 ml-3 mr-3 my-auto" src={github} alt="" />;18case "GitLab":19case AuthProviderType.GITLAB:20return <img className="fill-current filter-grayscale w-5 h-5 ml-3 mr-3 my-auto" src={gitlab} alt="" />;21case "Bitbucket":22case AuthProviderType.BITBUCKET:23return <img className="fill-current filter-grayscale w-5 h-5 ml-3 mr-3 my-auto" src={bitbucket} alt="" />;24case "BitbucketServer":25case AuthProviderType.BITBUCKET_SERVER:26return <img className="fill-current filter-grayscale w-5 h-5 ml-3 mr-3 my-auto" src={bitbucket} alt="" />;27case "AzureDevOps":28case AuthProviderType.AZURE_DEVOPS:29return <img className="fill-current filter-grayscale w-5 h-5 ml-3 mr-3 my-auto" src={azuredevops} alt="" />;30default:31return <></>;32}33}3435export function toAuthProviderLabel(type: AuthProviderType) {36switch (type) {37case AuthProviderType.GITHUB:38return "GitHub";39case AuthProviderType.GITLAB:40return "GitLab";41case AuthProviderType.BITBUCKET:42return "Bitbucket Cloud";43case AuthProviderType.BITBUCKET_SERVER:44return "Bitbucket Server";45case AuthProviderType.AZURE_DEVOPS:46return "Azure DevOps";47default:48return "-";49}50}5152function simplifyProviderName(host: string) {53switch (host) {54case "github.com":55return "GitHub";56case "gitlab.com":57return "GitLab";58case "bitbucket.org":59return "Bitbucket";60case "dev.azure.com":61return "Azure DevOps";62default:63return host;64}65}6667interface WindowMessageHandler {68onSuccess?: (payload?: string) => void;69onError?: (error: string | { error: string; description?: string }) => void;70}7172interface OpenAuthorizeWindowParams extends WindowMessageHandler {73login?: boolean;74host: string;75scopes?: string[];76overrideScopes?: boolean;77overrideReturn?: string;78}7980async function openAuthorizeWindow(params: OpenAuthorizeWindowParams) {81const { login, host, scopes, overrideScopes } = params;82const successKey = getUniqueSuccessKey();83let search = `message=${successKey}`;84const returnTo = gitpodHostUrl.with({ pathname: "complete-auth", search: search }).toString();85const requestedScopes = scopes || [];86const url = login87? gitpodHostUrl88.withApi({89pathname: "/login",90search: `host=${host}&returnTo=${encodeURIComponent(returnTo)}`,91})92.toString()93: gitpodHostUrl94.withApi({95pathname: "/authorize",96search: `returnTo=${encodeURIComponent(returnTo)}&host=${host}${97overrideScopes ? "&override=true" : ""98}&scopes=${requestedScopes.join(",")}`,99})100.toString();101102openModalWindow(url);103104attachMessageListener(successKey, params);105}106107async function redirectToAuthorize(params: OpenAuthorizeWindowParams) {108const { login, host, scopes, overrideScopes } = params;109const successKey = getUniqueSuccessKey();110const searchParamsReturn = new URLSearchParams({ message: successKey });111for (const [key, value] of new URLSearchParams(window.location.search)) {112if (key === "message") {113continue;114}115searchParamsReturn.append(key, value);116}117const returnTo = gitpodHostUrl118.with({ pathname: window.location.pathname, search: searchParamsReturn.toString(), hash: window.location.hash })119.toString();120const requestedScopes = scopes ?? [];121const url = login122? gitpodHostUrl123.withApi({124pathname: "/login",125search: `host=${host}&returnTo=${encodeURIComponent(returnTo)}`,126})127.toString()128: gitpodHostUrl129.withApi({130pathname: "/authorize",131search: `returnTo=${encodeURIComponent(returnTo)}&host=${host}${132overrideScopes ? "&override=true" : ""133}&scopes=${requestedScopes.join(",")}`,134})135.toString();136137window.location.href = url;138}139140function openModalWindow(url: string) {141const width = 800;142const height = 800;143const left = window.screen.width / 2 - width / 2;144const top = window.screen.height / 2 - height / 2;145146// Optimistically assume that the new window was opened.147window.open(148url,149"gitpod-auth-window",150`width=${width},height=${height},top=${top},left=${left},status=yes,scrollbars=yes,resizable=yes`,151);152}153154function parseError(data: string) {155let error: string | { error: string; description?: string } = atob(data.substring("error:".length));156try {157const payload = JSON.parse(error);158if (typeof payload === "object" && payload.error) {159error = { ...payload };160}161} catch (error) {162console.log(error);163}164165return error;166}167168function attachMessageListener(successKey: string, { onSuccess, onError }: WindowMessageHandler) {169const eventListener = (event: MessageEvent) => {170if (event?.origin !== document.location.origin) {171return;172}173174const killAuthWindow = () => {175window.removeEventListener("message", eventListener);176177if (event.source && "close" in event.source && event.source.close) {178console.log(`Received Auth Window Result. Closing Window.`);179event.source.close();180}181};182183if (typeof event.data === "string" && event.data.startsWith(successKey)) {184killAuthWindow();185onSuccess && onSuccess(event.data);186}187if (typeof event.data === "string" && event.data.startsWith("error:")) {188const error = parseError(event.data);189190killAuthWindow();191onError && onError(error);192}193};194window.addEventListener("message", eventListener);195}196197interface OpenOIDCStartWindowParams extends WindowMessageHandler {198orgSlug?: string;199configId?: string;200activate?: boolean;201verify?: boolean;202}203204/**205* @param orgSlug when empty, tries to log in the user using the SSO for a single-org setup206*/207async function redirectToOIDC({ orgSlug = "", configId, activate = false, verify = false }: OpenOIDCStartWindowParams) {208const successKey = getUniqueSuccessKey();209const searchParamsReturn = new URLSearchParams({ message: successKey });210for (const [key, value] of new URLSearchParams(window.location.search)) {211if (key === "message") {212continue;213}214searchParamsReturn.append(key, value);215}216const returnTo = gitpodHostUrl217.with({ pathname: window.location.pathname, search: searchParamsReturn.toString(), hash: window.location.hash })218.toString();219const searchParams = new URLSearchParams({ returnTo });220if (orgSlug) {221searchParams.append("orgSlug", orgSlug);222}223if (configId) {224searchParams.append("id", configId);225}226if (activate) {227searchParams.append("activate", "true");228} else if (verify) {229searchParams.append("verify", "true");230}231232const url = gitpodHostUrl233.with(() => ({234pathname: `/iam/oidc/start`,235search: searchParams.toString(),236}))237.toString();238239window.location.href = url;240}241242async function openOIDCStartWindow(params: OpenOIDCStartWindowParams) {243const { orgSlug, configId, activate = false, verify = false } = params;244const successKey = getUniqueSuccessKey();245let search = `message=${successKey}`;246const returnTo = gitpodHostUrl.with({ pathname: "complete-auth", search }).toString();247const searchParams = new URLSearchParams({ returnTo });248if (orgSlug) {249searchParams.append("orgSlug", orgSlug);250}251if (configId) {252searchParams.append("id", configId);253}254if (activate) {255searchParams.append("activate", "true");256} else if (verify) {257searchParams.append("verify", "true");258}259260const url = gitpodHostUrl261.with((url) => ({262pathname: `/iam/oidc/start`,263search: searchParams.toString(),264}))265.toString();266267openModalWindow(url);268269attachMessageListener(successKey, params);270}271272// Used to ensure each callback is handled uniquely273let counter = 0;274const getUniqueSuccessKey = () => {275return `success:${counter++}`;276};277278export {279iconForAuthProvider,280simplifyProviderName,281openAuthorizeWindow,282openOIDCStartWindow,283redirectToAuthorize,284redirectToOIDC,285parseError,286};287288289