Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6a98155

Browse files
committed
feat: add user/settings page for managing external auth
1 parentb34827f commit6a98155

File tree

8 files changed

+347
-8
lines changed

8 files changed

+347
-8
lines changed

‎site/src/AppRouter.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ const ObservabilitySettingsPage = lazy(
131131
constExternalAuthPage=lazy(
132132
()=>import("./pages/ExternalAuthPage/ExternalAuthPage"),
133133
);
134+
constUserExternalAuthSettingsPage=lazy(
135+
()=>
136+
import("./pages/UserExternalAuthSettingsPage/UserExternalAuthSettingsPage"),
137+
);
134138
constTemplateVersionPage=lazy(
135139
()=>import("./pages/TemplateVersionPage/TemplateVersionPage"),
136140
);
@@ -259,6 +263,10 @@ export const AppRouter: FC = () => {
259263
<Routepath="versions">
260264
<Routepath=":version">
261265
<Routeindexelement={<TemplateVersionPage/>}/>
266+
<Route
267+
path="edit"
268+
element={<TemplateVersionEditorPage/>}
269+
/>
262270
</Route>
263271
</Route>
264272
</Route>
@@ -314,6 +322,10 @@ export const AppRouter: FC = () => {
314322
<Routepath="schedule"element={<SchedulePage/>}/>
315323
<Routepath="security"element={<SecurityPage/>}/>
316324
<Routepath="ssh-keys"element={<SSHKeysPage/>}/>
325+
<Route
326+
path="external-auth"
327+
element={<UserExternalAuthSettingsPage/>}
328+
/>
317329
<Routepath="tokens">
318330
<Routeindexelement={<TokensPage/>}/>
319331
<Routepath="new"element={<CreateTokenPage/>}/>
@@ -342,17 +354,13 @@ export const AppRouter: FC = () => {
342354
</Route>
343355
</Route>
344356

345-
{/*Pages that don't have the dashboard layout */}
357+
{/*Terminal and CLI auth pages don't have the dashboard layout */}
346358
<Route
347359
path="/:username/:workspace/terminal"
348360
element={<TerminalPage/>}
349361
/>
350362
<Routepath="/cli-auth"element={<CliAuthenticationPage/>}/>
351363
<Routepath="/icons"element={<IconsPage/>}/>
352-
<Route
353-
path="/templates/:template/versions/:version/edit"
354-
element={<TemplateVersionEditorPage/>}
355-
/>
356364
</Route>
357365

358366
{/* Using path="*"" means "match anything", so this route

‎site/src/api/api.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,19 @@ export const exchangeExternalAuthDevice = async (
937937
returnresp.data;
938938
};
939939

940+
exportconstgetUserExternalAuthProviders=async(
941+
):Promise<TypesGen.UserExternalAuthResponse>=>{
942+
constresp=awaitaxios.get(`/api/v2/external-auth`);
943+
returnresp.data;
944+
};
945+
946+
exportconstunlinkExternalAuthProvider=async(
947+
provider:string,
948+
):Promise<string>=>{
949+
constresp=awaitaxios.delete(`/api/v2/external-auth/${provider}`);
950+
returnresp.data;
951+
};
952+
940953
exportconstgetAuditLogs=async(
941954
options:TypesGen.AuditLogsRequest,
942955
):Promise<TypesGen.AuditLogResponse>=>{

‎site/src/api/queries/externalauth.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import*asAPIfrom"api/api";
2+
import{QueryClient}from"react-query";
3+
4+
constgetUserExternalAuthsKey=()=>["list","external-auth"];
5+
6+
// listUserExternalAuths returns all configured external auths for a given user.
7+
exportconstlistUserExternalAuths=()=>{
8+
return{
9+
queryKey:getUserExternalAuthsKey(),
10+
queryFn:()=>API.getUserExternalAuthProviders(),
11+
};
12+
};
13+
14+
exportconstvalidateExternalAuth=(_:QueryClient)=>{
15+
return{
16+
mutationFn:API.getExternalAuthProvider,
17+
onSuccess:async()=>{
18+
// No invalidation needed.
19+
},
20+
};
21+
};
22+
23+
exportconstunlinkExternalAuths=(queryClient:QueryClient)=>{
24+
return{
25+
mutationFn:API.unlinkExternalAuthProvider,
26+
onSuccess:async()=>{
27+
awaitqueryClient.invalidateQueries(["external-auth"]);
28+
},
29+
};
30+
};

‎site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export interface DeleteDialogProps {
1818
name:string;
1919
info?:string;
2020
confirmLoading?:boolean;
21+
verb?:string;
22+
title?:string;
23+
label?:string;
24+
confirmText?:string;
2125
}
2226

2327
exportconstDeleteDialog:FC<PropsWithChildren<DeleteDialogProps>>=({
@@ -28,6 +32,11 @@ export const DeleteDialog: FC<PropsWithChildren<DeleteDialogProps>> = ({
2832
info,
2933
name,
3034
confirmLoading,
35+
// All optional to change the verbiage. For example, "unlinking" vs "deleting"
36+
verb,
37+
title,
38+
label,
39+
confirmText,
3140
})=>{
3241
consthookId=useId();
3342
consttheme=useTheme();
@@ -52,14 +61,17 @@ export const DeleteDialog: FC<PropsWithChildren<DeleteDialogProps>> = ({
5261
type="delete"
5362
hideCancel={false}
5463
open={isOpen}
55-
title={`Delete${entity}`}
64+
title={title??`Delete${entity}`}
5665
onConfirm={onConfirm}
5766
onClose={onCancel}
5867
confirmLoading={confirmLoading}
5968
disabled={!deletionConfirmed}
69+
confirmText={confirmText}
6070
description={
6171
<>
62-
<p>Deleting this{entity} is irreversible!</p>
72+
<p>
73+
{verb??"Deleting"} this{entity} is irreversible!
74+
</p>
6375

6476
{Boolean(info)&&(
6577
<pcss={{color:theme.palette.warning.light}}>{info}</p>
@@ -84,7 +96,7 @@ export const DeleteDialog: FC<PropsWithChildren<DeleteDialogProps>> = ({
8496
onChange={(event)=>setUserConfirmationText(event.target.value)}
8597
onFocus={()=>setIsFocused(true)}
8698
onBlur={()=>setIsFocused(false)}
87-
label={`Name of the${entity} to delete`}
99+
label={label??`Name of the${entity} to delete`}
88100
color={inputColor}
89101
error={displayErrorMessage}
90102
helperText={

‎site/src/components/SettingsLayout/Sidebar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
SidebarHeader,
1212
SidebarNavItem,
1313
}from"components/Sidebar/Sidebar";
14+
import{GitIcon}from"components/Icons/GitIcon";
1415

1516
exportconstSidebar:React.FC<{user:User}>=({ user})=>{
1617
const{ entitlements}=useDashboard();
@@ -40,6 +41,9 @@ export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
4041
<SidebarNavItemhref="ssh-keys"icon={FingerprintOutlinedIcon}>
4142
SSH Keys
4243
</SidebarNavItem>
44+
<SidebarNavItemhref="external-auth"icon={GitIcon}>
45+
External Authentication
46+
</SidebarNavItem>
4347
<SidebarNavItemhref="tokens"icon={VpnKeyOutlined}>
4448
Tokens
4549
</SidebarNavItem>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import{FC,useState}from"react";
2+
import{UserExternalAuthSettingsPageView}from"./UserExternalAuthSettingsPageView";
3+
import{
4+
listUserExternalAuths,
5+
unlinkExternalAuths,
6+
validateExternalAuth,
7+
}from"api/queries/externalauth";
8+
import{Section}from"components/SettingsLayout/Section";
9+
import{DeleteDialog}from"components/Dialogs/DeleteDialog/DeleteDialog";
10+
import{useMutation,useQuery,useQueryClient}from"react-query";
11+
import{displayError,displaySuccess}from"components/GlobalSnackbar/utils";
12+
import{getErrorMessage}from"api/errors";
13+
14+
constUserExternalAuthSettingsPage:FC=()=>{
15+
constqueryClient=useQueryClient();
16+
17+
constuserExternalAuthsQuery=useQuery(listUserExternalAuths());
18+
19+
const[appToUnlink,setAppToUnlink]=useState<string>();
20+
constunlinkAppMutation=useMutation(unlinkExternalAuths(queryClient));
21+
22+
constvalidateAppMutation=useMutation(validateExternalAuth(queryClient));
23+
24+
return(
25+
<Sectiontitle="External Authentication">
26+
<UserExternalAuthSettingsPageView
27+
isLoading={userExternalAuthsQuery.isLoading}
28+
getAuthsError={userExternalAuthsQuery.error}
29+
auths={userExternalAuthsQuery.data}
30+
onUnlinkExternalAuth={(providerID:string)=>{
31+
setAppToUnlink(providerID);
32+
}}
33+
onValidateExternalAuth={async(providerID:string)=>{
34+
try{
35+
awaitvalidateAppMutation.mutateAsync(providerID,{
36+
onSuccess:(data)=>{
37+
if(data.authenticated){
38+
displaySuccess("Application link is valid.");
39+
}else{
40+
displayError(
41+
"Application link is not valid. Please unlink the application and reauthenticate.",
42+
);
43+
}
44+
},
45+
});
46+
}catch(e){
47+
displayError(
48+
getErrorMessage(e,"Error validating application link."),
49+
);
50+
}
51+
}}
52+
/>
53+
<DeleteDialog
54+
key={appToUnlink}
55+
title="Unlink Application"
56+
verb="Unlinking"
57+
info="This does not revoke the access token from the oauth2 provider.
58+
It only removes the link on this side. To fully revoke access, you must
59+
do so on the oauth2 provider's side."
60+
label="Name of the application to unlink"
61+
isOpen={appToUnlink!==undefined}
62+
confirmLoading={unlinkAppMutation.isLoading}
63+
name={appToUnlink??""}
64+
entity="application"
65+
onCancel={()=>setAppToUnlink(undefined)}
66+
onConfirm={async()=>{
67+
try{
68+
awaitunlinkAppMutation.mutateAsync(appToUnlink!);
69+
setAppToUnlink(undefined);
70+
displaySuccess("Successfully unlinked the oauth2 application.");
71+
}catch(e){
72+
displayError(getErrorMessage(e,"Error unlinking application."));
73+
}
74+
}}
75+
/>
76+
</Section>
77+
);
78+
};
79+
80+
exportdefaultUserExternalAuthSettingsPage;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// import { ExternalAuthSettingsPageView } from "./ExternalAuthSettingsPageView";
2+
// import type { Meta, StoryObj } from "@storybook/react";
3+
4+
// const meta: Meta<typeof ExternalAuthSettingsPageView> = {
5+
// title: "pages/DeploySettingsPage/ExternalAuthSettingsPageView",
6+
// component: ExternalAuthSettingsPageView,
7+
// args: {
8+
// config: {
9+
// external_auth: [
10+
// {
11+
// id: "0000-1111",
12+
// type: "GitHub",
13+
// client_id: "client_id",
14+
// regex: "regex",
15+
// auth_url: "",
16+
// token_url: "",
17+
// validate_url: "",
18+
// app_install_url: "https://github.com/apps/coder/installations/new",
19+
// app_installations_url: "",
20+
// no_refresh: false,
21+
// scopes: [],
22+
// extra_token_keys: [],
23+
// device_flow: true,
24+
// device_code_url: "",
25+
// display_icon: "",
26+
// display_name: "GitHub",
27+
// },
28+
// ],
29+
// },
30+
// },
31+
// };
32+
33+
// export default meta;
34+
// type Story = StoryObj<typeof ExternalAuthSettingsPageView>;
35+
36+
// export const Page: Story = {};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp