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

feat: add user/settings page for managing external auth#10945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Emyrk merged 9 commits intomainfromexternal_auth_fe_updates
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletionscoderd/apidoc/docs.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

6 changes: 6 additions & 0 deletionscoderd/apidoc/swagger.json
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

13 changes: 10 additions & 3 deletionscoderd/database/db2sdk/db2sdk.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,21 +16,28 @@ import (
"github.com/coder/coder/v2/provisionersdk/proto"
)

func ExternalAuths(auths []database.ExternalAuthLink) []codersdk.ExternalAuthLink {
type ExternalAuthMeta struct {
Authenticated bool
ValidateError string
}

func ExternalAuths(auths []database.ExternalAuthLink, meta map[string]ExternalAuthMeta) []codersdk.ExternalAuthLink {
out := make([]codersdk.ExternalAuthLink, 0, len(auths))
for _, auth := range auths {
out = append(out, ExternalAuth(auth))
out = append(out, ExternalAuth(auth, meta[auth.ProviderID]))
}
return out
}

func ExternalAuth(auth database.ExternalAuthLink) codersdk.ExternalAuthLink {
func ExternalAuth(auth database.ExternalAuthLink, meta ExternalAuthMeta) codersdk.ExternalAuthLink {
return codersdk.ExternalAuthLink{
ProviderID: auth.ProviderID,
CreatedAt: auth.CreatedAt,
UpdatedAt: auth.UpdatedAt,
HasRefreshToken: auth.OAuthRefreshToken != "",
Expires: auth.OAuthExpiry,
Authenticated: meta.Authenticated,
ValidateError: meta.ValidateError,
}
}

Expand Down
32 changes: 31 additions & 1 deletioncoderd/externalauth.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -337,14 +337,44 @@ func (api *API) listUserExternalAuths(rw http.ResponseWriter, r *http.Request) {
return
}

// This process of authenticating each external link increases the
// response time. However, it is necessary to more correctly debug
// authentication issues.
// We can do this in parallel if we want to speed it up.
configs := make(map[string]*externalauth.Config)
for _, cfg := range api.ExternalAuthConfigs {
configs[cfg.ID] = cfg
}
// Check if the links are authenticated.
linkMeta := make(map[string]db2sdk.ExternalAuthMeta)
for i, link := range links {
if link.OAuthAccessToken != "" {
cfg, ok := configs[link.ProviderID]
if ok {
newLink, valid, err := cfg.RefreshToken(ctx, api.Database, link)
meta := db2sdk.ExternalAuthMeta{
Authenticated: valid,
}
if err != nil {
meta.ValidateError = err.Error()
}
// Update the link if it was potentially refreshed.
if err == nil && valid {
links[i] = newLink
}
break
}
}
}

// Note: It would be really nice if we could cfg.Validate() the links and
// return their authenticated status. To do this, we would also have to
// refresh expired tokens too. For now, I do not want to cause the excess
// traffic on this request, so the user will have to do this with a separate
// call.
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ListUserExternalAuthResponse{
Providers: ExternalAuthConfigs(api.ExternalAuthConfigs),
Links: db2sdk.ExternalAuths(links),
Links: db2sdk.ExternalAuths(links, linkMeta),
})
}

Expand Down
2 changes: 2 additions & 0 deletionscodersdk/externalauth.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,8 @@ type ExternalAuthLink struct {
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
HasRefreshToken bool `json:"has_refresh_token"`
Expires time.Time `json:"expires" format:"date-time"`
Authenticated bool `json:"authenticated"`
ValidateError string `json:"validate_error"`
}

// ExternalAuthLinkProvider are the static details of a provider.
Expand Down
4 changes: 3 additions & 1 deletiondocs/api/git.md
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

6 changes: 5 additions & 1 deletiondocs/api/schemas.md
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

18 changes: 13 additions & 5 deletionssite/src/AppRouter.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -132,6 +132,10 @@ const ObservabilitySettingsPage = lazy(
const ExternalAuthPage = lazy(
() => import("./pages/ExternalAuthPage/ExternalAuthPage"),
);
const UserExternalAuthSettingsPage = lazy(
() =>
import("./pages/UserExternalAuthSettingsPage/UserExternalAuthSettingsPage"),
);
const TemplateVersionPage = lazy(
() => import("./pages/TemplateVersionPage/TemplateVersionPage"),
);
Expand DownExpand Up@@ -265,6 +269,10 @@ export const AppRouter: FC = () => {
<Route path="versions">
<Route path=":version">
<Route index element={<TemplateVersionPage />} />
<Route
path="edit"
element={<TemplateVersionEditorPage />}
/>
</Route>
</Route>
</Route>
Expand DownExpand Up@@ -320,6 +328,10 @@ export const AppRouter: FC = () => {
<Route path="schedule" element={<SchedulePage />} />
<Route path="security" element={<SecurityPage />} />
<Route path="ssh-keys" element={<SSHKeysPage />} />
<Route
path="external-auth"
element={<UserExternalAuthSettingsPage />}
/>
<Route path="tokens">
<Route index element={<TokensPage />} />
<Route path="new" element={<CreateTokenPage />} />
Expand DownExpand Up@@ -366,17 +378,13 @@ export const AppRouter: FC = () => {
<Route path="*" element={<NotFoundPage />} />
</Route>

{/*Pages that don't have the dashboard layout */}
{/*Terminal and CLI auth pages don't have the dashboard layout */}
<Route
path="/:username/:workspace/terminal"
element={<TerminalPage />}
/>
<Route path="/cli-auth" element={<CliAuthenticationPage />} />
<Route path="/icons" element={<IconsPage />} />
<Route
path="/templates/:template/versions/:version/edit"
element={<TemplateVersionEditorPage />}
/>
</Route>
</Routes>
</Router>
Expand Down
13 changes: 13 additions & 0 deletionssite/src/api/api.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -939,6 +939,19 @@ export const exchangeExternalAuthDevice = async (
return resp.data;
};

export const getUserExternalAuthProviders =
async (): Promise<TypesGen.ListUserExternalAuthResponse> => {
const resp = await axios.get(`/api/v2/external-auth`);
return resp.data;
};

export const unlinkExternalAuthProvider = async (
provider: string,
): Promise<string> => {
const resp = await axios.delete(`/api/v2/external-auth/${provider}`);
return resp.data;
};

export const getAuditLogs = async (
options: TypesGen.AuditLogsRequest,
): Promise<TypesGen.AuditLogResponse> => {
Expand Down
40 changes: 40 additions & 0 deletionssite/src/api/queries/externalauth.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
import * as API from "api/api";
import { QueryClient } from "react-query";

const getUserExternalAuthsKey = () => ["list", "external-auth"];

// listUserExternalAuths returns all configured external auths for a given user.
export const listUserExternalAuths = () => {
return {
queryKey: getUserExternalAuthsKey(),
queryFn: () => API.getUserExternalAuthProviders(),
};
};

const getUserExternalAuthKey = (providerID: string) => [
providerID,
"get",
"external-auth",
];

export const userExternalAuth = (providerID: string) => {
return {
queryKey: getUserExternalAuthKey(providerID),
queryFn: () => API.getExternalAuthProvider(providerID),
};
};

export const validateExternalAuth = (_: QueryClient) => {
return {
mutationFn: API.getExternalAuthProvider,
};
};

export const unlinkExternalAuths = (queryClient: QueryClient) => {
return {
mutationFn: API.unlinkExternalAuthProvider,
onSuccess: async () => {
await queryClient.invalidateQueries(["external-auth"]);
},
};
};
2 changes: 2 additions & 0 deletionssite/src/api/typesGenerated.ts
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

18 changes: 15 additions & 3 deletionssite/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,10 @@ export interface DeleteDialogProps {
name: string;
info?: string;
confirmLoading?: boolean;
verb?: string;
title?: string;
label?: string;
confirmText?: string;
}

export const DeleteDialog: FC<PropsWithChildren<DeleteDialogProps>> = ({
Expand All@@ -28,6 +32,11 @@ export const DeleteDialog: FC<PropsWithChildren<DeleteDialogProps>> = ({
info,
name,
confirmLoading,
// All optional to change the verbiage. For example, "unlinking" vs "deleting"
verb,
title,
label,
confirmText,
}) => {
const hookId = useId();
const theme = useTheme();
Expand All@@ -52,14 +61,17 @@ export const DeleteDialog: FC<PropsWithChildren<DeleteDialogProps>> = ({
type="delete"
hideCancel={false}
open={isOpen}
title={`Delete ${entity}`}
title={title ??`Delete ${entity}`}
onConfirm={onConfirm}
onClose={onCancel}
confirmLoading={confirmLoading}
disabled={!deletionConfirmed}
confirmText={confirmText}
description={
<>
<p>Deleting this {entity} is irreversible!</p>
<p>
{verb ?? "Deleting"} this {entity} is irreversible!
</p>

{Boolean(info) && (
<p css={{ color: theme.palette.warning.light }}>{info}</p>
Expand All@@ -84,7 +96,7 @@ export const DeleteDialog: FC<PropsWithChildren<DeleteDialogProps>> = ({
onChange={(event) => setUserConfirmationText(event.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
label={`Name of the ${entity} to delete`}
label={label ??`Name of the ${entity} to delete`}
color={inputColor}
error={displayErrorMessage}
helperText={
Expand Down
4 changes: 4 additions & 0 deletionssite/src/components/SettingsLayout/Sidebar.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import {
SidebarHeader,
SidebarNavItem,
} from "components/Sidebar/Sidebar";
import { GitIcon } from "components/Icons/GitIcon";

export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
const { entitlements } = useDashboard();
Expand DownExpand Up@@ -40,6 +41,9 @@ export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
<SidebarNavItem href="ssh-keys" icon={FingerprintOutlinedIcon}>
SSH Keys
</SidebarNavItem>
<SidebarNavItem href="external-auth" icon={GitIcon}>
External Authentication
</SidebarNavItem>
<SidebarNavItem href="tokens" icon={VpnKeyOutlined}>
Tokens
</SidebarNavItem>
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp