Skip to content

Commit a353043

Browse files
committed
End
1 parent 75d7865 commit a353043

File tree

4 files changed

+322
-266
lines changed

4 files changed

+322
-266
lines changed

site/src/components/SettingsSecurityForm/SettingsSecurityForm.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getFormHelpers } from "../../utils/formUtils"
66
import { LoadingButton } from "../LoadingButton/LoadingButton"
77
import { ErrorAlert } from "components/Alert/ErrorAlert"
88
import { Form, FormFields } from "components/Form/Form"
9+
import { Alert } from "components/Alert/Alert"
910

1011
interface SecurityFormValues {
1112
old_password: string
@@ -41,6 +42,7 @@ const validationSchema = Yup.object({
4142
})
4243

4344
export interface SecurityFormProps {
45+
disabled?: boolean
4446
isLoading: boolean
4547
initialValues: SecurityFormValues
4648
onSubmit: (values: SecurityFormValues) => void
@@ -50,6 +52,7 @@ export interface SecurityFormProps {
5052
}
5153

5254
export const SecurityForm: FC<SecurityFormProps> = ({
55+
disabled,
5356
isLoading,
5457
onSubmit,
5558
initialValues,
@@ -68,6 +71,14 @@ export const SecurityForm: FC<SecurityFormProps> = ({
6871
updateSecurityError,
6972
)
7073

74+
if (disabled) {
75+
return (
76+
<Alert severity="info">
77+
Password changes are only allowed for password based accounts.
78+
</Alert>
79+
)
80+
}
81+
7182
return (
7283
<>
7384
<Form onSubmit={form.handleSubmit}>

site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx

+14-249
Original file line numberDiff line numberDiff line change
@@ -1,270 +1,35 @@
1-
import { ComponentProps, FC, useState } from "react"
1+
import { FC } from "react"
22
import { Section } from "../../../components/SettingsLayout/Section"
33
import { AccountForm } from "../../../components/SettingsAccountForm/SettingsAccountForm"
44
import { useAuth } from "components/AuthProvider/AuthProvider"
55
import { useMe } from "hooks/useMe"
66
import { usePermissions } from "hooks/usePermissions"
7-
import TextField from "@mui/material/TextField"
8-
import Box from "@mui/material/Box"
9-
import GitHubIcon from "@mui/icons-material/GitHub"
10-
import KeyIcon from "@mui/icons-material/VpnKey"
11-
import Button from "@mui/material/Button"
12-
import { useLocation } from "react-router-dom"
13-
import { retrieveRedirect } from "utils/redirect"
14-
import Typography from "@mui/material/Typography"
15-
import { convertToOAUTH, getAuthMethods } from "api/api"
16-
import { AuthMethods, LoginType } from "api/typesGenerated"
17-
import Skeleton from "@mui/material/Skeleton"
18-
import { Stack } from "components/Stack/Stack"
19-
import { useMutation, useQuery } from "@tanstack/react-query"
20-
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"
21-
import { getErrorMessage } from "api/errors"
22-
23-
type LoginTypeConfirmation =
24-
| {
25-
open: false
26-
selectedType: undefined
27-
}
28-
| {
29-
open: true
30-
selectedType: LoginType
31-
}
327

338
export const AccountPage: FC = () => {
349
const [authState, authSend] = useAuth()
3510
const me = useMe()
3611
const permissions = usePermissions()
3712
const { updateProfileError } = authState.context
3813
const canEditUsers = permissions && permissions.updateUsers
39-
const location = useLocation()
40-
const redirectTo = retrieveRedirect(location.search)
41-
const [loginTypeConfirmation, setLoginTypeConfirmation] =
42-
useState<LoginTypeConfirmation>({ open: false, selectedType: undefined })
43-
const { data: authMethods } = useQuery({
44-
queryKey: ["authMethods"],
45-
queryFn: getAuthMethods,
46-
})
47-
const loginTypeMutation = useMutation(convertToOAUTH, {
48-
onSuccess: (data) => {
49-
window.location.href = `/api/v2/users/oidc/callback?oidc_merge_state=${
50-
data.state_string
51-
}&redirect=${encodeURIComponent(redirectTo)}`
52-
},
53-
})
5414

5515
return (
56-
<Stack spacing={8}>
57-
<Section title="Account" description="Update your account info">
58-
<AccountForm
59-
editable={Boolean(canEditUsers)}
60-
email={me.email}
61-
updateProfileError={updateProfileError}
62-
isLoading={authState.matches("signedIn.profile.updatingProfile")}
63-
initialValues={{
64-
username: me.username,
65-
}}
66-
onSubmit={(data) => {
67-
authSend({
68-
type: "UPDATE_PROFILE",
69-
data,
70-
})
71-
}}
72-
/>
73-
</Section>
74-
75-
<Section
76-
title="Single Sign On"
77-
description="Authenticate in Coder using one-click"
78-
>
79-
<Box display="grid" gap="16px">
80-
{authMethods ? (
81-
authMethods.me_login_type === "password" ? (
82-
<>
83-
{authMethods.github.enabled && (
84-
<GitHubButton
85-
disabled={loginTypeMutation.isLoading}
86-
onClick={() =>
87-
setLoginTypeConfirmation({
88-
open: true,
89-
selectedType: "github",
90-
})
91-
}
92-
>
93-
GitHub
94-
</GitHubButton>
95-
)}
96-
{authMethods.oidc.enabled && (
97-
<OIDCButton
98-
authMethods={authMethods}
99-
disabled={loginTypeMutation.isLoading}
100-
onClick={() =>
101-
setLoginTypeConfirmation({
102-
open: true,
103-
selectedType: "oidc",
104-
})
105-
}
106-
>
107-
{getOIDCLabel(authMethods)}
108-
</OIDCButton>
109-
)}
110-
</>
111-
) : (
112-
<>
113-
{authMethods.me_login_type === "github" && (
114-
<GitHubButton disabled>
115-
Authenticated with GitHub
116-
</GitHubButton>
117-
)}
118-
119-
{authMethods.me_login_type === "oidc" && (
120-
<OIDCButton authMethods={authMethods} disabled>
121-
Authenticated with {getOIDCLabel(authMethods)}
122-
</OIDCButton>
123-
)}
124-
</>
125-
)
126-
) : (
127-
<>
128-
<Skeleton
129-
variant="rectangular"
130-
sx={{ height: 40, borderRadius: 1 }}
131-
/>
132-
<Skeleton
133-
variant="rectangular"
134-
sx={{ height: 40, borderRadius: 1 }}
135-
/>
136-
</>
137-
)}
138-
</Box>
139-
</Section>
140-
141-
<ConfirmLoginTypeChangeModal
142-
open={loginTypeConfirmation.open}
143-
error={loginTypeMutation.error}
144-
// We still want to show it loading when it is success so the modal is
145-
// not going to close or change until the oauth redirect
146-
loading={loginTypeMutation.isLoading || loginTypeMutation.isSuccess}
147-
onClose={() => {
148-
setLoginTypeConfirmation({ open: false, selectedType: undefined })
149-
loginTypeMutation.reset()
16+
<Section title="Account" description="Update your account info">
17+
<AccountForm
18+
editable={Boolean(canEditUsers)}
19+
email={me.email}
20+
updateProfileError={updateProfileError}
21+
isLoading={authState.matches("signedIn.profile.updatingProfile")}
22+
initialValues={{
23+
username: me.username,
15024
}}
151-
onConfirm={(password) => {
152-
if (!loginTypeConfirmation.selectedType) {
153-
throw new Error("No login type selected")
154-
}
155-
loginTypeMutation.mutate({
156-
to_login_type: loginTypeConfirmation.selectedType,
157-
email: me.email,
158-
password,
25+
onSubmit={(data) => {
26+
authSend({
27+
type: "UPDATE_PROFILE",
28+
data,
15929
})
16030
}}
16131
/>
162-
</Stack>
163-
)
164-
}
165-
166-
const GitHubButton = (props: ComponentProps<typeof Button>) => {
167-
return (
168-
<Button
169-
startIcon={<GitHubIcon sx={{ width: 16, height: 16 }} />}
170-
fullWidth
171-
type="submit"
172-
size="large"
173-
{...props}
174-
/>
175-
)
176-
}
177-
178-
const OIDCButton = ({
179-
authMethods,
180-
...buttonProps
181-
}: ComponentProps<typeof Button> & { authMethods: AuthMethods }) => {
182-
return (
183-
<Button
184-
size="large"
185-
startIcon={
186-
authMethods.oidc.iconUrl ? (
187-
<Box
188-
component="img"
189-
alt="Open ID Connect icon"
190-
src={authMethods.oidc.iconUrl}
191-
sx={{ width: 16, height: 16 }}
192-
/>
193-
) : (
194-
<KeyIcon sx={{ width: 16, height: 16 }} />
195-
)
196-
}
197-
fullWidth
198-
type="submit"
199-
{...buttonProps}
200-
/>
201-
)
202-
}
203-
204-
const getOIDCLabel = (authMethods: AuthMethods) => {
205-
return authMethods.oidc.signInText || "OpenID Connect"
206-
}
207-
208-
const ConfirmLoginTypeChangeModal = ({
209-
open,
210-
loading,
211-
error,
212-
onClose,
213-
onConfirm,
214-
}: {
215-
open: boolean
216-
loading: boolean
217-
error: unknown
218-
onClose: () => void
219-
onConfirm: (password: string) => void
220-
}) => {
221-
const [password, setPassword] = useState("")
222-
223-
const handleConfirm = () => {
224-
onConfirm(password)
225-
}
226-
227-
return (
228-
<ConfirmDialog
229-
open={open}
230-
onClose={() => {
231-
onClose()
232-
}}
233-
onConfirm={handleConfirm}
234-
hideCancel={false}
235-
cancelText="Cancel"
236-
confirmText="Update"
237-
title="Change login type"
238-
confirmLoading={loading}
239-
description={
240-
<Stack>
241-
<Typography>
242-
After changing your login type, you will not be able to change it
243-
again. Are you sure you want to proceed and change your login type?
244-
</Typography>
245-
<TextField
246-
autoFocus
247-
onKeyDown={(event) => {
248-
if (event.key === "Enter") {
249-
handleConfirm()
250-
}
251-
}}
252-
error={Boolean(error)}
253-
helperText={
254-
error
255-
? getErrorMessage(error, "Your password is incorrect")
256-
: undefined
257-
}
258-
name="confirm-password"
259-
id="confirm-password"
260-
value={password}
261-
onChange={(e) => setPassword(e.currentTarget.value)}
262-
label="Confirm your password"
263-
type="password"
264-
/>
265-
</Stack>
266-
}
267-
/>
32+
</Section>
26833
)
26934
}
27035

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

+38-17
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import { FC } from "react"
44
import { userSecuritySettingsMachine } from "xServices/userSecuritySettings/userSecuritySettingsXService"
55
import { Section } from "../../../components/SettingsLayout/Section"
66
import { SecurityForm } from "../../../components/SettingsSecurityForm/SettingsSecurityForm"
7-
8-
export const Language = {
9-
title: "Security",
10-
}
7+
import { useQuery } from "@tanstack/react-query"
8+
import { getAuthMethods } from "api/api"
9+
import {
10+
SingleSignOnSection,
11+
useSingleSignOnSection,
12+
} from "./SingleSignOnSection"
13+
import { Loader } from "components/Loader/Loader"
14+
import { Stack } from "components/Stack/Stack"
1115

1216
export const SecurityPage: FC = () => {
1317
const me = useMe()
@@ -20,21 +24,38 @@ export const SecurityPage: FC = () => {
2024
},
2125
)
2226
const { error } = securityState.context
27+
const { data: authMethods } = useQuery({
28+
queryKey: ["authMethods"],
29+
queryFn: getAuthMethods,
30+
})
31+
const singleSignOnSection = useSingleSignOnSection()
32+
33+
if (!authMethods) {
34+
return <Loader />
35+
}
2336

2437
return (
25-
<Section title={Language.title} description="Update your account password">
26-
<SecurityForm
27-
updateSecurityError={error}
28-
isLoading={securityState.matches("updatingSecurity")}
29-
initialValues={{ old_password: "", password: "", confirm_password: "" }}
30-
onSubmit={(data) => {
31-
securitySend({
32-
type: "UPDATE_SECURITY",
33-
data,
34-
})
35-
}}
36-
/>
37-
</Section>
38+
<Stack spacing={6}>
39+
<Section title="Security" description="Update your account password">
40+
<SecurityForm
41+
disabled={authMethods.me_login_type !== "password"}
42+
updateSecurityError={error}
43+
isLoading={securityState.matches("updatingSecurity")}
44+
initialValues={{
45+
old_password: "",
46+
password: "",
47+
confirm_password: "",
48+
}}
49+
onSubmit={(data) => {
50+
securitySend({
51+
type: "UPDATE_SECURITY",
52+
data,
53+
})
54+
}}
55+
/>
56+
</Section>
57+
<SingleSignOnSection authMethods={authMethods} {...singleSignOnSection} />
58+
</Stack>
3859
)
3960
}
4061

0 commit comments

Comments
 (0)