fix: prevent email from being altered (#1863)

This commit is contained in:
Garrett Delfosse 2022-05-27 17:25:04 -05:00 committed by GitHub
parent cfa316be89
commit 5598ac05dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 24 additions and 96 deletions

View File

@ -254,19 +254,12 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
return
}
existentUser, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
Email: params.Email,
Username: params.Username,
})
isDifferentUser := existentUser.ID != user.ID
if err == nil && isDifferentUser {
responseErrors := []httpapi.Error{}
if existentUser.Email == params.Email {
responseErrors = append(responseErrors, httpapi.Error{
Field: "email",
Detail: "this value is already in use and should be unique",
})
}
if existentUser.Username == params.Username {
responseErrors = append(responseErrors, httpapi.Error{
Field: "username",
@ -288,7 +281,7 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
updatedUserProfile, err := api.Database.UpdateUserProfile(r.Context(), database.UpdateUserProfileParams{
ID: user.ID,
Email: params.Email,
Email: user.Email,
Username: params.Username,
UpdatedAt: database.Now(),
})

View File

@ -259,7 +259,6 @@ func TestUpdateUserProfile(t *testing.T) {
coderdtest.CreateFirstUser(t, client)
_, err := client.UpdateUserProfile(context.Background(), uuid.New().String(), codersdk.UpdateUserProfileRequest{
Username: "newusername",
Email: "newemail@coder.com",
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
@ -268,25 +267,6 @@ func TestUpdateUserProfile(t *testing.T) {
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
t.Run("ConflictingEmail", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
existentUser, _ := client.CreateUser(context.Background(), codersdk.CreateUserRequest{
Email: "bruno@coder.com",
Username: "bruno",
Password: "password",
OrganizationID: user.OrganizationID,
})
_, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: "newusername",
Email: existentUser.Email,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
})
t.Run("ConflictingUsername", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
@ -300,38 +280,22 @@ func TestUpdateUserProfile(t *testing.T) {
require.NoError(t, err)
_, err = client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: existentUser.Username,
Email: "newemail@coder.com",
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
})
t.Run("UpdateUsernameAndEmail", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
userProfile, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: "newusername",
Email: "newemail@coder.com",
})
require.NoError(t, err)
require.Equal(t, userProfile.Username, "newusername")
require.Equal(t, userProfile.Email, "newemail@coder.com")
})
t.Run("UpdateUsername", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
me, _ := client.User(context.Background(), codersdk.Me)
_, _ = client.User(context.Background(), codersdk.Me)
userProfile, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: me.Username,
Email: "newemail@coder.com",
Username: "newusername",
})
require.NoError(t, err)
require.Equal(t, userProfile.Username, me.Username)
require.Equal(t, userProfile.Email, "newemail@coder.com")
require.Equal(t, userProfile.Username, "newusername")
})
}

View File

@ -60,7 +60,6 @@ type CreateUserRequest struct {
}
type UpdateUserProfileRequest struct {
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required,username"`
}

View File

@ -12,7 +12,7 @@ export interface AgentGitSSHKey {
readonly private_key: string
}
// From codersdk/users.go:152:6
// From codersdk/users.go:151:6
export interface AuthMethods {
readonly password: boolean
readonly github: boolean
@ -44,7 +44,7 @@ export interface CreateFirstUserResponse {
readonly organization_id: string
}
// From codersdk/users.go:147:6
// From codersdk/users.go:146:6
export interface CreateOrganizationRequest {
readonly name: string
}
@ -100,7 +100,7 @@ export interface CreateWorkspaceRequest {
readonly parameter_values?: CreateParameterRequest[]
}
// From codersdk/users.go:143:6
// From codersdk/users.go:142:6
export interface GenerateAPIKeyResponse {
readonly key: string
}
@ -118,13 +118,13 @@ export interface GoogleInstanceIdentityToken {
readonly json_web_token: string
}
// From codersdk/users.go:132:6
// From codersdk/users.go:131:6
export interface LoginWithPasswordRequest {
readonly email: string
readonly password: string
}
// From codersdk/users.go:138:6
// From codersdk/users.go:137:6
export interface LoginWithPasswordResponse {
readonly session_token: string
}
@ -276,12 +276,12 @@ export interface UpdateActiveTemplateVersion {
readonly id: string
}
// From codersdk/users.go:72:6
// From codersdk/users.go:71:6
export interface UpdateRoles {
readonly roles: string[]
}
// From codersdk/users.go:67:6
// From codersdk/users.go:66:6
export interface UpdateUserPasswordRequest {
readonly old_password: string
readonly password: string
@ -289,7 +289,6 @@ export interface UpdateUserPasswordRequest {
// From codersdk/users.go:62:6
export interface UpdateUserProfileRequest {
readonly email: string
readonly username: string
}
@ -320,13 +319,13 @@ export interface User {
readonly roles: Role[]
}
// From codersdk/users.go:97:6
// From codersdk/users.go:96:6
export interface UserAuthorization {
readonly object: UserAuthorizationObject
readonly action: string
}
// From codersdk/users.go:113:6
// From codersdk/users.go:112:6
export interface UserAuthorizationObject {
readonly resource_type: string
readonly owner_id?: string
@ -334,15 +333,15 @@ export interface UserAuthorizationObject {
readonly resource_id?: string
}
// From codersdk/users.go:86:6
// From codersdk/users.go:85:6
export interface UserAuthorizationRequest {
readonly checks: Record<string, UserAuthorization>
}
// From codersdk/users.go:81:6
// From codersdk/users.go:80:6
export type UserAuthorizationResponse = Record<string, boolean>
// From codersdk/users.go:76:6
// From codersdk/users.go:75:6
export interface UserRoles {
readonly roles: string[]
readonly organization_roles: Record<string, string[]>

View File

@ -8,25 +8,23 @@ import { LoadingButton } from "../LoadingButton/LoadingButton"
import { Stack } from "../Stack/Stack"
interface AccountFormValues {
email: string
username: string
}
export const Language = {
usernameLabel: "Username",
emailLabel: "Email",
emailInvalid: "Please enter a valid email address.",
emailRequired: "Please enter an email address.",
updateSettings: "Update settings",
}
const validationSchema = Yup.object({
email: Yup.string().trim().email(Language.emailInvalid).required(Language.emailRequired),
username: nameValidator(Language.usernameLabel),
})
export type AccountFormErrors = FormikErrors<AccountFormValues>
export interface AccountFormProps {
email: string
isLoading: boolean
initialValues: AccountFormValues
onSubmit: (values: AccountFormValues) => void
@ -35,6 +33,7 @@ export interface AccountFormProps {
}
export const AccountForm: React.FC<AccountFormProps> = ({
email,
isLoading,
onSubmit,
initialValues,
@ -52,14 +51,7 @@ export const AccountForm: React.FC<AccountFormProps> = ({
<>
<form onSubmit={form.handleSubmit}>
<Stack>
<TextField
{...getFieldHelpers("email")}
onChange={onChangeTrimmed(form)}
autoComplete="email"
fullWidth
label={Language.emailLabel}
variant="outlined"
/>
<TextField disabled fullWidth label={Language.emailLabel} value={email} variant="outlined" />
<TextField
{...getFieldHelpers("username")}
onChange={onChangeTrimmed(form)}

View File

@ -17,13 +17,11 @@ const renderPage = () => {
}
const newData = {
email: "user@coder.com",
username: "user",
}
const fillAndSubmitForm = async () => {
await waitFor(() => screen.findByLabelText("Email"))
fireEvent.change(screen.getByLabelText("Email"), { target: { value: newData.email } })
await waitFor(() => screen.findByLabelText("Username"))
fireEvent.change(screen.getByLabelText("Username"), { target: { value: newData.username } })
fireEvent.click(screen.getByText(AccountForm.Language.updateSettings))
}
@ -34,6 +32,7 @@ describe("AccountPage", () => {
jest.spyOn(API, "updateProfile").mockImplementationOnce((userId, data) =>
Promise.resolve({
id: userId,
email: "user@coder.com",
created_at: new Date().toString(),
status: "active",
organization_ids: ["123"],
@ -51,25 +50,6 @@ describe("AccountPage", () => {
})
})
describe("when the email is already taken", () => {
it("shows an error", async () => {
jest.spyOn(API, "updateProfile").mockRejectedValueOnce({
isAxiosError: true,
response: {
data: { message: "Invalid profile", errors: [{ detail: "Email is already in use", field: "email" }] },
},
})
const { user } = renderPage()
await fillAndSubmitForm()
const errorMessage = await screen.findByText("Email is already in use")
expect(errorMessage).toBeDefined()
expect(API.updateProfile).toBeCalledTimes(1)
expect(API.updateProfile).toBeCalledWith(user.id, newData)
})
})
describe("when the username is already taken", () => {
it("shows an error", async () => {
jest.spyOn(API, "updateProfile").mockRejectedValueOnce({

View File

@ -26,10 +26,11 @@ export const AccountPage: React.FC = () => {
return (
<Section title={Language.title}>
<AccountForm
email={me.email}
error={hasUnknownError ? Language.unknownError : undefined}
formErrors={formErrors}
isLoading={authState.matches("signedIn.profile.updatingProfile")}
initialValues={{ username: me.username, email: me.email }}
initialValues={{ username: me.username }}
onSubmit={(data) => {
authSend({
type: "UPDATE_PROFILE",