Skip to content

Commit

Permalink
add dedicated edit/create pages for auth connectors (#50749)
Browse files Browse the repository at this point in the history
  • Loading branch information
rudream authored and mvbrock committed Jan 18, 2025
1 parent c11b42c commit 4f92de5
Show file tree
Hide file tree
Showing 17 changed files with 714 additions and 158 deletions.
3 changes: 3 additions & 0 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,9 @@ func (h *Handler) bindDefaultEndpoints() {

h.GET("/webapi/github", h.WithAuth(h.getGithubConnectorsHandle))
h.POST("/webapi/github", h.WithAuth(h.createGithubConnectorHandle))
// The extra "connector" in the path is to avoid a wildcard conflict with the github handlers used
// during the login flow ("github/login/web" and "github/callback").
h.GET("/webapi/github/connector/:name", h.WithAuth(h.getGithubConnectorHandle))
h.PUT("/webapi/github/:name", h.WithAuth(h.updateGithubConnectorHandle))
h.DELETE("/webapi/github/:name", h.WithAuth(h.deleteGithubConnector))

Expand Down
15 changes: 15 additions & 0 deletions lib/web/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ func (h *Handler) getPresetRoles(w http.ResponseWriter, r *http.Request, p httpr
return ui.NewRoles(presets)
}

// getGithubConnectorHandle returns a GitHub connector by name.
func (h *Handler) getGithubConnectorHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) {
clt, err := ctx.GetClient()
if err != nil {
return nil, trace.Wrap(err)
}

connector, err := clt.GetGithubConnector(r.Context(), params.ByName("name"), true)
if err != nil {
return nil, trace.Wrap(err)
}

return ui.NewResourceItem(connector)
}

func (h *Handler) getGithubConnectorsHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) {
clt, err := ctx.GetClient()
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* Teleport
* Copyright (C) 2025 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 { Link as RouterLink } from 'react-router-dom';

import { Link } from 'design';
import { Alert } from 'design/Alert';
import Box from 'design/Box';
import { ButtonPrimary, ButtonSecondary } from 'design/Button';
import Flex from 'design/Flex';
import { ArrowBack } from 'design/Icon';
import { Indicator } from 'design/Indicator';
import { H1, H3 } from 'design/Text';
import { P } from 'design/Text/Text';
import TextEditor from 'shared/components/TextEditor';
import { Attempt } from 'shared/hooks/useAsync';

import { DesktopDescription } from 'teleport/AuthConnectors/styles/AuthConnectors.styles';
import { FeatureBox, FeatureHeaderTitle } from 'teleport/components/Layout';

import { description } from '../AuthConnectors';

/**
* AuthConnectorEditorContent is a the content of an Auth Connector editor page.
*/
export function AuthConnectorEditorContent({
title,
content,
backButtonRoute,
isSaveDisabled,
saveAttempt,
fetchAttempt,
onSave,
onCancel,
setContent,
isGithub,
}: Props) {
return (
<FeatureBox>
<FeatureHeaderTitle py={3} mb={2}>
<Flex alignItems="center">
<ArrowBack
as={RouterLink}
mr={2}
size="large"
color="text.main"
to={backButtonRoute}
/>
<Box mr={4}>
<H1>{title}</H1>
</Box>
</Flex>
</FeatureHeaderTitle>
{fetchAttempt.status === 'error' && (
<Alert>{fetchAttempt.statusText}</Alert>
)}
{fetchAttempt.status === 'processing' && (
<Flex alignItems="center" justifyContent="center">
<Indicator />
</Flex>
)}
{fetchAttempt.status === 'success' && (
<Flex width="100%" height="100%">
<Flex
alignItems="start"
flexDirection={'column'}
height="100%"
flex={4}
>
{saveAttempt.status === 'error' && (
<Alert width="100%">{saveAttempt.statusText}</Alert>
)}
<Flex height="600px" width="100%">
{content && (
<TextEditor
bg="levels.deep"
readOnly={false}
data={[{ content, type: 'yaml' }]}
onChange={setContent}
/>
)}
</Flex>
<Box mt={3}>
<ButtonPrimary disabled={isSaveDisabled} onClick={onSave} mr="3">
Save Changes
</ButtonPrimary>
<ButtonSecondary
disabled={saveAttempt.status === 'processing'}
onClick={onCancel}
>
Cancel
</ButtonSecondary>
</Box>
</Flex>
<DesktopDescription>
<H3 mb={3}>Auth Connectors</H3>
<P mb={3}>{description}</P>
{isGithub ? (
<P mb={2}>
Please
<Link
color="text.main"
href="https://goteleport.com/docs/setup/admin/github-sso/"
target="_blank"
>
view our documentation
</Link>{' '}
on how to configure a GitHub connector.
</P>
) : (
<P>
Please{' '}
<Link
color="text.main"
href="https://goteleport.com/docs/admin-guides/access-controls/sso/"
target="_blank"
>
view our documentation
</Link>{' '}
for samples of each connector.
</P>
)}
</DesktopDescription>
</Flex>
)}
</FeatureBox>
);
}

type Props = {
title: string;
content: string;
backButtonRoute: string;
isSaveDisabled: boolean;
saveAttempt: Attempt<void>;
fetchAttempt: Attempt<void>;
onSave: () => void;
onCancel: () => void;
setContent: (content: string) => void;
isGithub?: boolean;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Teleport
* Copyright (C) 2025 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 { delay, http, HttpResponse } from 'msw';
import { MemoryRouter, Route } from 'react-router';

import { ContextProvider } from 'teleport';
import cfg from 'teleport/config';
import { createTeleportContext } from 'teleport/mocks/contexts';

import { connectors } from '../fixtures';
import { GitHubConnectorEditor } from './GitHubConnectorEditor';

export default {
title: 'Teleport/AuthConnectors/GitHubConnectorEditor',
};

export function Processing() {
return (
<MemoryRouter
initialEntries={[
cfg.getEditAuthConnectorRoute('github', 'github_connector'),
]}
>
<ContextWrapper>
<Route path={cfg.routes.ssoConnector.edit}>
<GitHubConnectorEditor />
</Route>
</ContextWrapper>
</MemoryRouter>
);
}
Processing.parameters = {
msw: {
handlers: [
http.get(
cfg.getGithubConnectorUrl('github_connector'),
async () => await delay('infinite')
),
],
},
};

export function Loaded() {
return (
<MemoryRouter
initialEntries={[
cfg.getEditAuthConnectorRoute('github', 'github_connector'),
]}
>
<ContextWrapper>
<Route path={cfg.routes.ssoConnector.edit}>
<GitHubConnectorEditor />
</Route>
</ContextWrapper>
</MemoryRouter>
);
}
Loaded.parameters = {
msw: {
handlers: [
http.get(cfg.getGithubConnectorUrl('github_connector'), () =>
HttpResponse.json(connectors[0])
),
],
},
};

export function Failed() {
return (
<MemoryRouter
initialEntries={[
cfg.getEditAuthConnectorRoute('github', 'github_connector'),
]}
>
<ContextWrapper>
<Route path={cfg.routes.ssoConnector.edit}>
<GitHubConnectorEditor />
</Route>
</ContextWrapper>
</MemoryRouter>
);
}
Failed.parameters = {
msw: {
handlers: [
http.get(cfg.getGithubConnectorUrl('github_connector'), () =>
HttpResponse.json(
{ message: 'something went wrong' },
{
status: 500,
}
)
),
],
},
};

function ContextWrapper({ children }: { children: JSX.Element }) {
const ctx = createTeleportContext();
return <ContextProvider ctx={ctx}>{children}</ContextProvider>;
}
Loading

0 comments on commit 4f92de5

Please sign in to comment.