Adding 2FA support. Fixes #938

This commit is contained in:
Dessalines 2023-02-17 14:18:40 -05:00
parent deffaf1ee0
commit af1e8868bd
6 changed files with 120 additions and 9 deletions

@ -1 +1 @@
Subproject commit 819531ae64c6cba12cb406eb98333fd52988bf3e
Subproject commit 1c42c579460871de7b4ea18e58dc25543b80d289

View file

@ -45,7 +45,7 @@
"inferno-server": "^8.0.5",
"isomorphic-cookie": "^1.2.4",
"jwt-decode": "^3.1.2",
"lemmy-js-client": "0.17.2-rc.1",
"lemmy-js-client": "0.17.2-rc.2",
"markdown-it": "^13.0.1",
"markdown-it-container": "^3.0.0",
"markdown-it-footnote": "^3.0.3",

View file

@ -26,8 +26,10 @@ interface State {
form: {
username_or_email?: string;
password?: string;
totp_token?: string;
};
loginLoading: boolean;
showTotp: boolean;
siteRes: GetSiteResponse;
}
@ -38,6 +40,7 @@ export class Login extends Component<any, State> {
state: State = {
form: {},
loginLoading: false,
showTotp: false,
siteRes: this.isoData.site_res,
};
@ -141,6 +144,28 @@ export class Login extends Component<any, State> {
</button>
</div>
</div>
{this.state.showTotp && (
<div className="form-group row">
<label
className="col-sm-6 col-form-label"
htmlFor="login-totp-token"
>
{i18n.t("two_factor_token")}
</label>
<div className="col-sm-6">
<input
type="number"
inputMode="numeric"
className="form-control"
id="login-totp-token"
pattern="[0-9]*"
autoComplete="one-time-code"
value={this.state.form.totp_token}
onInput={linkEvent(this, this.handleLoginTotpChange)}
/>
</div>
</div>
)}
<div className="form-group row">
<div className="col-sm-10">
<button type="submit" className="btn btn-secondary">
@ -159,10 +184,12 @@ export class Login extends Component<any, State> {
let lForm = i.state.form;
let username_or_email = lForm.username_or_email;
let password = lForm.password;
let totp_token = lForm.totp_token;
if (username_or_email && password) {
let form: LoginI = {
username_or_email,
password,
totp_token,
};
WebSocketService.Instance.send(wsClient.login(form));
}
@ -173,6 +200,11 @@ export class Login extends Component<any, State> {
i.setState(i.state);
}
handleLoginTotpChange(i: Login, event: any) {
i.state.form.totp_token = event.target.value;
i.setState(i.state);
}
handleLoginPasswordChange(i: Login, event: any) {
i.state.form.password = event.target.value;
i.setState(i.state);
@ -191,9 +223,16 @@ export class Login extends Component<any, State> {
let op = wsUserOp(msg);
console.log(msg);
if (msg.error) {
toast(i18n.t(msg.error), "danger");
this.setState({ form: {} });
// If the error comes back that the token is missing, show the TOTP field
if (msg.error == "missing_totp_token") {
this.setState({ showTotp: true, loginLoading: false });
toast(i18n.t("enter_two_factor_code"));
return;
} else {
toast(i18n.t(msg.error), "danger");
this.setState({ form: {}, loginLoading: false });
return;
}
} else {
if (op == UserOperation.Login) {
let data = wsJsonToRes<LoginResponse>(msg);

View file

@ -86,6 +86,7 @@ interface SettingsState {
show_read_posts?: boolean;
show_new_post_notifs?: boolean;
discussion_languages?: number[];
generate_totp?: boolean;
};
changePasswordForm: {
new_password?: string;
@ -789,6 +790,7 @@ export class Settings extends Component<any, SettingsState> {
</label>
</div>
</div>
{this.totpSection()}
<div className="form-group">
<button type="submit" className="btn btn-block btn-secondary mr-4">
{this.state.saveUserSettingsLoading ? (
@ -853,6 +855,58 @@ export class Settings extends Component<any, SettingsState> {
);
}
totpSection() {
let totpUrl =
UserService.Instance.myUserInfo?.local_user_view.local_user.totp_url;
return (
<>
{!totpUrl && (
<div className="form-group">
<div className="form-check">
<input
className="form-check-input"
id="user-generate-totp"
type="checkbox"
checked={this.state.saveUserSettingsForm.generate_totp}
onChange={linkEvent(this, this.handleGenerateTotp)}
/>
<label className="form-check-label" htmlFor="user-generate-totp">
{i18n.t("set_up_two_factor")}
</label>
</div>
</div>
)}
{totpUrl && (
<>
<div>
<a className="btn btn-secondary mb-2" href={totpUrl}>
{i18n.t("two_factor_link")}
</a>
</div>
<div className="form-group">
<div className="form-check">
<input
className="form-check-input"
id="user-remove-totp"
type="checkbox"
checked={
this.state.saveUserSettingsForm.generate_totp == false
}
onChange={linkEvent(this, this.handleRemoveTotp)}
/>
<label className="form-check-label" htmlFor="user-remove-totp">
{i18n.t("remove_two_factor")}
</label>
</div>
</div>
</>
)}
</>
);
}
setupBlockPersonChoices() {
if (isBrowser()) {
let selectId: any = document.getElementById("block-person-filter");
@ -1017,6 +1071,23 @@ export class Settings extends Component<any, SettingsState> {
i.setState(i.state);
}
handleGenerateTotp(i: Settings, event: any) {
// Coerce false to undefined here, so it won't generate it.
let checked: boolean | undefined = event.target.checked || undefined;
if (checked) {
toast(i18n.t("two_factor_setup_instructions"));
}
i.state.saveUserSettingsForm.generate_totp = checked;
i.setState(i.state);
}
handleRemoveTotp(i: Settings, event: any) {
// Coerce true to undefined here, so it won't generate it.
let checked: boolean | undefined = !event.target.checked && undefined;
i.state.saveUserSettingsForm.generate_totp = checked;
i.setState(i.state);
}
handleSendNotificationsToEmailChange(i: Settings, event: any) {
i.state.saveUserSettingsForm.send_notifications_to_email =
event.target.checked;

View file

@ -500,6 +500,7 @@ export function toast(text: string, background = "success") {
backgroundColor: backgroundColor,
gravity: "bottom",
position: "left",
duration: 5000,
}).showToast();
}
}

View file

@ -5324,10 +5324,10 @@ leac@^0.6.0:
resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
lemmy-js-client@0.17.2-rc.1:
version "0.17.2-rc.1"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.1.tgz#fe8d1508311bbf245acc98c2c3e47e2165a95b14"
integrity sha512-YrOXuCofgkqp28krmPTQZAfUWL5zEDA0sRJ0abKcgf/I8YYkYkUkPS9TOORN5Lv3bc8RAAz4+2/zLHqYL/Tnow==
lemmy-js-client@0.17.2-rc.2:
version "0.17.2-rc.2"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.2.tgz#c2b045742c44992ec563c75a17cc1c7ebbb385eb"
integrity sha512-Nsb0CnQ8JA8KlhDxb4Tri1QTT8l+4KMjbh17hBmMTjXzlSbo0xDyGBzF60Z2ffKu1ZPPll1ncO2tJeqCroin6w==
dependencies:
node-fetch "2.6.6"