From 7503f0d6282d498186cafc6c38deae7230fa9587 Mon Sep 17 00:00:00 2001 From: Hrishav Date: Tue, 9 Jul 2024 10:14:03 +0530 Subject: [PATCH] feat: Updated login and update password APIs (#4751) * fix: Added error response in createUser API Signed-off-by: Hrishav * fix: added project ID to update password mutation Signed-off-by: Hrishav * fix: fixed failing UTs Signed-off-by: Hrishav --------- Signed-off-by: Hrishav --- .../api/handlers/rest/user_handlers.go | 59 ++++++++++++++++++- .../api/handlers/rest/user_handlers_test.go | 14 ++++- chaoscenter/authentication/api/main.go | 9 +-- .../api/auth/hooks/useCreateUserMutation.ts | 3 +- chaoscenter/web/src/api/auth/index.ts | 1 + .../api/auth/schemas/ResponseErrBadRequest.ts | 14 +++++ .../auth/schemas/ResponseMessageResponse.ts | 3 +- .../PasswordInput/PasswordInput.tsx | 6 +- .../CreateNewUser/CreateNewUser.tsx | 6 +- .../PasswordReset/PasswordReset.tsx | 7 ++- .../CreateNewUser/CreateNewUser.module.scss | 3 + .../CreateNewUser.module.scss.d.ts | 9 +++ .../src/views/CreateNewUser/CreateNewUser.tsx | 34 +++++------ mkdocs/docs/auth/v3.9.0/auth-api.json | 26 ++++++++ 14 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 chaoscenter/web/src/api/auth/schemas/ResponseErrBadRequest.ts create mode 100644 chaoscenter/web/src/views/CreateNewUser/CreateNewUser.module.scss create mode 100644 chaoscenter/web/src/views/CreateNewUser/CreateNewUser.module.scss.d.ts diff --git a/chaoscenter/authentication/api/handlers/rest/user_handlers.go b/chaoscenter/authentication/api/handlers/rest/user_handlers.go index a839c46f062..7464d96a558 100644 --- a/chaoscenter/authentication/api/handlers/rest/user_handlers.go +++ b/chaoscenter/authentication/api/handlers/rest/user_handlers.go @@ -430,6 +430,15 @@ func UpdatePassword(service services.ApplicationService) gin.HandlerFunc { return } username := c.MustGet("username").(string) + + // Fetching userDetails + user, err := service.FindUserByUsername(username) + if err != nil { + log.Error(err) + c.JSON(utils.ErrorStatusCodes[utils.ErrUserNotFound], presenter.CreateErrorResponse(utils.ErrInvalidCredentials)) + return + } + userPasswordRequest.Username = username if userPasswordRequest.NewPassword != "" { err := utils.ValidateStrictPassword(userPasswordRequest.NewPassword) @@ -454,8 +463,56 @@ func UpdatePassword(service services.ApplicationService) gin.HandlerFunc { } return } + + var defaultProject string + ownerProjects, err := service.GetOwnerProjectIDs(c, user.ID) + + if len(ownerProjects) > 0 { + defaultProject = ownerProjects[0].ID + } else { + // Adding user as project owner in project's member list + newMember := &entities.Member{ + UserID: user.ID, + Role: entities.RoleOwner, + Invitation: entities.AcceptedInvitation, + Username: user.Username, + Name: user.Name, + Email: user.Email, + JoinedAt: time.Now().UnixMilli(), + } + var members []*entities.Member + members = append(members, newMember) + state := "active" + newProject := &entities.Project{ + ID: uuid.Must(uuid.NewRandom()).String(), + Name: user.Username + "-project", + Members: members, + State: &state, + Audit: entities.Audit{ + IsRemoved: false, + CreatedAt: time.Now().UnixMilli(), + CreatedBy: entities.UserDetailResponse{ + Username: user.Username, + UserID: user.ID, + Email: user.Email, + }, + UpdatedAt: time.Now().UnixMilli(), + UpdatedBy: entities.UserDetailResponse{ + Username: user.Username, + UserID: user.ID, + Email: user.Email, + }, + }, + } + err := service.CreateProject(newProject) + if err != nil { + return + } + defaultProject = newProject.ID + } c.JSON(http.StatusOK, gin.H{ - "message": "password has been updated successfully", + "message": "password has been updated successfully", + "projectID": defaultProject, }) } } diff --git a/chaoscenter/authentication/api/handlers/rest/user_handlers_test.go b/chaoscenter/authentication/api/handlers/rest/user_handlers_test.go index 0f1d5696d1a..a5447514c43 100644 --- a/chaoscenter/authentication/api/handlers/rest/user_handlers_test.go +++ b/chaoscenter/authentication/api/handlers/rest/user_handlers_test.go @@ -490,7 +490,7 @@ func TestUpdatePassword(t *testing.T) { givenStrictPassword: false, givenServiceResponse: nil, expectedCode: http.StatusOK, - expectedOutput: `{"message":"password has been updated successfully"}`, + expectedOutput: `{"message":"password has been updated successfully","projectID":"someProjectID"}`, }, { name: "Invalid new password", @@ -524,9 +524,19 @@ func TestUpdatePassword(t *testing.T) { Email: "test@example.com", IsInitialLogin: false, } + userFromDB := &entities.User{ + ID: "testUserID", + Username: "testUser", + Password: "hashedPassword", + Email: "test@example.com", + } + service.On("FindUserByUsername", "testUser").Return(userFromDB, nil) service.On("GetUser", "testUID").Return(user, nil) service.On("UpdatePassword", &userPassword, true).Return(tt.givenServiceResponse) - + project := &entities.Project{ + ID: "someProjectID", + } + service.On("GetOwnerProjectIDs", mock.Anything, "testUserID").Return([]*entities.Project{project}, nil) rest.UpdatePassword(service)(c) assert.Equal(t, tt.expectedCode, w.Code) diff --git a/chaoscenter/authentication/api/main.go b/chaoscenter/authentication/api/main.go index 4736a48af97..2fa046184e0 100644 --- a/chaoscenter/authentication/api/main.go +++ b/chaoscenter/authentication/api/main.go @@ -157,10 +157,11 @@ func validatedAdminSetup(service services.ApplicationService) { password := string(hashedPassword) adminUser := entities.User{ - ID: uID, - Username: utils.AdminName, - Password: password, - Role: entities.RoleAdmin, + ID: uID, + Username: utils.AdminName, + Password: password, + Role: entities.RoleAdmin, + IsInitialLogin: true, Audit: entities.Audit{ CreatedAt: time.Now().UnixMilli(), UpdatedAt: time.Now().UnixMilli(), diff --git a/chaoscenter/web/src/api/auth/hooks/useCreateUserMutation.ts b/chaoscenter/web/src/api/auth/hooks/useCreateUserMutation.ts index 697af9e2426..89fc27cd5c1 100644 --- a/chaoscenter/web/src/api/auth/hooks/useCreateUserMutation.ts +++ b/chaoscenter/web/src/api/auth/hooks/useCreateUserMutation.ts @@ -4,6 +4,7 @@ import { useMutation, UseMutationOptions } from '@tanstack/react-query'; import type { User } from '../schemas/User'; +import type { ResponseErrBadRequest } from '../schemas/ResponseErrBadRequest'; import { fetcher, FetcherOptions } from 'services/fetcher'; export type CreateUserRequestBody = { @@ -17,7 +18,7 @@ export type CreateUserRequestBody = { export type CreateUserOkResponse = User; -export type CreateUserErrorResponse = unknown; +export type CreateUserErrorResponse = ResponseErrBadRequest; export interface CreateUserProps extends Omit, 'url'> { body: CreateUserRequestBody; diff --git a/chaoscenter/web/src/api/auth/index.ts b/chaoscenter/web/src/api/auth/index.ts index aeaf856e414..0e25ae9cf21 100644 --- a/chaoscenter/web/src/api/auth/index.ts +++ b/chaoscenter/web/src/api/auth/index.ts @@ -223,6 +223,7 @@ export type { LogoutResponse } from './schemas/LogoutResponse'; export type { Project } from './schemas/Project'; export type { ProjectMember } from './schemas/ProjectMember'; export type { RemoveApiTokenResponse } from './schemas/RemoveApiTokenResponse'; +export type { ResponseErrBadRequest } from './schemas/ResponseErrBadRequest'; export type { ResponseErrInvalidCredentials } from './schemas/ResponseErrInvalidCredentials'; export type { ResponseErrOldPassword } from './schemas/ResponseErrOldPassword'; export type { ResponseMessageResponse } from './schemas/ResponseMessageResponse'; diff --git a/chaoscenter/web/src/api/auth/schemas/ResponseErrBadRequest.ts b/chaoscenter/web/src/api/auth/schemas/ResponseErrBadRequest.ts new file mode 100644 index 00000000000..aa2337cdc22 --- /dev/null +++ b/chaoscenter/web/src/api/auth/schemas/ResponseErrBadRequest.ts @@ -0,0 +1,14 @@ +/* eslint-disable */ +// This code is autogenerated using @harnessio/oats-cli. +// Please do not modify this code directly. + +export interface ResponseErrBadRequest { + /** + * @example "The old and new passwords can't be same" + */ + error?: string; + /** + * @example "The old and new passwords can't be same" + */ + errorDescription?: string; +} diff --git a/chaoscenter/web/src/api/auth/schemas/ResponseMessageResponse.ts b/chaoscenter/web/src/api/auth/schemas/ResponseMessageResponse.ts index c5d0ba97b2c..727e6d2e999 100644 --- a/chaoscenter/web/src/api/auth/schemas/ResponseMessageResponse.ts +++ b/chaoscenter/web/src/api/auth/schemas/ResponseMessageResponse.ts @@ -3,5 +3,6 @@ // Please do not modify this code directly. export interface ResponseMessageResponse { - message?: string; + message: string; + projectID: string; } diff --git a/chaoscenter/web/src/components/PasswordInput/PasswordInput.tsx b/chaoscenter/web/src/components/PasswordInput/PasswordInput.tsx index 8d26514b59d..2dcf5cac3ee 100644 --- a/chaoscenter/web/src/components/PasswordInput/PasswordInput.tsx +++ b/chaoscenter/web/src/components/PasswordInput/PasswordInput.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Icon, IconName } from '@harnessio/icons'; import { FormInput, Layout, Text } from '@harnessio/uicore'; import { Color, FontVariation } from '@harnessio/design-system'; +import cx from 'classnames'; import style from './PasswordInput.module.scss'; interface PasswordInputProps { @@ -9,10 +10,11 @@ interface PasswordInputProps { placeholder?: string; name: string; label: string | React.ReactElement; + className?: string; } const PasswordInput = (props: PasswordInputProps): React.ReactElement => { - const { disabled, label, name, placeholder } = props; + const { disabled, label, name, placeholder, className } = props; const [showPassword, setShowPassword] = React.useState(false); const stateIcon: IconName = showPassword ? 'eye-off' : 'eye-open'; @@ -21,7 +23,7 @@ const PasswordInput = (props: PasswordInputProps): React.ReactElement => { } return ( - + {label && typeof label === 'string' ? ( {label} diff --git a/chaoscenter/web/src/controllers/CreateNewUser/CreateNewUser.tsx b/chaoscenter/web/src/controllers/CreateNewUser/CreateNewUser.tsx index cd409492969..e1eeeb6fbdb 100644 --- a/chaoscenter/web/src/controllers/CreateNewUser/CreateNewUser.tsx +++ b/chaoscenter/web/src/controllers/CreateNewUser/CreateNewUser.tsx @@ -14,7 +14,7 @@ interface CreateNewUserControllerProps { export default function CreateNewUserController(props: CreateNewUserControllerProps): React.ReactElement { const { getUsersRefetch, handleClose } = props; - const { showSuccess } = useToaster(); + const { showSuccess, showError } = useToaster(); const { getString } = useStrings(); const { mutate: createNewUserMutation, isLoading } = useCreateUserMutation( @@ -22,7 +22,11 @@ export default function CreateNewUserController(props: CreateNewUserControllerPr { onSuccess: data => { getUsersRefetch(); + handleClose(); showSuccess(getString('userCreateSuccessMessage', { name: data.name })); + }, + onError: e => { + showError(e.errorDescription); } } ); diff --git a/chaoscenter/web/src/controllers/PasswordReset/PasswordReset.tsx b/chaoscenter/web/src/controllers/PasswordReset/PasswordReset.tsx index db808765d07..4cdf639dd95 100644 --- a/chaoscenter/web/src/controllers/PasswordReset/PasswordReset.tsx +++ b/chaoscenter/web/src/controllers/PasswordReset/PasswordReset.tsx @@ -5,11 +5,13 @@ import PasswordResetView from '@views/PasswordReset'; import { useGetUserQuery, useUpdatePasswordMutation } from '@api/auth'; import { getUserDetails, setUserDetails } from '@utils'; import { normalizePath } from '@routes/RouteDefinitions'; +import { useAppStore } from '@context'; const PasswordResetController = (): React.ReactElement => { - const { accountID, projectID } = getUserDetails(); + const { accountID } = getUserDetails(); const { showSuccess, showError } = useToaster(); const history = useHistory(); + const { updateAppStore } = useAppStore(); const { data: currentUserData, isLoading: getUserLoading } = useGetUserQuery( { @@ -26,7 +28,8 @@ const PasswordResetController = (): React.ReactElement => { onSuccess: data => { setUserDetails({ isInitialLogin: false }); showSuccess(`${data.message}`); - history.push(normalizePath(`/account/${accountID}/project/${projectID}/dashboard`)); + updateAppStore({ projectID: data.projectID }); + history.push(normalizePath(`/account/${accountID}/project/${data.projectID}/dashboard`)); }, onError: err => showError(err.errorDescription) } diff --git a/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.module.scss b/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.module.scss new file mode 100644 index 00000000000..2845b5e3d66 --- /dev/null +++ b/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.module.scss @@ -0,0 +1,3 @@ +.passwordField { + margin-bottom: 15px !important; +} diff --git a/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.module.scss.d.ts b/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.module.scss.d.ts new file mode 100644 index 00000000000..dfa1984cecb --- /dev/null +++ b/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.module.scss.d.ts @@ -0,0 +1,9 @@ +declare namespace CreateNewUserModuleScssNamespace { + export interface ICreateNewUserModuleScss { + passwordField: string; + } +} + +declare const CreateNewUserModuleScssModule: CreateNewUserModuleScssNamespace.ICreateNewUserModuleScss; + +export = CreateNewUserModuleScssModule; diff --git a/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.tsx b/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.tsx index d3e243ddee9..59fca7a6883 100644 --- a/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.tsx +++ b/chaoscenter/web/src/views/CreateNewUser/CreateNewUser.tsx @@ -8,6 +8,8 @@ import * as Yup from 'yup'; import type { CreateUserMutationProps, User } from '@api/auth'; import { useStrings } from '@strings'; import { USERNAME_REGEX } from '@constants/validation'; +import PasswordInput from '@components/PasswordInput'; +import css from './CreateNewUser.module.scss'; interface CreateNewUserViewProps { createNewUserMutation: UseMutateFunction, unknown>; @@ -28,29 +30,22 @@ export default function CreateNewUserView(props: CreateNewUserViewProps): React. const { getString } = useStrings(); function handleSubmit(values: CreateNewUserFormProps): void { - createNewUserMutation( - { - body: { - name: values.name, - email: values.email, - username: values.username, - password: values.password, - role: 'user' - } - }, - { - onSuccess: () => { - handleClose(); - } + createNewUserMutation({ + body: { + name: values.name, + email: values.email, + username: values.username, + password: values.password, + role: 'user' } - ); + }); } return ( {getString('createNewUser')} - handleClose()} /> + @@ -99,15 +94,14 @@ export default function CreateNewUserView(props: CreateNewUserViewProps): React. placeholder={getString('enterYourUsername')} label={{getString('username')}} /> - {getString('password')}} + className={css.passwordField} /> - {getString('confirmPassword')}} /> diff --git a/mkdocs/docs/auth/v3.9.0/auth-api.json b/mkdocs/docs/auth/v3.9.0/auth-api.json index 93857d97bdc..792b86e92c7 100644 --- a/mkdocs/docs/auth/v3.9.0/auth-api.json +++ b/mkdocs/docs/auth/v3.9.0/auth-api.json @@ -488,6 +488,12 @@ "schema": { "$ref": "#/definitions/User" } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrBadRequest" + } } }, "parameters": [ @@ -1837,9 +1843,16 @@ }, "response.MessageResponse": { "type": "object", + "required": [ + "message", + "projectID" + ], "properties": { "message": { "type": "string" + }, + "projectID": { + "type": "string" } } }, @@ -1868,6 +1881,19 @@ "example": "The old and new passwords can't be same" } } + }, + "response.ErrBadRequest": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "The old and new passwords can't be same" + }, + "errorDescription": { + "type": "string", + "example": "The old and new passwords can't be same" + } + } } } } \ No newline at end of file