fleet/frontend/pages/MfaPage/MfaPage.tsx
Gabriel Hernandez 084ebe6e16 update auth token storage (#40504)
**Related issue:** Resolves #14401

this updates the mechanism of storing the auth token for a user that is
used for making requests and validating a user session. We change the
storage from local storage to a cookie. This allow a bit more security
and prepares for a future change where we will allow the browser to
handle setting and passing the auth token in the request.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [x] QA'd all new/changed functionality manually
2026-02-27 12:36:42 -06:00

125 lines
3.3 KiB
TypeScript

import React, { useContext, useState, useEffect } from "react";
import { InjectedRouter } from "react-router";
import { Params } from "react-router/lib/Router";
import { AppContext } from "context/app";
import { RoutingContext } from "context/routing";
import paths from "router/paths";
import local from "utilities/local";
import authToken from "utilities/auth_token";
import configAPI from "services/entities/config";
import sessionsAPI from "services/entities/sessions";
import Button from "components/buttons/Button";
import AuthenticationFormWrapper from "components/AuthenticationFormWrapper";
import Spinner from "components/Spinner";
interface IMfaPage {
router: InjectedRouter; // v3
params: Params;
}
const baseClass = "mfa-page";
const MfaPage = ({ router, params }: IMfaPage) => {
const { token: mfaToken } = params;
const {
config,
currentUser,
setAvailableTeams,
setConfig,
setCurrentUser,
setCurrentTeam,
} = useContext(AppContext);
const { redirectLocation } = useContext(RoutingContext);
const [isExpired, setIsExpired] = useState(false);
const [shouldFinishMFA, setShouldFinishMFA] = useState(
!!local.getItem("auth_pending_mfa")
);
local.removeItem("auth_pending_mfa");
const finishMFA = async () => {
const { DASHBOARD, RESET_PASSWORD, NO_ACCESS } = paths;
try {
const response = await sessionsAPI.finishMFA({ token: mfaToken });
const { user, available_teams, token } = response;
authToken.save(token);
setCurrentUser(user);
setAvailableTeams(user, available_teams);
setCurrentTeam(undefined);
if (!user.global_role && user.teams.length === 0) {
router.push(NO_ACCESS);
return;
}
// Redirect to password reset page if user is forced to reset password.
// Any other requests will fail.
else if (user.force_password_reset) {
router.push(RESET_PASSWORD);
return;
} else if (config) {
router.push(redirectLocation || DASHBOARD);
return;
}
configAPI.loadAll().then((configResponse) => {
setConfig(configResponse);
router.push(redirectLocation || DASHBOARD);
});
} catch (response) {
setIsExpired(true);
}
};
useEffect(() => {
if (shouldFinishMFA) {
finishMFA();
}
}, [shouldFinishMFA, finishMFA]);
useEffect(() => {
if (currentUser) {
return router.push(paths.DASHBOARD);
}
}, [currentUser, router]);
const onClickLoginButton = () => {
router.push(paths.LOGIN);
};
const onClickFinishLoginButton = () => {
setShouldFinishMFA(true);
};
if (!shouldFinishMFA) {
return (
<AuthenticationFormWrapper className={baseClass}>
<Button onClick={onClickFinishLoginButton}>Log in</Button>
</AuthenticationFormWrapper>
);
}
if (isExpired) {
return (
<AuthenticationFormWrapper className={baseClass} header="Invalid token">
<>
<div className={`${baseClass}__description`}>
<p>Log in again for a new link.</p>
</div>
<Button onClick={onClickLoginButton}>Back to login</Button>
</>
</AuthenticationFormWrapper>
);
}
return (
<AuthenticationFormWrapper className={baseClass}>
<Spinner />
</AuthenticationFormWrapper>
);
};
export default MfaPage;