feat: improve MFA by using user's own Email and Phone (#2002)

* refactor: mfa

* fix: clean code

* fix: clean code

* fix: fix crash and improve robot
This commit is contained in:
Yaodong Yu
2023-06-21 18:56:37 +08:00
committed by GitHub
parent 6ebca6dbe7
commit c391af4552
28 changed files with 528 additions and 362 deletions

View File

@ -34,7 +34,6 @@ import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
import PopconfirmModal from "./common/modal/PopconfirmModal";
import {DeleteMfa} from "./backend/MfaBackend";
import {CheckCircleOutlined, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons";
import {SmsMfaType} from "./auth/MfaSetupPage";
import * as MfaBackend from "./backend/MfaBackend";
const {Option} = Select;
@ -189,16 +188,6 @@ class UserEditPage extends React.Component {
return this.props.account.countryCode;
}
getMfaProps(type = "") {
if (!(this.state.multiFactorAuths?.length > 0)) {
return [];
}
if (type === "") {
return this.state.multiFactorAuths;
}
return this.state.multiFactorAuths.filter(mfaProps => mfaProps.type === type);
}
loadMore = (table, type) => {
return <div
style={{
@ -216,13 +205,12 @@ class UserEditPage extends React.Component {
</div>;
};
deleteMfa = (id) => {
deleteMfa = () => {
this.setState({
RemoveMfaLoading: true,
});
DeleteMfa({
id: id,
owner: this.state.user.owner,
name: this.state.user.name,
}).then((res) => {
@ -447,7 +435,7 @@ class UserEditPage extends React.Component {
<CountryCodeSelect
style={{width: "30%"}}
// disabled={!Setting.isLocalAdminUser(this.props.account) ? true : disabled}
value={this.state.user.countryCode}
initValue={this.state.user.countryCode}
onChange={(value) => {
this.updateUserField("countryCode", value);
}}
@ -860,61 +848,61 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("mfa:Multi-factor authentication"), i18next.t("mfa:Multi-factor authentication - Tooltip "))} :
</Col>
<Col span={22} >
<Card title={i18next.t("mfa:Multi-factor methods")}>
<Card type="inner" title={i18next.t("mfa:SMS/Email message")}>
<List
itemLayout="horizontal"
dataSource={this.getMfaProps(SmsMfaType)}
loadMore={this.loadMore(this.state.multiFactorAuths, SmsMfaType)}
renderItem={(item, index) => (
<List.Item>
<div>
{item?.id === undefined ?
<Button type={"default"} onClick={() => {
Setting.goToLink("/mfa-authentication/setup");
}}>
{i18next.t("mfa:Setup")}
</Button> :
<Card title={i18next.t("mfa:Multi-factor methods")}
extra={this.state.multiFactorAuths?.some(mfaProps => mfaProps.enabled) ?
<PopconfirmModal
text={i18next.t("general:Disable")}
title={i18next.t("general:Sure to disable") + "?"}
onConfirm={() => this.deleteMfa()}
/> : null
}>
<List
rowKey="mfaType"
itemLayout="horizontal"
dataSource={this.state.multiFactorAuths}
renderItem={(item, index) => (
<List.Item>
<Space>
{i18next.t("general:Type")}: {item.mfaType}
{item.secret}
</Space>
{item.enabled ? (
<Space>
{item.enabled ?
<Tag icon={<CheckCircleOutlined />} color="success">
{i18next.t("general:Enabled")}
</Tag>
</Tag> : null
}
{item.secret}
</div>
{item?.id === undefined ? null :
<div>
{item.isPreferred ?
<Tag icon={<CheckCircleOutlined />} color="blue" style={{marginRight: 20}} >
{i18next.t("mfa:preferred")}
</Tag> :
<Button type="primary" style={{marginRight: 20}} onClick={() => {
const values = {
owner: this.state.user.owner,
name: this.state.user.name,
id: item.id,
};
MfaBackend.SetPreferredMfa(values).then((res) => {
if (res.status === "ok") {
this.setState({
multiFactorAuths: res.data,
});
}
});
}}>
{i18next.t("mfa:Set preferred")}
</Button>
}
<PopconfirmModal
title={i18next.t("general:Sure to delete") + "?"}
onConfirm={() => this.deleteMfa(item.id)}
>
</PopconfirmModal>
</div>
}
</List.Item>
)}
/>
</Card>
{item.isPreferred ?
<Tag icon={<CheckCircleOutlined />} color="blue" style={{marginRight: 20}} >
{i18next.t("mfa:preferred")}
</Tag> :
<Button type="primary" style={{marginRight: 20}} onClick={() => {
const values = {
owner: this.state.user.owner,
name: this.state.user.name,
mfaType: item.mfaType,
};
MfaBackend.SetPreferredMfa(values).then((res) => {
if (res.status === "ok") {
this.setState({
multiFactorAuths: res.data,
});
}
});
}}>
{i18next.t("mfa:Set preferred")}
</Button>
}
</Space>
) : <Button type={"default"} onClick={() => {
Setting.goToLink(`/mfa-authentication/setup?mfaType=${item.mfaType}`);
}}>
{i18next.t("mfa:Setup")}
</Button>}
</List.Item>
)}
/>
</Card>
</Col>
</Row>

View File

@ -16,8 +16,8 @@ import React, {useState} from "react";
import i18next from "i18next";
import {Button, Input} from "antd";
import * as AuthBackend from "./AuthBackend";
import {SmsMfaType} from "./MfaSetupPage";
import {MfaSmsVerifyForm} from "./MfaVerifyForm";
import {EmailMfaType, SmsMfaType} from "./MfaSetupPage";
import {MfaSmsVerifyForm, mfaAuth} from "./MfaVerifyForm";
export const NextMfa = "NextMfa";
export const RequiredMfa = "RequiredMfa";
@ -26,20 +26,20 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
formValues.password = "";
formValues.username = "";
const [loading, setLoading] = useState(false);
const [type, setType] = useState(mfaProps.type);
const [mfaType, setMfaType] = useState(mfaProps.mfaType);
const [recoveryCode, setRecoveryCode] = useState("");
const verify = ({passcode}) => {
setLoading(true);
const values = {...formValues, passcode, mfaType: type};
const values = {...formValues, passcode, mfaType};
AuthBackend.login(values, oAuthParams).then((res) => {
if (res.status === "ok") {
onSuccess(res);
} else {
onFail(res.msg);
}
}).catch((reason) => {
onFail(reason.message);
}).catch((res) => {
onFail(res.message);
}).finally(() => {
setLoading(false);
});
@ -49,19 +49,18 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
setLoading(true);
AuthBackend.login({...formValues, recoveryCode}, oAuthParams).then(res => {
if (res.status === "ok") {
onSuccess();
onSuccess(res);
} else {
onFail(res.msg);
}
}).catch((reason) => {
onFail(reason.message);
}).catch((res) => {
onFail(res.message);
}).finally(() => {
setLoading(false);
});
};
switch (type) {
case SmsMfaType:
if (mfaType === SmsMfaType || mfaType === EmailMfaType) {
return (
<div style={{width: 300, height: 350}}>
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
@ -72,20 +71,21 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
</div>
<MfaSmsVerifyForm
mfaProps={mfaProps}
method={mfaAuth}
onFinish={verify}
application={application}
/>
<span style={{float: "right"}}>
{i18next.t("mfa:Have problems?")}
<a onClick={() => {
setType("recovery");
setMfaType("recovery");
}}>
{i18next.t("mfa:Use a recovery code")}
</a>
</span>
</div>
);
case "recovery":
} else if (mfaType === "recovery") {
return (
<div style={{width: 300, height: 350}}>
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
@ -108,14 +108,12 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
<span style={{float: "right"}}>
{i18next.t("mfa:Have problems?")}
<a onClick={() => {
setType(mfaProps.type);
setMfaType(mfaProps.mfaType);
}}>
{i18next.t("mfa:Use SMS verification code")}
</a>
</span>
</div>
);
default:
return null;
}
}

View File

@ -12,18 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import React, {useState} from "react";
import React, {useEffect, useState} from "react";
import {Button, Col, Form, Input, Result, Row, Steps} from "antd";
import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Setting from "../Setting";
import i18next from "i18next";
import * as MfaBackend from "../backend/MfaBackend";
import {CheckOutlined, KeyOutlined, LockOutlined, UserOutlined} from "@ant-design/icons";
import * as UserBackend from "../backend/UserBackend";
import {MfaSmsVerifyForm, MfaTotpVerifyForm} from "./MfaVerifyForm";
import * as ApplicationBackend from "../backend/ApplicationBackend";
import {MfaSmsVerifyForm, MfaTotpVerifyForm, mfaSetup} from "./MfaVerifyForm";
const {Step} = Steps;
export const EmailMfaType = "email";
export const SmsMfaType = "sms";
export const TotpMfaType = "app";
@ -76,12 +76,29 @@ function CheckPasswordForm({user, onSuccess, onFail}) {
);
}
export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail}) {
export function MfaVerifyForm({mfaType, application, user, onSuccess, onFail}) {
const [form] = Form.useForm();
mfaProps = mfaProps ?? {type: ""};
const [mfaProps, setMfaProps] = useState({mfaType: mfaType});
useEffect(() => {
if (mfaType === SmsMfaType) {
setMfaProps({
mfaType: mfaType,
secret: user.phone,
countryCode: user.countryCode,
});
}
if (mfaType === EmailMfaType) {
setMfaProps({
mfaType: mfaType,
secret: user.email,
});
}
}, [mfaType]);
const onFinish = ({passcode}) => {
const data = {passcode, type: mfaProps.type, ...user};
const data = {passcode, mfaType: mfaType, ...user};
MfaBackend.MfaSetupVerify(data)
.then((res) => {
if (res.status === "ok") {
@ -98,20 +115,24 @@ export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail})
});
};
if (mfaProps.type === SmsMfaType) {
return <MfaSmsVerifyForm onFinish={onFinish} application={application} />;
} else if (mfaProps.type === TotpMfaType) {
return <MfaTotpVerifyForm onFinish={onFinish} mfaProps={mfaProps} />;
if (mfaType === null || mfaType === undefined || mfaProps.secret === undefined) {
return <div></div>;
}
if (mfaType === SmsMfaType || mfaType === EmailMfaType) {
return <MfaSmsVerifyForm onFinish={onFinish} application={application} method={mfaSetup} mfaProps={mfaProps} />;
} else if (mfaType === TotpMfaType) {
return <MfaTotpVerifyForm onFinish={onFinish} />;
} else {
return <div></div>;
}
}
function EnableMfaForm({user, mfaProps, onSuccess, onFail}) {
function EnableMfaForm({user, mfaType, recoveryCodes, onSuccess, onFail}) {
const [loading, setLoading] = useState(false);
const requestEnableTotp = () => {
const data = {
type: mfaProps.type,
mfaType,
...user,
};
setLoading(true);
@ -131,7 +152,7 @@ function EnableMfaForm({user, mfaProps, onSuccess, onFail}) {
<div style={{width: "400px"}}>
<p>{i18next.t("mfa:Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code")}</p>
<br />
<code style={{fontStyle: "solid"}}>{mfaProps.recoveryCodes[0]}</code>
<code style={{fontStyle: "solid"}}>{recoveryCodes[0]}</code>
<Button style={{marginTop: 24}} loading={loading} onClick={() => {
requestEnableTotp();
}} block type="primary">
@ -146,12 +167,13 @@ class MfaSetupPage extends React.Component {
super(props);
this.state = {
account: props.account,
applicationName: (props.applicationName ?? props.account?.signupApplication) ?? "",
application: this.props.application ?? null,
applicationName: props.account.signupApplication ?? "",
isAuthenticated: props.isAuthenticated ?? false,
isPromptPage: props.isPromptPage,
redirectUri: props.redirectUri,
current: props.current ?? 0,
type: props.type ?? SmsMfaType,
mfaType: props.mfaType ?? new URLSearchParams(props.location?.search)?.get("mfaType") ?? SmsMfaType,
mfaProps: null,
};
}
@ -163,7 +185,7 @@ class MfaSetupPage extends React.Component {
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.isAuthenticated === true && this.state.mfaProps === null) {
MfaBackend.MfaSetupInitiate({
type: this.state.type,
mfaType: this.state.mfaType,
...this.getUser(),
}).then((res) => {
if (res.status === "ok") {
@ -178,6 +200,10 @@ class MfaSetupPage extends React.Component {
}
getApplication() {
if (this.state.application !== null) {
return;
}
ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => {
if (application !== null) {
@ -217,25 +243,58 @@ class MfaSetupPage extends React.Component {
return null;
}
return <MfaVerifyForm
mfaProps={this.state.mfaProps}
application={this.state.application}
user={this.getUser()}
onSuccess={() => {
this.setState({
current: this.state.current + 1,
});
}}
onFail={(res) => {
Setting.showMessage("error", i18next.t("general:Failed to verify"));
}}
/>;
return (
<div>
<MfaVerifyForm
mfaType={this.state.mfaType}
application={this.state.application}
user={this.props.account}
onSuccess={() => {
this.setState({
current: this.state.current + 1,
});
}}
onFail={(res) => {
Setting.showMessage("error", i18next.t("general:Failed to verify"));
}}
/>
<Col span={24} style={{display: "flex", justifyContent: "left"}}>
{(this.state.mfaType === EmailMfaType || this.props.account.mfaEmailEnabled) ? null :
<Button type={"link"} onClick={() => {
if (this.state.isPromptPage) {
this.props.history.push(`/prompt/${this.state.application.name}?promptType=mfa&mfaType=${EmailMfaType}`);
} else {
this.props.history.push(`/mfa-authentication/setup?mfaType=${EmailMfaType}`);
}
this.setState({
mfaType: EmailMfaType,
});
}
}>{i18next.t("mfa:Use Email")}</Button>
}
{
(this.state.mfaType === SmsMfaType || this.props.account.mfaPhoneEnabled) ? null :
<Button type={"link"} onClick={() => {
if (this.state.isPromptPage) {
this.props.history.push(`/prompt/${this.state.application.name}?promptType=mfa&mfaType=${SmsMfaType}`);
} else {
this.props.history.push(`/mfa-authentication/setup?mfaType=${SmsMfaType}`);
}
this.setState({
mfaType: SmsMfaType,
});
}
}>{i18next.t("mfa:Use SMS")}</Button>
}
</Col>
</div>
);
case 2:
if (!this.state.isAuthenticated) {
return null;
}
return <EnableMfaForm user={this.getUser()} mfaProps={{type: this.state.type, ...this.state.mfaProps}}
return <EnableMfaForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes}
onSuccess={() => {
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
if (this.state.isPromptPage && this.state.redirectUri) {
@ -276,15 +335,14 @@ class MfaSetupPage extends React.Component {
</Row>
<Row>
<Col span={24}>
<Steps current={this.state.current} style={{
width: "90%",
maxWidth: "500px",
margin: "auto",
marginTop: "80px",
}} >
<Step title={i18next.t("mfa:Verify Password")} icon={<UserOutlined />} />
<Step title={i18next.t("mfa:Verify Code")} icon={<KeyOutlined />} />
<Step title={i18next.t("general:Enable")} icon={<CheckOutlined />} />
<Steps current={this.state.current}
items={[
{title: i18next.t("mfa:Verify Password"), icon: <UserOutlined />},
{title: i18next.t("mfa:Verify Code"), icon: <KeyOutlined />},
{title: i18next.t("general:Enable"), icon: <CheckOutlined />},
]}
style={{width: "90%", maxWidth: "500px", margin: "auto", marginTop: "80px",
}} >
</Steps>
</Col>
</Row>

View File

@ -20,52 +20,70 @@ import * as Setting from "../Setting";
import React from "react";
import copy from "copy-to-clipboard";
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
import {EmailMfaType} from "./MfaSetupPage";
export const MfaSmsVerifyForm = ({mfaProps, application, onFinish}) => {
const [dest, setDest] = React.useState(mfaProps?.secret ?? "");
export const mfaAuth = "mfaAuth";
export const mfaSetup = "mfaSetup";
export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method}) => {
const [dest, setDest] = React.useState(mfaProps.secret ?? "");
const [form] = Form.useForm();
const isEmail = () => {
return mfaProps.mfaType === EmailMfaType;
};
return (
<Form
form={form}
style={{width: "300px"}}
onFinish={onFinish}
initialValues={{
countryCode: mfaProps.countryCode,
}}
>
{mfaProps?.secret !== undefined ?
<div style={{marginBottom: 20}}>
{Setting.IsEmail(dest) ? i18next.t("mfa:Your email is") : i18next.t("mfa:Your phone is")} {dest}
{mfaProps.secret !== "" ?
<div style={{marginBottom: 20, textAlign: "left", gap: 8}}>
{isEmail() ? i18next.t("mfa:Your email is") : i18next.t("mfa:Your phone is")} {mfaProps.secret}
</div> :
<Input.Group compact style={{width: "300Px", marginBottom: "30px"}}>
{Setting.IsEmail(dest) ? null :
(<React.Fragment>
<p>{isEmail() ? i18next.t("mfa:Please bind your email first, the system will automatically uses the mail for multi-factor authentication") :
i18next.t("mfa:Please bind your phone first, the system automatically uses the phone for multi-factor authentication")}
</p>
<Input.Group compact style={{width: "300Px", marginBottom: "30px"}}>
{isEmail() ? null :
<Form.Item
name="countryCode"
noStyle
rules={[
{
required: false,
message: i18next.t("signup:Please select your country code!"),
},
]}
>
<CountryCodeSelect
initValue={mfaProps.countryCode}
style={{width: "30%"}}
countryCodes={application.organizationObj.countryCodes}
/>
</Form.Item>
}
<Form.Item
name="countryCode"
name="dest"
noStyle
rules={[
{
required: false,
message: i18next.t("signup:Please select your country code!"),
},
]}
rules={[{required: true, message: i18next.t("login:Please input your Email or Phone!")}]}
>
<CountryCodeSelect
style={{width: "30%"}}
countryCodes={application.organizationObj.countryCodes}
<Input
style={{width: isEmail() ? "100% " : "70%"}}
onChange={(e) => {setDest(e.target.value);}}
prefix={<UserOutlined />}
placeholder={isEmail() ? i18next.t("general:Email") : i18next.t("general:Phone")}
/>
</Form.Item>
}
<Form.Item
name="dest"
noStyle
rules={[{required: true, message: i18next.t("login:Please input your Email or Phone!")}]}
>
<Input
style={{width: Setting.IsEmail(dest) ? "100% " : "70%"}}
onChange={(e) => {setDest(e.target.value);}}
prefix={<UserOutlined />}
placeholder={i18next.t("general:Phone or email")}
/>
</Form.Item>
</Input.Group>
</Input.Group>
</React.Fragment>
)
}
<Form.Item
name="passcode"
@ -73,8 +91,8 @@ export const MfaSmsVerifyForm = ({mfaProps, application, onFinish}) => {
>
<SendCodeInput
countryCode={form.getFieldValue("countryCode")}
method={mfaProps?.id === undefined ? "mfaSetup" : "mfaAuth"}
onButtonClickArgs={[dest, Setting.IsEmail(dest) ? "email" : "phone", Setting.getApplicationName(application)]}
method={method}
onButtonClickArgs={[mfaProps.secret || dest, isEmail() ? "email" : "phone", Setting.getApplicationName(application)]}
application={application}
/>
</Form.Item>

View File

@ -28,14 +28,13 @@ import MfaSetupPage from "./MfaSetupPage";
class PromptPage extends React.Component {
constructor(props) {
super(props);
const params = new URLSearchParams(this.props.location.search);
this.state = {
classes: props,
type: props.type,
applicationName: props.applicationName ?? (props.match === undefined ? null : props.match.params.applicationName),
application: null,
user: null,
promptType: params.get("promptType"),
promptType: new URLSearchParams(this.props.location.search).get("promptType"),
};
}
@ -233,19 +232,22 @@ class PromptPage extends React.Component {
{this.renderContent(application)}
<div style={{marginTop: "50px"}}>
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button>
</div>;
</div>
</>;
}
renderPromptMfa() {
return <MfaSetupPage
applicationName={this.getApplicationObj().name}
account={this.props.account}
current={1}
isAuthenticated={true}
isPromptPage={true}
redirectUri={this.getRedirectUrl()}
/>;
return (
<MfaSetupPage
application={this.getApplicationObj()}
account={this.props.account}
current={1}
isAuthenticated={true}
isPromptPage={true}
redirectUri={this.getRedirectUrl()}
{...this.props}
/>
);
}
render() {

View File

@ -18,7 +18,7 @@ export function MfaSetupInitiate(values) {
const formData = new FormData();
formData.append("owner", values.owner);
formData.append("name", values.name);
formData.append("type", values.type);
formData.append("mfaType", values.mfaType);
return fetch(`${Setting.ServerUrl}/api/mfa/setup/initiate`, {
method: "POST",
credentials: "include",
@ -30,7 +30,7 @@ export function MfaSetupVerify(values) {
const formData = new FormData();
formData.append("owner", values.owner);
formData.append("name", values.name);
formData.append("type", values.type);
formData.append("mfaType", values.mfaType);
formData.append("passcode", values.passcode);
return fetch(`${Setting.ServerUrl}/api/mfa/setup/verify`, {
method: "POST",
@ -41,7 +41,7 @@ export function MfaSetupVerify(values) {
export function MfaSetupEnable(values) {
const formData = new FormData();
formData.append("type", values.type);
formData.append("mfaType", values.mfaType);
formData.append("owner", values.owner);
formData.append("name", values.name);
return fetch(`${Setting.ServerUrl}/api/mfa/setup/enable`, {
@ -53,7 +53,6 @@ export function MfaSetupEnable(values) {
export function DeleteMfa(values) {
const formData = new FormData();
formData.append("id", values.id);
formData.append("owner", values.owner);
formData.append("name", values.name);
return fetch(`${Setting.ServerUrl}/api/delete-mfa`, {
@ -65,7 +64,7 @@ export function DeleteMfa(values) {
export function SetPreferredMfa(values) {
const formData = new FormData();
formData.append("id", values.id);
formData.append("mfaType", values.mfaType);
formData.append("owner", values.owner);
formData.append("name", values.name);
return fetch(`${Setting.ServerUrl}/api/set-preferred-mfa`, {

View File

@ -17,13 +17,17 @@ import * as Setting from "../../Setting";
import React from "react";
export const CountryCodeSelect = (props) => {
const {onChange, style, disabled} = props;
const {onChange, style, disabled, initValue} = props;
const countryCodes = props.countryCodes ?? [];
const [value, setValue] = React.useState("");
React.useEffect(() => {
const initValue = countryCodes.length > 0 ? countryCodes[0] : "";
handleOnChange(initValue);
if (initValue !== undefined) {
setValue(initValue);
} else {
const initValue = countryCodes.length > 0 ? countryCodes[0] : "";
handleOnChange(initValue);
}
}, []);
const handleOnChange = (value) => {

View File

@ -202,6 +202,7 @@
"Delete": "Löschen",
"Description": "Beschreibung",
"Description - Tooltip": "Detaillierte Beschreibungsinformationen zur Referenz, Casdoor selbst wird es nicht verwenden",
"Disable": "Disable",
"Display name": "Anzeigename",
"Display name - Tooltip": "Ein benutzerfreundlicher, leicht lesbarer Name, der öffentlich in der Benutzeroberfläche angezeigt wird",
"Down": "Nach unten",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "Berechtigungen, die diesem Benutzer gehören",
"Phone": "Telefon",
"Phone - Tooltip": "Telefonnummer",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Pläne",
@ -316,6 +316,7 @@
"Supported country codes": "Unterstützte Ländercodes",
"Supported country codes - Tooltip": "Ländercodes, die von der Organisation unterstützt werden. Diese Codes können als Präfix ausgewählt werden, wenn SMS-Verifizierungscodes gesendet werden",
"Sure to delete": "Sicher zu löschen",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "Synchronisieren",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "Delete",
"Description": "Description",
"Description - Tooltip": "Detailed description information for reference, Casdoor itself will not use it",
"Disable": "Disable",
"Display name": "Display name",
"Display name - Tooltip": "A user-friendly, easily readable name displayed publicly in the UI",
"Down": "Down",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "Permissions owned by this user",
"Phone": "Phone",
"Phone - Tooltip": "Phone number",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Plans",
@ -316,6 +316,7 @@
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Country codes supported by the organization. These codes can be selected as a prefix when sending SMS verification codes",
"Sure to delete": "Sure to delete",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "Sync",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "Eliminar",
"Description": "Descripción",
"Description - Tooltip": "Información detallada de descripción para referencia, Casdoor en sí no la utilizará",
"Disable": "Disable",
"Display name": "Nombre de pantalla",
"Display name - Tooltip": "Un nombre fácil de usar y leer que se muestra públicamente en la interfaz de usuario",
"Down": "Abajo",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "Permisos propiedad de este usuario",
"Phone": "Teléfono",
"Phone - Tooltip": "Número de teléfono",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Planes",
@ -316,6 +316,7 @@
"Supported country codes": "Códigos de país admitidos",
"Supported country codes - Tooltip": "Códigos de país compatibles con la organización. Estos códigos se pueden seleccionar como prefijo al enviar códigos de verificación SMS",
"Sure to delete": "Seguro que eliminar",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "Sincronización",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "Supprimer",
"Description": "Description",
"Description - Tooltip": "Informations détaillées pour référence, Casdoor ne l'utilisera pas en soi",
"Disable": "Disable",
"Display name": "Nom d'affichage",
"Display name - Tooltip": "Un nom convivial et facilement lisible affiché publiquement dans l'interface utilisateur",
"Down": "En bas",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "Autorisations détenues par cet utilisateur",
"Phone": "Téléphone",
"Phone - Tooltip": "Numéro de téléphone",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Plans",
@ -316,6 +316,7 @@
"Supported country codes": "Codes de pays pris en charge",
"Supported country codes - Tooltip": "Codes de pays pris en charge par l'organisation. Ces codes peuvent être sélectionnés comme préfixe lors de l'envoi de codes de vérification SMS",
"Sure to delete": "Sûr de supprimer",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "Synchronisation",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "Hapus",
"Description": "Deskripsi",
"Description - Tooltip": "Informasi deskripsi terperinci untuk referensi, Casdoor itu sendiri tidak akan menggunakannya",
"Disable": "Disable",
"Display name": "Nama tampilan",
"Display name - Tooltip": "Sebuah nama yang mudah digunakan dan mudah dibaca yang ditampilkan secara publik di UI",
"Down": "Turun",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "Izin dimiliki oleh pengguna ini",
"Phone": "Telepon",
"Phone - Tooltip": "Nomor telepon",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Rencana",
@ -316,6 +316,7 @@
"Supported country codes": "Kode negara yang didukung",
"Supported country codes - Tooltip": "Kode negara yang didukung oleh organisasi. Kode-kode ini dapat dipilih sebagai awalan saat mengirim kode verifikasi SMS",
"Sure to delete": "Pasti untuk menghapus",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "Sinkronisasi",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "削除",
"Description": "説明",
"Description - Tooltip": "参照用の詳細な説明情報です。Casdoor自体はそれを使用しません",
"Disable": "Disable",
"Display name": "表示名",
"Display name - Tooltip": "UIで公開されている使いやすく読みやすい名前",
"Down": "ダウン",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "このユーザーが所有する権限",
"Phone": "電話",
"Phone - Tooltip": "電話番号",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "プラン",
@ -316,6 +316,7 @@
"Supported country codes": "サポートされている国コード",
"Supported country codes - Tooltip": "組織でサポートされている国コード。これらのコードは、SMS認証コードのプレフィックスとして選択できます",
"Sure to delete": "削除することが確実です",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "同期",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "삭제하기",
"Description": "설명",
"Description - Tooltip": "참고용으로 자세한 설명 정보가 제공됩니다. Casdoor 자체는 사용하지 않습니다",
"Disable": "Disable",
"Display name": "디스플레이 이름",
"Display name - Tooltip": "UI에서 공개적으로 표시되는 사용자 친화적이고 쉽게 읽을 수 있는 이름",
"Down": "아래로",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "이 사용자가 소유한 권한",
"Phone": "전화기",
"Phone - Tooltip": "전화 번호",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "플랜",
@ -316,6 +316,7 @@
"Supported country codes": "지원되는 국가 코드들",
"Supported country codes - Tooltip": "조직에서 지원하는 국가 코드입니다. 이 코드들은 SMS 인증 코드를 보낼 때 접두사로 선택할 수 있습니다",
"Sure to delete": "삭제하시겠습니까?",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "싱크",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "Excluir",
"Description": "Descrição",
"Description - Tooltip": "Informações de descrição detalhadas para referência, o Casdoor em si não irá utilizá-las",
"Disable": "Disable",
"Display name": "Nome de exibição",
"Display name - Tooltip": "Um nome amigável e facilmente legível exibido publicamente na interface do usuário",
"Down": "Descer",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "Permissões pertencentes a este usuário",
"Phone": "Telefone",
"Phone - Tooltip": "Número de telefone",
"Phone or email": "Telefone ou email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Kế hoạch",
@ -316,6 +316,7 @@
"Supported country codes": "Códigos de país suportados",
"Supported country codes - Tooltip": "Códigos de país suportados pela organização. Esses códigos podem ser selecionados como prefixo ao enviar códigos de verificação SMS",
"Sure to delete": "Tem certeza que deseja excluir",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "Sincronizar",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Segredo de vários fatores - Dica de ferramenta",
"Multi-factor secret to clipboard successfully": "Segredo de vários fatores copiado para a área de transferência com sucesso",
"Passcode": "Código de acesso",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Guarde este código de recuperação. Quando o seu dispositivo não puder fornecer um código de autenticação, você poderá redefinir a autenticação mfa usando este código de recuperação",
"Protect your account with Multi-factor authentication": "Proteja sua conta com autenticação de vários fatores",
"Recovery code": "Código de recuperação",
"SMS/Email message": "Mensagem SMS/E-mail",
"Set preferred": "Definir preferido",
"Setup": "Configuração",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Usar código de verificação SMS",
"Use a recovery code": "Usar um código de recuperação",
"Verification failed": "Verificação falhou",

View File

@ -202,6 +202,7 @@
"Delete": "Удалить",
"Description": "Описание",
"Description - Tooltip": "Подробная описательная информация для справки, Casdoor сам не будет использовать ее",
"Disable": "Disable",
"Display name": "Отображаемое имя",
"Display name - Tooltip": "Понятное для пользователя имя, легко читаемое и отображаемое публично в пользовательском интерфейсе (UI)",
"Down": "вниз",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "Разрешения, принадлежащие этому пользователю",
"Phone": "Телефон",
"Phone - Tooltip": "Номер телефона",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Планы",
@ -316,6 +316,7 @@
"Supported country codes": "Поддерживаемые коды стран",
"Supported country codes - Tooltip": "Коды стран, поддерживаемые организацией. Эти коды могут быть выбраны в качестве префикса при отправке SMS-кодов подтверждения",
"Sure to delete": "Обязательное удаление",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "Синхронизация",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "Xóa",
"Description": "Mô tả",
"Description - Tooltip": "Thông tin chi tiết mô tả cho tham khảo, Casdoor chính nó sẽ không sử dụng nó",
"Disable": "Disable",
"Display name": "Tên hiển thị",
"Display name - Tooltip": "Một tên dễ sử dụng, dễ đọc được hiển thị công khai trên giao diện người dùng",
"Down": "Xuống",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "Quyền sở hữu của người dùng này",
"Phone": "Điện thoại",
"Phone - Tooltip": "Số điện thoại",
"Phone or email": "Phone or email",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Kế hoạch",
@ -316,6 +316,7 @@
"Supported country codes": "Các mã quốc gia được hỗ trợ",
"Supported country codes - Tooltip": "Mã quốc gia được hỗ trợ bởi tổ chức. Những mã này có thể được chọn làm tiền tố khi gửi mã xác nhận SMS",
"Sure to delete": "Chắc chắn muốn xóa",
"Sure to disable": "Sure to disable",
"Sure to remove": "Sure to remove",
"Swagger": "Swagger",
"Sync": "Đồng bộ hoá",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code",
"SMS/Email message": "SMS/Email message",
"Set preferred": "Set preferred",
"Setup": "Setup",
"Use Email": "Use Email",
"Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code",
"Use a recovery code": "Use a recovery code",
"Verification failed": "Verification failed",

View File

@ -202,6 +202,7 @@
"Delete": "删除",
"Description": "描述信息",
"Description - Tooltip": "供人参考的详细描述信息Casdoor平台本身不会使用",
"Disable": "关闭",
"Display name": "显示名称",
"Display name - Tooltip": "在界面里公开显示的、易读的名称",
"Down": "下移",
@ -272,7 +273,6 @@
"Permissions - Tooltip": "该用户所拥有的权限",
"Phone": "手机号",
"Phone - Tooltip": "手机号",
"Phone or email": "手机或邮箱",
"Plan": "计划",
"Plan - Tooltip": "订阅里的计划",
"Plans": "计划",
@ -316,6 +316,7 @@
"Supported country codes": "支持的国家代码",
"Supported country codes - Tooltip": "该组织所支持的国家代码,发送短信验证码时可以选择这些国家的代码前缀",
"Sure to delete": "确定删除",
"Sure to disable": "确认关闭",
"Sure to remove": "确定移除",
"Swagger": "API文档",
"Sync": "同步",
@ -437,12 +438,15 @@
"Multi-factor secret - Tooltip": "多因素密钥 - Tooltip",
"Multi-factor secret to clipboard successfully": "多因素密钥已复制到剪贴板",
"Passcode": "认证码",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "请先绑定邮箱,之后会自动使用该邮箱作为多因素认证的方式",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "请先绑定手机号,之后会自动使用该手机号作为多因素认证的方式",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "请保存此恢复代码。一旦您的设备无法提供身份验证码,您可以通过此恢复码重置多因素认证",
"Protect your account with Multi-factor authentication": "通过多因素认证保护您的帐户",
"Recovery code": "恢复码",
"SMS/Email message": "短信或邮件认证",
"Set preferred": "设为首选",
"Setup": "设置",
"Use Email": "使用电子邮件",
"Use SMS": "使用短信",
"Use SMS verification code": "使用手机或电子邮件发送验证码认证",
"Use a recovery code": "使用恢复代码",
"Verification failed": "验证失败",