mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-10 02:15:11 +00:00
* Adding 2FA support. Fixes #938 * Updating totp_2fa names.
This commit is contained in:
parent
578709b986
commit
07e7e1eb87
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue