Skip to content

Commit

Permalink
feat: Added password reset as a route to manage initial login case (#…
Browse files Browse the repository at this point in the history
…4744)

* feat: Added password reset as a route to manage initial login case

Signed-off-by: Hrishav <hrishav.kumar@harness.io>

* feat: Fixed deepspan issue

Signed-off-by: Hrishav <hrishav.kumar@harness.io>

* fix: Updated API response in front-end

Signed-off-by: Hrishav <hrishav.kumar@harness.io>

* chore: addressed review comment

Signed-off-by: Hrishav <hrishav.kumar@harness.io>

---------

Signed-off-by: Hrishav <hrishav.kumar@harness.io>
  • Loading branch information
hrishavjha authored Jul 5, 2024
1 parent 00f0bd7 commit fb46bb9
Show file tree
Hide file tree
Showing 23 changed files with 347 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,10 @@ func UpdatePassword(service services.ApplicationService) gin.HandlerFunc {
log.Info(err)
if strings.Contains(err.Error(), "old and new passwords can't be same") {
c.JSON(utils.ErrorStatusCodes[utils.ErrOldPassword], presenter.CreateErrorResponse(utils.ErrOldPassword))
} else if strings.Contains(err.Error(), "invalid credentials") {
c.JSON(utils.ErrorStatusCodes[utils.ErrInvalidCredentials], presenter.CreateErrorResponse(utils.ErrInvalidCredentials))
} else {
c.JSON(utils.ErrorStatusCodes[utils.ErrInvalidRequest], presenter.CreateErrorResponse(utils.ErrInvalidRequest))
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
}
return
}
Expand Down
2 changes: 1 addition & 1 deletion chaoscenter/authentication/pkg/user/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (r repository) UpdatePassword(userPassword *entities.UserPassword, isAdminB
if isAdminBeingReset {
err := bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(userPassword.OldPassword))
if err != nil {
return err
return fmt.Errorf("invalid credentials")
}
// check if the new pwd is same as old pwd, if yes return err
err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(userPassword.NewPassword))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// Please do not modify this code directly.
import { useMutation, UseMutationOptions } from '@tanstack/react-query';

import type { ResponseMessageResponse } from '../schemas/ResponseMessageResponse';
import type { ResponseErrOldPassword } from '../schemas/ResponseErrOldPassword';
import type { ResponseErrInvalidCredentials } from '../schemas/ResponseErrInvalidCredentials';
import { fetcher, FetcherOptions } from 'services/fetcher';

export type UpdatePasswordRequestBody = {
Expand All @@ -11,11 +14,9 @@ export type UpdatePasswordRequestBody = {
username: string;
};

export type UpdatePasswordOkResponse = {
message?: string;
};
export type UpdatePasswordOkResponse = ResponseMessageResponse;

export type UpdatePasswordErrorResponse = unknown;
export type UpdatePasswordErrorResponse = ResponseErrOldPassword | ResponseErrInvalidCredentials;

export interface UpdatePasswordProps extends Omit<FetcherOptions<unknown, UpdatePasswordRequestBody>, 'url'> {
body: UpdatePasswordRequestBody;
Expand Down
3 changes: 3 additions & 0 deletions chaoscenter/web/src/api/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,8 @@ 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 { ResponseErrInvalidCredentials } from './schemas/ResponseErrInvalidCredentials';
export type { ResponseErrOldPassword } from './schemas/ResponseErrOldPassword';
export type { ResponseMessageResponse } from './schemas/ResponseMessageResponse';
export type { User } from './schemas/User';
export type { Users } from './schemas/Users';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable */
// This code is autogenerated using @harnessio/oats-cli.
// Please do not modify this code directly.

export interface ResponseErrInvalidCredentials {
/**
* @example "The old and new passwords can't be same"
*/
error?: string;
/**
* @example "The old and new passwords can't be same"
*/
errorDescription?: string;
}
14 changes: 14 additions & 0 deletions chaoscenter/web/src/api/auth/schemas/ResponseErrOldPassword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable */
// This code is autogenerated using @harnessio/oats-cli.
// Please do not modify this code directly.

export interface ResponseErrOldPassword {
/**
* @example "The old and new passwords can't be same"
*/
error?: string;
/**
* @example "The old and new passwords can't be same"
*/
errorDescription?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable */
// This code is autogenerated using @harnessio/oats-cli.
// Please do not modify this code directly.

export interface ResponseMessageResponse {
message?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface PasswordInputProps {
disabled?: boolean;
placeholder?: string;
name: string;
label: string;
label: string | React.ReactElement;
}

const PasswordInput = (props: PasswordInputProps): React.ReactElement => {
Expand All @@ -22,10 +22,12 @@ const PasswordInput = (props: PasswordInputProps): React.ReactElement => {

return (
<Layout.Vertical className={style.fieldContainer}>
{label && (
{label && typeof label === 'string' ? (
<Text font={{ variation: FontVariation.BODY, weight: 'semi-bold' }} color={Color.GREY_600}>
{label}
</Text>
) : (
label
)}
<div className={style.inputContainer}>
<FormInput.Text
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
import React from 'react';
import { useToaster } from '@harnessio/uicore';
import { useHistory } from 'react-router-dom';
import { useUpdatePasswordMutation } from '@api/auth';
import AccountPasswordChangeView from '@views/AccountPasswordChange';
import { useLogout, useRouteWithBaseUrl } from '@hooks';
import { useLogout } from '@hooks';
import { useStrings } from '@strings';
import { setUserDetails } from '@utils';

interface AccountPasswordChangeViewProps {
handleClose: () => void;
username: string | undefined;
initialMode?: boolean;
}

export default function AccountPasswordChangeController(props: AccountPasswordChangeViewProps): React.ReactElement {
const { handleClose, username, initialMode } = props;
const { handleClose, username } = props;
const { showSuccess } = useToaster();
const { getString } = useStrings();
const history = useHistory();
const paths = useRouteWithBaseUrl();
const { forceLogout } = useLogout();

const { mutate: updatePasswordMutation, isLoading } = useUpdatePasswordMutation(
{},
{
onSuccess: data => {
setUserDetails({ isInitialLogin: false });
if (initialMode) {
history.push(paths.toDashboard());
} else {
showSuccess(`${data.message}, ${getString('loginToContinue')}`);
forceLogout();
}
showSuccess(`${data.message}, ${getString('loginToContinue')}`);
forceLogout();
}
}
);
Expand All @@ -42,7 +34,6 @@ export default function AccountPasswordChangeController(props: AccountPasswordCh
updatePasswordMutation={updatePasswordMutation}
updatePasswordMutationLoading={isLoading}
username={username}
initialMode={initialMode}
/>
);
}
20 changes: 15 additions & 5 deletions chaoscenter/web/src/controllers/Login/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useToaster } from '@harnessio/uicore';
import jwtDecode from 'jwt-decode';
import LoginPageView from '@views/Login';
import { useLoginMutation, useGetCapabilitiesQuery, useGetUserQuery } from '@api/auth';
import { getUserDetails, setUserDetails } from '@utils';
import { getUserDetails, setUserDetails, toTitleCase } from '@utils';
import { normalizePath } from '@routes/RouteDefinitions';
import type { DecodedTokenType, PermissionGroup } from '@models';
import { useSearchParams } from '@hooks';
Expand Down Expand Up @@ -37,7 +37,13 @@ const LoginController: React.FC = () => {
const { isLoading, mutate: handleLogin } = useLoginMutation(
{},
{
onError: err => showError(err.error),
onError: err =>
showError(
toTitleCase({
separator: '_',
text: err.error ?? ''
})
),
onSuccess: response => {
if (response.accessToken) {
setUserDetails(response);
Expand All @@ -60,9 +66,13 @@ const LoginController: React.FC = () => {
setUserDetails({
isInitialLogin: response.isInitialLogin
});
history.push(
normalizePath(`/account/${userDetails.accountID}/project/${userDetails.projectID ?? ''}/dashboard`)
);
if (response.isInitialLogin) {
history.push(`/account/${userDetails.accountID}/settings/password-reset`);
} else {
history.push(
normalizePath(`/account/${userDetails.accountID}/project/${userDetails.projectID ?? ''}/dashboard`)
);
}
}
}
);
Expand Down
17 changes: 2 additions & 15 deletions chaoscenter/web/src/controllers/Overview/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import React from 'react';
import { useToaster } from '@harnessio/uicore';
import { getChaosHubStats, getExperimentStats, getInfraStats, listExperiment } from '@api/core';
import { getScope, getUserDetails } from '@utils';
import { getScope } from '@utils';
import OverviewView from '@views/Overview';
import { generateExperimentDashboardTableContent } from '@controllers/ExperimentDashboardV2/helpers';
import type { ExperimentDashboardTableProps } from '@controllers/ExperimentDashboardV2';
import { useGetUserQuery } from '@api/auth';

export default function OverviewController(): React.ReactElement {
const scope = getScope();
const { showError } = useToaster();
const userDetails = getUserDetails();

const { data: chaosHubStats, loading: loadingChaosHubStats } = getChaosHubStats({
...scope
Expand All @@ -37,15 +35,6 @@ export default function OverviewController(): React.ReactElement {
}
});

const { data: currentUserData, isLoading: getUserLoading } = useGetUserQuery(
{
user_id: userDetails.accountID
},
{
enabled: !!userDetails.accountID
}
);

const experiments = experimentRunData?.listExperiment.experiments;

const experimentDashboardTableData: ExperimentDashboardTableProps | undefined = experiments && {
Expand All @@ -58,10 +47,8 @@ export default function OverviewController(): React.ReactElement {
chaosHubStats: loadingChaosHubStats,
infraStats: loadingInfraStats,
experimentStats: loadingExperimentStats,
recentExperimentsTable: loadingRecentExperimentsTable,
getUser: getUserLoading
recentExperimentsTable: loadingRecentExperimentsTable
}}
currentUserData={currentUserData}
chaosHubStats={chaosHubStats?.getChaosHubStats}
infraStats={infraStats?.getInfraStats}
experimentStats={experimentStats?.getExperimentStats}
Expand Down
47 changes: 47 additions & 0 deletions chaoscenter/web/src/controllers/PasswordReset/PasswordReset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { useToaster } from '@harnessio/uicore';
import { useHistory } from 'react-router-dom';
import PasswordResetView from '@views/PasswordReset';
import { useGetUserQuery, useUpdatePasswordMutation } from '@api/auth';
import { getUserDetails, setUserDetails } from '@utils';
import { normalizePath } from '@routes/RouteDefinitions';

const PasswordResetController = (): React.ReactElement => {
const { accountID, projectID } = getUserDetails();
const { showSuccess, showError } = useToaster();
const history = useHistory();

const { data: currentUserData, isLoading: getUserLoading } = useGetUserQuery(
{
user_id: accountID
},
{
enabled: !!accountID
}
);

const { mutate: updatePasswordMutation, isLoading: updatePasswordLoading } = useUpdatePasswordMutation(
{},
{
onSuccess: data => {
setUserDetails({ isInitialLogin: false });
showSuccess(`${data.message}`);
history.push(normalizePath(`/account/${accountID}/project/${projectID}/dashboard`));
},
onError: err => showError(err.errorDescription)
}
);

return (
<PasswordResetView
currentUserData={currentUserData}
updatePasswordMutation={updatePasswordMutation}
loading={{
getUser: getUserLoading,
updatePassword: updatePasswordLoading
}}
/>
);
};

export default PasswordResetController;
3 changes: 3 additions & 0 deletions chaoscenter/web/src/controllers/PasswordReset/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import PasswordResetController from './PasswordReset';

export default PasswordResetController;
2 changes: 2 additions & 0 deletions chaoscenter/web/src/routes/RouteDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface UseRouteDefinitionsProps {
toKubernetesChaosInfrastructures(params: { environmentID: string }): string;
toKubernetesChaosInfrastructureDetails(params: { chaosInfrastructureID: string; environmentID: string }): string;
toAccountSettingsOverview(): string;
toPasswordReset(): string;
toProjectSetup(): string;
toProjectMembers(): string;
toImageRegistry(): string;
Expand Down Expand Up @@ -60,6 +61,7 @@ export const paths: UseRouteDefinitionsProps = {
`/environments/${environmentID}/kubernetes/${chaosInfrastructureID}`,
// Account Scoped Routes
toAccountSettingsOverview: () => '/settings/overview',
toPasswordReset: () => '/settings/password-reset',
// Project Setup Routes
toProjectSetup: () => '/setup',
toProjectMembers: () => '/setup/members',
Expand Down
10 changes: 7 additions & 3 deletions chaoscenter/web/src/routes/RouteDestinations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import AccountSettingsController from '@controllers/AccountSettings';
import ProjectMembersView from '@views/ProjectMembers';
import ChaosProbesController from '@controllers/ChaosProbes';
import ChaosProbeController from '@controllers/ChaosProbe';
import PasswordResetController from '@controllers/PasswordReset';

const experimentID = ':experimentID';
const runID = ':runID';
Expand All @@ -45,14 +46,14 @@ export function RoutesWithAuthentication(): React.ReactElement {
const history = useHistory();

const { forceLogout } = useLogout();
const { accessToken: token, isInitialLogin } = getUserDetails();
const { accessToken: token, isInitialLogin, accountID } = getUserDetails();

useEffect(() => {
if (!token || !isUserAuthenticated()) {
forceLogout();
}
if (isInitialLogin) {
history.push(projectRenderPaths.toDashboard());
history.push(`/account/${accountID}/settings/password-reset`);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token, isInitialLogin]);
Expand All @@ -61,7 +62,10 @@ export function RoutesWithAuthentication(): React.ReactElement {
<Switch>
<Redirect exact from={accountMatchPaths.toRoot()} to={accountRenderPaths.toAccountSettingsOverview()} />
<Redirect exact from={projectMatchPaths.toRoot()} to={projectRenderPaths.toDashboard()} />
<Route exact path={accountMatchPaths.toAccountSettingsOverview()} component={AccountSettingsController} />
{/* Account */}
<Route exact path={accountRenderPaths.toAccountSettingsOverview()} component={AccountSettingsController} />
<Route exact path={accountRenderPaths.toPasswordReset()} component={PasswordResetController} />
{/* Dashboard */}
<Route exact path={projectMatchPaths.toDashboard()} component={OverviewController} />
{/* Chaos Experiments */}
<Route exact path={projectMatchPaths.toExperiments()} component={ExperimentDashboardV2Controller} />
Expand Down
Loading

0 comments on commit fb46bb9

Please sign in to comment.