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

Commitb376b2c

Browse files
authored
feat: add user/settings page for managing external auth (#10945)
Also add support for unlinking on the coder side to allow reflow.
1 parentf6891bc commitb376b2c

File tree

18 files changed

+577
-23
lines changed

18 files changed

+577
-23
lines changed

‎coderd/apidoc/docs.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/db2sdk/db2sdk.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,28 @@ import (
1616
"github.com/coder/coder/v2/provisionersdk/proto"
1717
)
1818

19-
funcExternalAuths(auths []database.ExternalAuthLink) []codersdk.ExternalAuthLink {
19+
typeExternalAuthMetastruct {
20+
Authenticatedbool
21+
ValidateErrorstring
22+
}
23+
24+
funcExternalAuths(auths []database.ExternalAuthLink,metamap[string]ExternalAuthMeta) []codersdk.ExternalAuthLink {
2025
out:=make([]codersdk.ExternalAuthLink,0,len(auths))
2126
for_,auth:=rangeauths {
22-
out=append(out,ExternalAuth(auth))
27+
out=append(out,ExternalAuth(auth,meta[auth.ProviderID]))
2328
}
2429
returnout
2530
}
2631

27-
funcExternalAuth(auth database.ExternalAuthLink) codersdk.ExternalAuthLink {
32+
funcExternalAuth(auth database.ExternalAuthLink,metaExternalAuthMeta) codersdk.ExternalAuthLink {
2833
return codersdk.ExternalAuthLink{
2934
ProviderID:auth.ProviderID,
3035
CreatedAt:auth.CreatedAt,
3136
UpdatedAt:auth.UpdatedAt,
3237
HasRefreshToken:auth.OAuthRefreshToken!="",
3338
Expires:auth.OAuthExpiry,
39+
Authenticated:meta.Authenticated,
40+
ValidateError:meta.ValidateError,
3441
}
3542
}
3643

‎coderd/externalauth.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,14 +337,44 @@ func (api *API) listUserExternalAuths(rw http.ResponseWriter, r *http.Request) {
337337
return
338338
}
339339

340+
// This process of authenticating each external link increases the
341+
// response time. However, it is necessary to more correctly debug
342+
// authentication issues.
343+
// We can do this in parallel if we want to speed it up.
344+
configs:=make(map[string]*externalauth.Config)
345+
for_,cfg:=rangeapi.ExternalAuthConfigs {
346+
configs[cfg.ID]=cfg
347+
}
348+
// Check if the links are authenticated.
349+
linkMeta:=make(map[string]db2sdk.ExternalAuthMeta)
350+
fori,link:=rangelinks {
351+
iflink.OAuthAccessToken!="" {
352+
cfg,ok:=configs[link.ProviderID]
353+
ifok {
354+
newLink,valid,err:=cfg.RefreshToken(ctx,api.Database,link)
355+
meta:= db2sdk.ExternalAuthMeta{
356+
Authenticated:valid,
357+
}
358+
iferr!=nil {
359+
meta.ValidateError=err.Error()
360+
}
361+
// Update the link if it was potentially refreshed.
362+
iferr==nil&&valid {
363+
links[i]=newLink
364+
}
365+
break
366+
}
367+
}
368+
}
369+
340370
// Note: It would be really nice if we could cfg.Validate() the links and
341371
// return their authenticated status. To do this, we would also have to
342372
// refresh expired tokens too. For now, I do not want to cause the excess
343373
// traffic on this request, so the user will have to do this with a separate
344374
// call.
345375
httpapi.Write(ctx,rw,http.StatusOK, codersdk.ListUserExternalAuthResponse{
346376
Providers:ExternalAuthConfigs(api.ExternalAuthConfigs),
347-
Links:db2sdk.ExternalAuths(links),
377+
Links:db2sdk.ExternalAuths(links,linkMeta),
348378
})
349379
}
350380

‎codersdk/externalauth.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ type ExternalAuthLink struct {
7676
UpdatedAt time.Time`json:"updated_at" format:"date-time"`
7777
HasRefreshTokenbool`json:"has_refresh_token"`
7878
Expires time.Time`json:"expires" format:"date-time"`
79+
Authenticatedbool`json:"authenticated"`
80+
ValidateErrorstring`json:"validate_error"`
7981
}
8082

8183
// ExternalAuthLinkProvider are the static details of a provider.

‎docs/api/git.md

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎docs/api/schemas.md

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎site/src/AppRouter.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ const ObservabilitySettingsPage = lazy(
132132
constExternalAuthPage=lazy(
133133
()=>import("./pages/ExternalAuthPage/ExternalAuthPage"),
134134
);
135+
constUserExternalAuthSettingsPage=lazy(
136+
()=>
137+
import("./pages/UserExternalAuthSettingsPage/UserExternalAuthSettingsPage"),
138+
);
135139
constTemplateVersionPage=lazy(
136140
()=>import("./pages/TemplateVersionPage/TemplateVersionPage"),
137141
);
@@ -265,6 +269,10 @@ export const AppRouter: FC = () => {
265269
<Routepath="versions">
266270
<Routepath=":version">
267271
<Routeindexelement={<TemplateVersionPage/>}/>
272+
<Route
273+
path="edit"
274+
element={<TemplateVersionEditorPage/>}
275+
/>
268276
</Route>
269277
</Route>
270278
</Route>
@@ -320,6 +328,10 @@ export const AppRouter: FC = () => {
320328
<Routepath="schedule"element={<SchedulePage/>}/>
321329
<Routepath="security"element={<SecurityPage/>}/>
322330
<Routepath="ssh-keys"element={<SSHKeysPage/>}/>
331+
<Route
332+
path="external-auth"
333+
element={<UserExternalAuthSettingsPage/>}
334+
/>
323335
<Routepath="tokens">
324336
<Routeindexelement={<TokensPage/>}/>
325337
<Routepath="new"element={<CreateTokenPage/>}/>
@@ -366,17 +378,13 @@ export const AppRouter: FC = () => {
366378
<Routepath="*"element={<NotFoundPage/>}/>
367379
</Route>
368380

369-
{/*Pages that don't have the dashboard layout */}
381+
{/*Terminal and CLI auth pages don't have the dashboard layout */}
370382
<Route
371383
path="/:username/:workspace/terminal"
372384
element={<TerminalPage/>}
373385
/>
374386
<Routepath="/cli-auth"element={<CliAuthenticationPage/>}/>
375387
<Routepath="/icons"element={<IconsPage/>}/>
376-
<Route
377-
path="/templates/:template/versions/:version/edit"
378-
element={<TemplateVersionEditorPage/>}
379-
/>
380388
</Route>
381389
</Routes>
382390
</Router>

‎site/src/api/api.ts

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

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

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
constgetUserExternalAuthKey=(providerID:string)=>[
15+
providerID,
16+
"get",
17+
"external-auth",
18+
];
19+
20+
exportconstuserExternalAuth=(providerID:string)=>{
21+
return{
22+
queryKey:getUserExternalAuthKey(providerID),
23+
queryFn:()=>API.getExternalAuthProvider(providerID),
24+
};
25+
};
26+
27+
exportconstvalidateExternalAuth=(_:QueryClient)=>{
28+
return{
29+
mutationFn:API.getExternalAuthProvider,
30+
};
31+
};
32+
33+
exportconstunlinkExternalAuths=(queryClient:QueryClient)=>{
34+
return{
35+
mutationFn:API.unlinkExternalAuthProvider,
36+
onSuccess:async()=>{
37+
awaitqueryClient.invalidateQueries(["external-auth"]);
38+
},
39+
};
40+
};

‎site/src/api/typesGenerated.ts

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎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>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp