Skip to content

Commit 84999cb

Browse files
authored
feat: add empty state for SSO auth methods (#9818)
* fix: remove needless undefined checks * refactor: clean up button markup * refactor: restrict access to full auth in oidc functions * feat: add empty SSO state
1 parent e9077f3 commit 84999cb

File tree

1 file changed

+111
-73
lines changed

1 file changed

+111
-73
lines changed

site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx

+111-73
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@ import KeyIcon from "@mui/icons-material/VpnKey";
77
import Button from "@mui/material/Button";
88
import Typography from "@mui/material/Typography";
99
import { convertToOAUTH } from "api/api";
10-
import { AuthMethods, LoginType, UserLoginType } from "api/typesGenerated";
11-
import Skeleton from "@mui/material/Skeleton";
10+
import {
11+
AuthMethods,
12+
LoginType,
13+
OIDCAuthMethod,
14+
UserLoginType,
15+
} from "api/typesGenerated";
1216
import { Stack } from "components/Stack/Stack";
1317
import { useMutation } from "@tanstack/react-query";
1418
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
1519
import { getErrorMessage } from "api/errors";
1620
import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined";
21+
import { EmptyState } from "components/EmptyState/EmptyState";
22+
import { makeStyles } from "@mui/styles";
23+
import Link from "@mui/material/Link";
24+
import { docs } from "utils/docs";
1725

1826
type LoginTypeConfirmation =
1927
| {
@@ -93,6 +101,32 @@ export const useSingleSignOnSection = () => {
93101
};
94102
};
95103

104+
const useEmptyStateStyles = makeStyles((theme) => ({
105+
root: {
106+
minHeight: 0,
107+
padding: theme.spacing(6, 4),
108+
backgroundColor: theme.palette.background.paper,
109+
borderRadius: theme.shape.borderRadius,
110+
},
111+
}));
112+
113+
function SSOEmptyState() {
114+
const styles = useEmptyStateStyles();
115+
116+
return (
117+
<EmptyState
118+
className={styles.root}
119+
message="No SSO Providers"
120+
description="No SSO providers are configured with this Coder deployment."
121+
cta={
122+
<Link href={docs("/admin/auth")} target="_blank" rel="noreferrer">
123+
Learn how to add a provider
124+
</Link>
125+
}
126+
/>
127+
);
128+
}
129+
96130
type SingleSignOnSectionProps = ReturnType<typeof useSingleSignOnSection> & {
97131
authMethods: AuthMethods;
98132
userLoginType: UserLoginType;
@@ -108,6 +142,12 @@ export const SingleSignOnSection = ({
108142
isConfirming,
109143
error,
110144
}: SingleSignOnSectionProps) => {
145+
const authList = Object.values(
146+
authMethods,
147+
) as (typeof authMethods)[keyof typeof authMethods][];
148+
149+
const noSsoEnabled = !authList.some((method) => method.enabled);
150+
111151
return (
112152
<>
113153
<Section
@@ -116,73 +156,69 @@ export const SingleSignOnSection = ({
116156
description="Authenticate in Coder using one-click"
117157
>
118158
<Box display="grid" gap="16px">
119-
{authMethods && userLoginType ? (
120-
userLoginType.login_type === "password" ? (
121-
<>
122-
{authMethods.github.enabled && (
123-
<Button
124-
disabled={isUpdating}
125-
onClick={() => openConfirmation("github")}
126-
startIcon={<GitHubIcon sx={{ width: 16, height: 16 }} />}
127-
fullWidth
128-
size="large"
129-
>
130-
GitHub
131-
</Button>
132-
)}
133-
{authMethods.oidc.enabled && (
134-
<Button
135-
size="large"
136-
startIcon={<OIDCIcon authMethods={authMethods} />}
137-
fullWidth
138-
disabled={isUpdating}
139-
onClick={() => openConfirmation("oidc")}
140-
>
141-
{getOIDCLabel(authMethods)}
142-
</Button>
143-
)}
144-
</>
145-
) : (
146-
<Box
159+
{userLoginType.login_type === "password" ? (
160+
<>
161+
{authMethods.github.enabled && (
162+
<Button
163+
size="large"
164+
fullWidth
165+
disabled={isUpdating}
166+
startIcon={<GitHubIcon sx={{ width: 16, height: 16 }} />}
167+
onClick={() => openConfirmation("github")}
168+
>
169+
GitHub
170+
</Button>
171+
)}
172+
173+
{authMethods.oidc.enabled && (
174+
<Button
175+
size="large"
176+
fullWidth
177+
disabled={isUpdating}
178+
startIcon={<OIDCIcon oidcAuth={authMethods.oidc} />}
179+
onClick={() => openConfirmation("oidc")}
180+
>
181+
{getOIDCLabel(authMethods.oidc)}
182+
</Button>
183+
)}
184+
185+
{noSsoEnabled && <SSOEmptyState />}
186+
</>
187+
) : (
188+
<Box
189+
sx={{
190+
background: (theme) => theme.palette.background.paper,
191+
borderRadius: 1,
192+
border: (theme) => `1px solid ${theme.palette.divider}`,
193+
padding: 2,
194+
display: "flex",
195+
gap: 2,
196+
alignItems: "center",
197+
fontSize: 14,
198+
}}
199+
>
200+
<CheckCircleOutlined
147201
sx={{
148-
background: (theme) => theme.palette.background.paper,
149-
borderRadius: 1,
150-
border: (theme) => `1px solid ${theme.palette.divider}`,
151-
padding: 2,
152-
display: "flex",
153-
gap: 2,
154-
alignItems: "center",
155-
fontSize: 14,
202+
color: (theme) => theme.palette.success.light,
203+
fontSize: 16,
156204
}}
157-
>
158-
<CheckCircleOutlined
159-
sx={{
160-
color: (theme) => theme.palette.success.light,
161-
fontSize: 16,
162-
}}
163-
/>
164-
<span>
165-
Authenticated with{" "}
166-
<strong>
167-
{userLoginType.login_type === "github"
168-
? "GitHub"
169-
: getOIDCLabel(authMethods)}
170-
</strong>
171-
</span>
172-
<Box sx={{ ml: "auto", lineHeight: 1 }}>
173-
{userLoginType.login_type === "github" ? (
174-
<GitHubIcon sx={{ width: 16, height: 16 }} />
175-
) : (
176-
<OIDCIcon authMethods={authMethods} />
177-
)}
178-
</Box>
205+
/>
206+
<span>
207+
Authenticated with{" "}
208+
<strong>
209+
{userLoginType.login_type === "github"
210+
? "GitHub"
211+
: getOIDCLabel(authMethods.oidc)}
212+
</strong>
213+
</span>
214+
<Box sx={{ ml: "auto", lineHeight: 1 }}>
215+
{userLoginType.login_type === "github" ? (
216+
<GitHubIcon sx={{ width: 16, height: 16 }} />
217+
) : (
218+
<OIDCIcon oidcAuth={authMethods.oidc} />
219+
)}
179220
</Box>
180-
)
181-
) : (
182-
<Skeleton
183-
variant="rectangular"
184-
sx={{ height: 40, borderRadius: 1 }}
185-
/>
221+
</Box>
186222
)}
187223
</Box>
188224
</Section>
@@ -198,21 +234,23 @@ export const SingleSignOnSection = ({
198234
);
199235
};
200236

201-
const OIDCIcon = ({ authMethods }: { authMethods: AuthMethods }) => {
202-
return authMethods.oidc.iconUrl ? (
237+
const OIDCIcon = ({ oidcAuth }: { oidcAuth: OIDCAuthMethod }) => {
238+
if (!oidcAuth.iconUrl) {
239+
return <KeyIcon sx={{ width: 16, height: 16 }} />;
240+
}
241+
242+
return (
203243
<Box
204244
component="img"
205245
alt="Open ID Connect icon"
206-
src={authMethods.oidc.iconUrl}
246+
src={oidcAuth.iconUrl}
207247
sx={{ width: 16, height: 16 }}
208248
/>
209-
) : (
210-
<KeyIcon sx={{ width: 16, height: 16 }} />
211249
);
212250
};
213251

214-
const getOIDCLabel = (authMethods: AuthMethods) => {
215-
return authMethods.oidc.signInText || "OpenID Connect";
252+
const getOIDCLabel = (oidcAuth: OIDCAuthMethod) => {
253+
return oidcAuth.signInText || "OpenID Connect";
216254
};
217255

218256
const ConfirmLoginTypeChangeModal = ({

0 commit comments

Comments
 (0)