Skip to content

Commit a712958

Browse files
authored
add dedicated edit/create pages for auth connectors (#50749) (#51236)
1 parent d3decad commit a712958

17 files changed

+714
-158
lines changed

lib/web/apiserver.go

+3
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,9 @@ func (h *Handler) bindDefaultEndpoints() {
973973

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

lib/web/resources.go

+15
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,21 @@ func (h *Handler) getPresetRoles(w http.ResponseWriter, r *http.Request, p httpr
191191
return ui.NewRoles(presets)
192192
}
193193

194+
// getGithubConnectorHandle returns a GitHub connector by name.
195+
func (h *Handler) getGithubConnectorHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) {
196+
clt, err := ctx.GetClient()
197+
if err != nil {
198+
return nil, trace.Wrap(err)
199+
}
200+
201+
connector, err := clt.GetGithubConnector(r.Context(), params.ByName("name"), true)
202+
if err != nil {
203+
return nil, trace.Wrap(err)
204+
}
205+
206+
return ui.NewResourceItem(connector)
207+
}
208+
194209
func (h *Handler) getGithubConnectorsHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) {
195210
clt, err := ctx.GetClient()
196211
if err != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* Teleport
3+
* Copyright (C) 2025 Gravitational, Inc.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
import { Link as RouterLink } from 'react-router-dom';
20+
21+
import { Link } from 'design';
22+
import { Alert } from 'design/Alert';
23+
import Box from 'design/Box';
24+
import { ButtonPrimary, ButtonSecondary } from 'design/Button';
25+
import Flex from 'design/Flex';
26+
import { ArrowBack } from 'design/Icon';
27+
import { Indicator } from 'design/Indicator';
28+
import { H1, H3 } from 'design/Text';
29+
import { P } from 'design/Text/Text';
30+
import TextEditor from 'shared/components/TextEditor';
31+
import { Attempt } from 'shared/hooks/useAsync';
32+
33+
import { DesktopDescription } from 'teleport/AuthConnectors/styles/AuthConnectors.styles';
34+
import { FeatureBox, FeatureHeaderTitle } from 'teleport/components/Layout';
35+
36+
import { description } from '../AuthConnectors';
37+
38+
/**
39+
* AuthConnectorEditorContent is a the content of an Auth Connector editor page.
40+
*/
41+
export function AuthConnectorEditorContent({
42+
title,
43+
content,
44+
backButtonRoute,
45+
isSaveDisabled,
46+
saveAttempt,
47+
fetchAttempt,
48+
onSave,
49+
onCancel,
50+
setContent,
51+
isGithub,
52+
}: Props) {
53+
return (
54+
<FeatureBox>
55+
<FeatureHeaderTitle py={3} mb={2}>
56+
<Flex alignItems="center">
57+
<ArrowBack
58+
as={RouterLink}
59+
mr={2}
60+
size="large"
61+
color="text.main"
62+
to={backButtonRoute}
63+
/>
64+
<Box mr={4}>
65+
<H1>{title}</H1>
66+
</Box>
67+
</Flex>
68+
</FeatureHeaderTitle>
69+
{fetchAttempt.status === 'error' && (
70+
<Alert>{fetchAttempt.statusText}</Alert>
71+
)}
72+
{fetchAttempt.status === 'processing' && (
73+
<Flex alignItems="center" justifyContent="center">
74+
<Indicator />
75+
</Flex>
76+
)}
77+
{fetchAttempt.status === 'success' && (
78+
<Flex width="100%" height="100%">
79+
<Flex
80+
alignItems="start"
81+
flexDirection={'column'}
82+
height="100%"
83+
flex={4}
84+
>
85+
{saveAttempt.status === 'error' && (
86+
<Alert width="100%">{saveAttempt.statusText}</Alert>
87+
)}
88+
<Flex height="600px" width="100%">
89+
{content && (
90+
<TextEditor
91+
bg="levels.deep"
92+
readOnly={false}
93+
data={[{ content, type: 'yaml' }]}
94+
onChange={setContent}
95+
/>
96+
)}
97+
</Flex>
98+
<Box mt={3}>
99+
<ButtonPrimary disabled={isSaveDisabled} onClick={onSave} mr="3">
100+
Save Changes
101+
</ButtonPrimary>
102+
<ButtonSecondary
103+
disabled={saveAttempt.status === 'processing'}
104+
onClick={onCancel}
105+
>
106+
Cancel
107+
</ButtonSecondary>
108+
</Box>
109+
</Flex>
110+
<DesktopDescription>
111+
<H3 mb={3}>Auth Connectors</H3>
112+
<P mb={3}>{description}</P>
113+
{isGithub ? (
114+
<P mb={2}>
115+
Please
116+
<Link
117+
color="text.main"
118+
href="https://goteleport.com/docs/setup/admin/github-sso/"
119+
target="_blank"
120+
>
121+
view our documentation
122+
</Link>{' '}
123+
on how to configure a GitHub connector.
124+
</P>
125+
) : (
126+
<P>
127+
Please{' '}
128+
<Link
129+
color="text.main"
130+
href="https://goteleport.com/docs/admin-guides/access-controls/sso/"
131+
target="_blank"
132+
>
133+
view our documentation
134+
</Link>{' '}
135+
for samples of each connector.
136+
</P>
137+
)}
138+
</DesktopDescription>
139+
</Flex>
140+
)}
141+
</FeatureBox>
142+
);
143+
}
144+
145+
type Props = {
146+
title: string;
147+
content: string;
148+
backButtonRoute: string;
149+
isSaveDisabled: boolean;
150+
saveAttempt: Attempt<void>;
151+
fetchAttempt: Attempt<void>;
152+
onSave: () => void;
153+
onCancel: () => void;
154+
setContent: (content: string) => void;
155+
isGithub?: boolean;
156+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* Teleport
3+
* Copyright (C) 2025 Gravitational, Inc.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
import { delay, http, HttpResponse } from 'msw';
20+
import { MemoryRouter, Route } from 'react-router';
21+
22+
import { ContextProvider } from 'teleport';
23+
import cfg from 'teleport/config';
24+
import { createTeleportContext } from 'teleport/mocks/contexts';
25+
26+
import { connectors } from '../fixtures';
27+
import { GitHubConnectorEditor } from './GitHubConnectorEditor';
28+
29+
export default {
30+
title: 'Teleport/AuthConnectors/GitHubConnectorEditor',
31+
};
32+
33+
export function Processing() {
34+
return (
35+
<MemoryRouter
36+
initialEntries={[
37+
cfg.getEditAuthConnectorRoute('github', 'github_connector'),
38+
]}
39+
>
40+
<ContextWrapper>
41+
<Route path={cfg.routes.ssoConnector.edit}>
42+
<GitHubConnectorEditor />
43+
</Route>
44+
</ContextWrapper>
45+
</MemoryRouter>
46+
);
47+
}
48+
Processing.parameters = {
49+
msw: {
50+
handlers: [
51+
http.get(
52+
cfg.getGithubConnectorUrl('github_connector'),
53+
async () => await delay('infinite')
54+
),
55+
],
56+
},
57+
};
58+
59+
export function Loaded() {
60+
return (
61+
<MemoryRouter
62+
initialEntries={[
63+
cfg.getEditAuthConnectorRoute('github', 'github_connector'),
64+
]}
65+
>
66+
<ContextWrapper>
67+
<Route path={cfg.routes.ssoConnector.edit}>
68+
<GitHubConnectorEditor />
69+
</Route>
70+
</ContextWrapper>
71+
</MemoryRouter>
72+
);
73+
}
74+
Loaded.parameters = {
75+
msw: {
76+
handlers: [
77+
http.get(cfg.getGithubConnectorUrl('github_connector'), () =>
78+
HttpResponse.json(connectors[0])
79+
),
80+
],
81+
},
82+
};
83+
84+
export function Failed() {
85+
return (
86+
<MemoryRouter
87+
initialEntries={[
88+
cfg.getEditAuthConnectorRoute('github', 'github_connector'),
89+
]}
90+
>
91+
<ContextWrapper>
92+
<Route path={cfg.routes.ssoConnector.edit}>
93+
<GitHubConnectorEditor />
94+
</Route>
95+
</ContextWrapper>
96+
</MemoryRouter>
97+
);
98+
}
99+
Failed.parameters = {
100+
msw: {
101+
handlers: [
102+
http.get(cfg.getGithubConnectorUrl('github_connector'), () =>
103+
HttpResponse.json(
104+
{ message: 'something went wrong' },
105+
{
106+
status: 500,
107+
}
108+
)
109+
),
110+
],
111+
},
112+
};
113+
114+
function ContextWrapper({ children }: { children: JSX.Element }) {
115+
const ctx = createTeleportContext();
116+
return <ContextProvider ctx={ctx}>{children}</ContextProvider>;
117+
}

0 commit comments

Comments
 (0)