Adding 2FA support. Fixes #938 (#939)

* Adding 2FA support. Fixes #938

* Updating totp_2fa names.
This commit is contained in:
Dessalines 2023-03-02 18:30:38 -05:00 committed by GitHub
parent 578709b986
commit 07e7e1eb87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 8 deletions

View file

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

View file

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

View file

@ -86,6 +86,7 @@ interface SettingsState {
show_read_posts?: boolean; show_read_posts?: boolean;
show_new_post_notifs?: boolean; show_new_post_notifs?: boolean;
discussion_languages?: number[]; discussion_languages?: number[];
generate_totp_2fa?: boolean;
}; };
changePasswordForm: { changePasswordForm: {
new_password?: string; new_password?: string;
@ -789,6 +790,7 @@ export class Settings extends Component<any, SettingsState> {
</label> </label>
</div> </div>
</div> </div>
{this.totpSection()}
<div className="form-group"> <div className="form-group">
<button type="submit" className="btn btn-block btn-secondary mr-4"> <button type="submit" className="btn btn-block btn-secondary mr-4">
{this.state.saveUserSettingsLoading ? ( {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_2fa_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_2fa}
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_2fa == false
}
onChange={linkEvent(this, this.handleRemoveTotp)}
/>
<label className="form-check-label" htmlFor="user-remove-totp">
{i18n.t("remove_two_factor")}
</label>
</div>
</div>
</>
)}
</>
);
}
setupBlockPersonChoices() { setupBlockPersonChoices() {
if (isBrowser()) { if (isBrowser()) {
let selectId: any = document.getElementById("block-person-filter"); let selectId: any = document.getElementById("block-person-filter");
@ -1017,6 +1071,23 @@ export class Settings extends Component<any, SettingsState> {
i.setState(i.state); 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_2fa = 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_2fa = checked;
i.setState(i.state);
}
handleSendNotificationsToEmailChange(i: Settings, event: any) { handleSendNotificationsToEmailChange(i: Settings, event: any) {
i.state.saveUserSettingsForm.send_notifications_to_email = i.state.saveUserSettingsForm.send_notifications_to_email =
event.target.checked; event.target.checked;

View file

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

View file

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