Skip to content

fix: prevent email from being altered #1863

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
9 changes: 1 addition & 8 deletions coderd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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(),
})
Expand Down
40 changes: 2 additions & 38 deletions coderd/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,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)
Expand All @@ -221,25 +220,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)
Expand All @@ -253,38 +233,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.Run("UpdateUsername", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
_, _ = client.User(context.Background(), codersdk.Me)
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)
userProfile, err := client.UpdateUserProfile(context.Background(), codersdk.Me, codersdk.UpdateUserProfileRequest{
Username: me.Username,
Email: "newemail@coder.com",
})
require.NoError(t, err)
require.Equal(t, userProfile.Username, me.Username)
require.Equal(t, userProfile.Email, "newemail@coder.com")
})
}

Expand Down
1 change: 0 additions & 1 deletion codersdk/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ type CreateUserRequest struct {
}

type UpdateUserProfileRequest struct {
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required,username"`
}

Expand Down
25 changes: 12 additions & 13 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -276,20 +276,19 @@ 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
}

// From codersdk/users.go:62:6
export interface UpdateUserProfileRequest {
readonly email: string
readonly username: string
}

Expand Down Expand Up @@ -320,29 +319,29 @@ 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
readonly organization_id?: string
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[]>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@ interface AccountFormValues {
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),
})

Expand Down Expand Up @@ -59,6 +56,7 @@ export const AccountForm: React.FC<AccountFormProps> = ({
fullWidth
label={Language.emailLabel}
variant="outlined"
disabled={true}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
disabled={true}
disabled

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should remove the getFieldHelpers("email") and onChange and autoComplete here.

At a minimum, can you remove the autoComplete (doesn't make much sense for a disabled field).

/>
<TextField
{...getFieldHelpers("username")}
Expand Down
22 changes: 1 addition & 21 deletions site/src/pages/SettingsPages/AccountPage/AccountPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } })
fireEvent.change(screen.getByLabelText("Username"), { target: { value: newData.username } })
fireEvent.click(screen.getByText(AccountForm.Language.updateSettings))
}
Expand All @@ -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"],
Expand All @@ -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({
Expand Down