Skip to content

Commit

Permalink
Introduce TokenAcquisitionSession
Browse files Browse the repository at this point in the history
  • Loading branch information
vicr123 committed Sep 23, 2024
1 parent f67929c commit 4244b28
Show file tree
Hide file tree
Showing 12 changed files with 450 additions and 344 deletions.
1 change: 0 additions & 1 deletion Parlance.ClientApp/src/components/NavMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export default function NavMenu() {
if (UserManager.isLoggedIn) {
Modal.mount(<UserModal navigate={navigate} />);
} else {
UserManager.clearLoginDetails();
Modal.mount(<LoginUsernameModal />);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export function LoginErrorModal() {
{
text: t("LOG_IN_AGAIN"),
onClick: () => {
UserManager.clearLoginDetails();
Modal.mount(<LoginUsernameModal />);
},
},
Expand Down
126 changes: 51 additions & 75 deletions Parlance.ClientApp/src/components/modals/account/LoginOtpModal.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,60 @@
import Modal from "../../Modal";
import React, { FormEvent, ReactElement } from "react";
import UserManager from "../../../helpers/UserManager";
import React, { useState } from "react";
import { LoginPasswordModal } from "./LoginPasswordModal";
import { TFunction, withTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";
import LineEdit from "../../LineEdit";
import { VerticalLayout, VerticalSpacer } from "../../Layouts";
import Styles from "./LoginOtpModal.module.css";
import { TokenAcquisitionSession } from "@/helpers/TokenAcquisitionSession";

interface LoginOtpModalProps {
t: TFunction;
}

interface LoginOtpModalState {
otp: string;
}

class LoginOtpModalComponent extends React.Component<
LoginOtpModalProps,
LoginOtpModalState
> {
constructor(props: LoginOtpModalProps) {
super(props);
export function LoginOtpModal({
acquisitionSession,
}: {
acquisitionSession: TokenAcquisitionSession;
}) {
const [otp, setOtp] = useState("");
const { t } = useTranslation();

this.state = {
otp: "",
};
}

otpTextChanged(e: FormEvent) {
this.setState({
otp: (e.target as HTMLInputElement).value,
});
}

render() {
return (
<Modal
heading={this.props.t("TWO_FACTOR_AUTHENTICATION")}
buttons={[
{
text: this.props.t("BACK"),
onClick: () => Modal.mount(<LoginPasswordModal />),
return (
<Modal
heading={t("TWO_FACTOR_AUTHENTICATION")}
buttons={[
{
text: t("BACK"),
onClick: () =>
Modal.mount(
<LoginPasswordModal
acquisitionSession={acquisitionSession}
/>,
),
},
{
text: t("NEXT"),
onClick: () => {
acquisitionSession.setLoginDetail("otpToken", otp);
acquisitionSession.attemptLogin();
},
{
text: this.props.t("NEXT"),
onClick: () => {
UserManager.setLoginDetail(
"otpToken",
this.state.otp,
);
UserManager.attemptLogin();
},
},
]}
>
<div style={{ display: "flex", flexDirection: "column" }}>
<VerticalLayout>
<span>
{this.props.t(
"LOG_IN_TWO_FACTOR_AUTHENTICATION_PROMPT_1",
)}
</span>
<span className={Styles.hint}>
{this.props.t(
"LOG_IN_TWO_FACTOR_AUTHENTICATION_PROMPT_2",
)}
</span>
<VerticalSpacer height={10} />
<LineEdit
placeholder={this.props.t(
"TWO_FACTOR_AUTHENTICATION_CODE",
)}
value={this.state.otp}
onChange={this.otpTextChanged.bind(this)}
/>
</VerticalLayout>
</div>
</Modal>
);
}
},
]}
>
<div style={{ display: "flex", flexDirection: "column" }}>
<VerticalLayout>
<span>
{t("LOG_IN_TWO_FACTOR_AUTHENTICATION_PROMPT_1")}
</span>
<span className={Styles.hint}>
{t("LOG_IN_TWO_FACTOR_AUTHENTICATION_PROMPT_2")}
</span>
<VerticalSpacer height={10} />
<LineEdit
placeholder={t("TWO_FACTOR_AUTHENTICATION_CODE")}
value={otp}
onChange={e =>
setOtp((e.target as HTMLInputElement).value)
}
/>
</VerticalLayout>
</div>
</Modal>
);
}

export const LoginOtpModal = withTranslation()(LoginOtpModalComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ import { useTranslation } from "react-i18next";
import LineEdit from "../../LineEdit";
import ModalList from "../../ModalList";
import { VerticalSpacer } from "@/components/Layouts";
import { TokenAcquisitionSession } from "@/helpers/TokenAcquisitionSession";

export function LoginPasswordModal() {
const [password, setPassword] = useState(
UserManager.loginDetail("prePassword"),
);
export function LoginPasswordModal({
acquisitionSession,
}: {
acquisitionSession: TokenAcquisitionSession;
}) {
const [password, setPassword] = useState(acquisitionSession.prePassword);
const { t } = useTranslation();

useEffect(() => {
UserManager.setLoginDetail("prePassword");
}, []);

const loginTypes = UserManager.loginTypes!.map(type => {
const loginTypes = acquisitionSession.loginTypes.map(type => {
switch (type) {
case "password":
return (
Expand Down Expand Up @@ -48,7 +47,8 @@ export function LoginPasswordModal() {
{[
{
text: t("LOG_IN_USE_SECURITY_KEY_PROMPT"),
onClick: () => UserManager.attemptFido2Login(),
onClick: () =>
acquisitionSession.attemptFido2Login(),
},
]}
</ModalList>
Expand All @@ -59,23 +59,23 @@ export function LoginPasswordModal() {
return (
<Modal
heading={t("LOG_IN_PASSWORD_TITLE", {
username: UserManager.loginDetail("username"),
username: acquisitionSession.username,
})}
buttons={[
{
text: t("BACK"),
onClick: () => Modal.mount(<LoginUsernameModal />),
onClick: () => acquisitionSession.quit(),
},
{
text: t("FORGOT_PASSWORD"),
onClick: () => UserManager.triggerPasswordReset(),
onClick: () => acquisitionSession.triggerPasswordReset(),
},
{
text: t("NEXT"),
onClick: () => {
UserManager.setLoginDetail("password", password);
UserManager.setLoginDetail("type", "password");
UserManager.attemptLogin();
acquisitionSession.setLoginDetail("password", password);
acquisitionSession.setLoginDetail("type", "password");
acquisitionSession.attemptLogin();
},
},
]}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import Modal from "../../Modal";
import React, { useState } from "react";
import LoginUsernameModal from "./LoginUsernameModal";
import UserManager from "../../../helpers/UserManager";
import { useTranslation } from "react-i18next";
import LineEdit from "../../LineEdit";
import { TokenAcquisitionSession } from "@/helpers/TokenAcquisitionSession";

export function LoginPasswordResetModal() {
export function LoginPasswordResetModal({
acquisitionSession,
}: {
acquisitionSession: TokenAcquisitionSession;
}) {
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const { t } = useTranslation();
Expand All @@ -16,7 +19,7 @@ export function LoginPasswordResetModal() {
buttons={[
{
text: t("CANCEL"),
onClick: () => Modal.mount(<LoginUsernameModal />),
onClick: () => acquisitionSession.quit(),
},
{
text: t("OK"),
Expand All @@ -25,8 +28,11 @@ export function LoginPasswordResetModal() {
return;
}

UserManager.setLoginDetail("newPassword", password);
UserManager.attemptLogin();
acquisitionSession.setLoginDetail(
"newPassword",
password,
);
acquisitionSession.attemptLogin();
},
},
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import { useTranslation } from "react-i18next";
import Modal from "../../Modal";
import UserManager from "@/helpers/UserManager";
import { LoginPasswordModal } from "./LoginPasswordModal";
import { TokenAcquisitionSession } from "@/helpers/TokenAcquisitionSession";

export function LoginSecurityKeyFailureModal() {
export function LoginSecurityKeyFailureModal({
acquisitionSession,
}: {
acquisitionSession: TokenAcquisitionSession;
}) {
const { t } = useTranslation();

return (
Expand All @@ -12,12 +17,16 @@ export function LoginSecurityKeyFailureModal() {
{
text: t("SECURITY_KEY_USE_PASSWORD_INSTEAD"),
onClick: () => {
Modal.mount(<LoginPasswordModal />);
Modal.mount(
<LoginPasswordModal
acquisitionSession={acquisitionSession}
/>,
);
},
},
{
text: t("SECURITY_KEY_RETRY_LOGIN"),
onClick: () => UserManager.attemptFido2Login(),
onClick: () => acquisitionSession.attemptFido2Login(),
},
]}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import { ServerInformationContext } from "@/context/ServerInformationContext";
import { VerticalSpacer } from "@/components/Layouts";

export default function LoginUsernameModal() {
const [username, setUsername] = useState(
UserManager.loginDetail("username"),
);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const { t } = useTranslation();
const serverInformation = useContext(ServerInformationContext);
Expand All @@ -31,14 +29,12 @@ export default function LoginUsernameModal() {
text: t("NEXT"),
onClick: async () => {
try {
Modal.mount(<LoadingModal />);
await UserManager.setUsername(username);
if (password)
await UserManager.setLoginDetail(
"prePassword",
password,
);
Modal.mount(<LoginPasswordModal />);
const token = await UserManager.obtainToken(
username,
"login",
password ?? "",
);
await UserManager.setToken(token);
} catch {
Modal.mount(<LoginUsernameModal />);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import {
PasswordResetMethod,
PasswordResetMethodEmail,
} from "@/interfaces/users";
import { TokenAcquisitionSession } from "@/helpers/TokenAcquisitionSession";

export function EmailResetModal({
method,
resetMethods,
acquisitionSession,
}: {
method: PasswordResetMethodEmail;
resetMethods: PasswordResetMethod[];
acquisitionSession: TokenAcquisitionSession;
}) {
const [email, setEmail] = useState("");
const { t } = useTranslation();
Expand All @@ -28,13 +31,16 @@ export function EmailResetModal({
text: t("BACK"),
onClick: () =>
Modal.mount(
<PasswordResetModal resetMethods={resetMethods} />,
<PasswordResetModal
resetMethods={resetMethods}
acquisitionSession={acquisitionSession}
/>,
),
},
{
text: t("RESET_PASSWORD"),
onClick: () =>
UserManager.performPasswordReset("email", {
acquisitionSession.performPasswordReset("email", {
email: email,
}),
},
Expand Down
Loading

0 comments on commit 4244b28

Please sign in to comment.