Skip to content

accounts-2fa

此软件包允许你为用户提供一种在其账户上启用 2FA 的方法,使用身份验证器应用(例如 Google Authenticator 或 1Password)。当用户登录你的应用时,他们将能够生成新的二维码并在他们喜欢的应用上读取此代码。之后,他们将开始接收他们的代码。然后,他们可以在你的应用上完成启用 2FA,并且每次他们尝试登录你的应用时,你都可以将他们重定向到一个可以提供他们从身份验证器收到的代码的地方。

¥This package allows you to provide a way for your users to enable 2FA on their accounts, using an authenticator app such as Google Authenticator, or 1Password. When the user is logged in on your app, they will be able to generate a new QR code and read this code on the app they prefer. After that, they'll start receiving their codes. Then, they can finish enabling 2FA on your app, and every time they try to log in to your app, you can redirect them to a place where they can provide a code they received from the authenticator.

为了提供与所有其他实现 TOTP 的 Authenticator 应用和服务完全兼容的代码,此包使用 node-2fa,它在 notp 之上工作,实现了基于 HOTP (RFC 4226) 的 TOTP (RFC 6238)(Authenticator 标准)。

¥To provide codes that are exactly compatible with all other Authenticator apps and services that implements TOTP, this package uses node-2fa which works on top of notp, that implements TOTP (RFC 6238) (the Authenticator standard), which is based on HOTP (RFC 4226).

此软件包旨在与 accounts-passwordaccounts-passwordless 一起使用,因此如果你的项目中没有其中任何一个,则需要添加其中一个。将来,我们希望将此包与其他登录方法(我们的 oauth 方法(Google、GitHub 等))一起使用。

¥This package is meant to be used with accounts-password or accounts-passwordless, so if you don't have either of those in your project, you'll need to add one of them. In the future, we want to enable the use of this package with other login methods, our oauth methods (Google, GitHub, etc...).

2FA 激活流程

¥2FA Activation Flow

为了启用 2FA,第一步是生成二维码,以便用户可以在身份验证器应用中扫描它并开始接收代码。

¥The first step, in order to enable 2FA, is to generate a QR code so that the user can scan it in an authenticator app and start receiving codes.

Accounts.generate2faActivationQrCode

Client only

Summary:

Generates a svg QR code and save secret on user

Arguments:

Source code
NameTypeDescriptionRequired
appNameString

It's the name of your app that will show up when the user scans the QR code.

Yes
callbackfunction

Called with a single Error argument on failure. Or, on success, called with an object containing the QR code in SVG format (svg), the QR secret (secret), and the URI so the user can manually activate the 2FA without reading the QR code (uri).

Yes
js
import { Accounts } from "meteor/accounts-base";


const result = Accounts.generate2faActivationQrCode();
  "appName",
() => {},
);

接收 appName,这是你的应用的名称,当用户扫描二维码时会显示出来。此外,成功时会调用一个回调,其中包含 SVG 格式的二维码、二维码密钥和可用于在身份验证器应用中激活 2FA 的 URI,失败时会调用单个 Error 参数。

¥Receives an appName which is the name of your app that will show up when the user scans the QR code. Also, a callback called, on success, with a QR code in SVG format, a QR secret, and the URI that can be used to activate the 2FA in an authenticator app, or a single Error argument on failure.

成功后,此函数还将向已登录用户的服务对象添加一个包含 QR 密钥的对象:

¥On success, this function will also add an object to the logged user's services object containing the QR secret:

js
services: {
  ...
  twoFactorAuthentication: {
    secret: "***"
  }
}

下面是如何调用此函数的示例:

¥Here it's an example on how to call this function:

js
import { Buffer } from "buffer";
import { Accounts } from 'meteor/accounts-base';


// component
const [qrCode, setQrCode] = useState(null);


<button
  onClick={() => {
    Accounts.generate2faActivationQrCode("My app name", (err, result) => {
      if (err) {console.error("...", err);return;}
      const { svg, secret, uri } = result;
      /*
        the svg can be converted to base64, then be used like:
         <img
            width="200"
            src={`data:image/svg+xml;base64,${qrCode}`}
         />
      */
      setQrCode(Buffer.from(svg).toString('base64'));
    })
  }}
>
  Generate a new code
</button>

此方法可能会失败并引发以下错误:

¥This method can fail throwing the following error:

  • “2FA 已激活。如果在用户已启用 2FA 的情况下尝试生成激活,则需要先禁用 2FA,然后尝试生成新的激活码 [2fa-activated]”。

    ¥"The 2FA is activated. You need to disable the 2FA first before trying to generate a new activation code [2fa-activated]" if trying to generate an activation when the user already have 2FA enabled.

此时,2FA 尚未激活。现在用户可以访问其身份验证器应用生成的代码,你可以调用函数 Accounts.enableUser2fa

¥At this point, the 2FA won't be activated just yet. Now that the user has access to the codes generated by their authenticator app, you can call the function Accounts.enableUser2fa:

Accounts.enableUser2fa

Client only

Summary:

Enable the user 2FA

Arguments:

Source code
NameTypeDescriptionRequired
codeString

Code received from the authenticator app.

Yes
callbackfunction

Optional callback. Called with no arguments on success, or with a single Error argument on failure.

No
js
import { Accounts } from "meteor/accounts-base";


const result = Accounts.enableUser2fa();
  "code",
() => {}, // this param is optional
);

它应该使用用户读取二维码后从身份验证器应用收到的代码来调用。如果失败,将使用单个 Error 参数调用回调。如果提供的代码正确,type 将被添加到用户的 twoFactorAuthentication 对象中,现在 2FA 被视为已启用:

¥It should be called with a code that the users will receive from the authenticator app once they read the QR code. The callback is called with a single Error argument on failure. If the code provided is correct, a type will be added to the user's twoFactorAuthentication object and now 2FA is considered enabled:

js
services: {
  ...
  twoFactorAuthentication: {
    type: "otp",
    secret: "***",
  }
}

要验证用户是否启用了 2FA,你可以调用函数 Accounts.has2faEnabled

¥To verify whether or not a user has 2FA enabled, you can call the function Accounts.has2faEnabled:

Accounts.has2faEnabled

Client only

Summary:

Verify if the logged user has 2FA enabled

Arguments:

Source code
NameTypeDescriptionRequired
callbackfunction

Called with a boolean on success that indicates whether the user has or not 2FA enabled, or with a single Error argument on failure.

No
js
import { Accounts } from "meteor/accounts-base";


const result = Accounts.has2faEnabled();
  () => {}
);

用户登录时必须调用此函数。

¥This function must be called when the user is logged in.

禁用 2FA

¥Disabling 2FA

要为用户禁用 2FA,请使用此方法:

¥To disable 2FA for a user use this method:

Accounts.disableUser2fa

Client only

Summary:

Disable user 2FA

Arguments:

Source code
NameTypeDescriptionRequired
callbackfunction

Optional callback. Called with no arguments on success, or with a single Error argument on failure.

No
js
import { Accounts } from "meteor/accounts-base";


const result = Accounts.disableUser2fa();
  () => {}
);

要调用此功能,用户必须已经登录。

¥To call this function the user must be already logged in.

使用 2FA 登录

¥Log in with 2FA

现在你有办法允许你的用户在其账户上启用 2FA,你可以基于此创建登录流程。

¥Now that you have a way to allow your users to enable 2FA on their accounts, you can create a login flow based on that.

正如本指南开头所述,此包目前与其他两个包一起使用:accounts-passwordaccounts-passwordless。下面有关于如何将此包与它们一起使用的说明。

¥As said at the beginning of this guide, this package is currently working with two other packages: accounts-password and accounts-passwordless. Below there is an explanation on how to use this package with them.

使用 accounts-password

¥Working with accounts-password

当调用函数 Meteor.loginWithPassword 时,如果为用户启用了 2FA,则会向回调返回错误,因此你可以将用户重定向到他们可以提供代码的地方。

¥When calling the function Meteor.loginWithPassword, if the 2FA is enabled for the user, an error will be returned to the callback, so you can redirect the user to a place where they can provide a code.

例如:

¥As an example:

js
<button
  onClick={() => {
    Meteor.loginWithPassword(username, password, (error) => {
      if (error) {
        if (error.error === "no-2fa-code") {
          // send user to a page or show a component
          // where they can provide a 2FA code
          setShouldAskCode(true);
          return;
        }
        console.error("Error trying to log in (user without 2fa)", error);
      }
    });
  }}
>
  Login
</button>

如果未启用 2FA,则用户将正常登录。

¥If the 2FA is not enabled, the user will be logged in normally.

你现在需要调用以允许用户登录的函数是 Meteor.loginWithPasswordAnd2faCode

¥The function you will need to call now to allow the user to login is Meteor.loginWithPasswordAnd2faCode:

Meteor.loginWithPasswordAnd2faCode

Client only

Summary:

Log the user in with a password and token.

Arguments:

Source code
NameTypeDescriptionRequired
selectorObject or String

Either a string interpreted as a username or an email; or an object with a single key: email, username or id. Username or email match in a case insensitive manner.

Yes
passwordString

The user's password.

Yes
tokenString

Token provide by the user's authenticator app.

Yes
callbackfunction

Optional callback. Called with no arguments on success, or with a single Error argument on failure.

No
js
import { Meteor } from "meteor/meteor";


const result = Meteor.loginWithPasswordAnd2faCode();
  selector,
"password",

"token",

() => {}, // this param is optional
);

现在,你将能够从用户那里收到代码,并且此功能将验证该代码是否有效。如果是,用户将登录。

¥Now you will be able to receive a code from the user and this function will verify if the code is valid. If it is, the user will be logged in.

因此,此函数的调用应如下所示:

¥So the call of this function should look something like this:

js
<button
  onClick={() => {
    Meteor.loginWithPasswordAnd2faCode(username, password, code, (error) => {
      if (error) {
        console.error("Error trying to log in (user with 2fa)", error);
      }
    });
  }}
>
  Validate and log in
</button>

此方法可能会失败并引发以下错误之一:

¥This method can fail throwing one of the following errors:

  • 如果未提供 2FA 代码,则为 "必须告知 2FA 代码 [no-2fa-code]"。

    ¥"2FA code must be informed [no-2fa-code]" if a 2FA code was not provided.

  • 如果提供的 2FA 代码无效,则为 "无效的 2FA 代码 [invalid-2fa-code]"。

    ¥"Invalid 2FA code [invalid-2fa-code]" if the provided 2FA code is invalid.

使用 accounts-passwordless

¥Working with accounts-passwordless

按照上一个包的相同逻辑,如果启用了 2FA,则会向函数 Meteor.passwordlessLoginWithToken 的回调返回错误,然后你可以将用户重定向到他们可以提供代码的地方。

¥Following the same logic from the previous package, if the 2FA is enabled, an error will be returned to the callback of the function Meteor.passwordlessLoginWithToken, then you can redirect the user to a place where they can provide a code.

这是一个例子:

¥Here is an example:

js
<button
  onClick={() => {
    // logging in just with token
    Meteor.passwordlessLoginWithToken(email, token, (error) => {
      if (error) {
        if (error.error === "no-2fa-code") {
          // send user to a page or show a component
          // where they can provide a 2FA code
          setShouldAskCode(true);
          return;
        }
        console.error("Error verifying token", error);
      }
    });
  }}
>
  Validate token
</button>

现在你可以调用函数 Meteor.passwordlessLoginWithTokenAnd2faCode,该函数允许你提供选择器、令牌和 2FA 代码:

¥Now you can call the function Meteor.passwordlessLoginWithTokenAnd2faCode that will allow you to provide a selector, token, and 2FA code:

Meteor.passwordlessLoginWithTokenAnd2faCode

Client only

Summary:

Log the user in with a one time token.

Arguments:

Source code
NameTypeDescriptionRequired
selectorObject or String

Username, email or custom selector to identify the user.

Yes
tokenString

one time token generated by the server

Yes
codeString

generated by the user's authenticator app

Yes
callbackfunction

Optional callback. Called with no arguments on success, or with a single Error argument on failure.

No
js
import { Meteor } from "meteor/meteor";


const result = Meteor.passwordlessLoginWithTokenAnd2faCode();
  selector,
"token",

"code",

() => {}, // this param is optional
);

此方法可能会失败并引发以下错误之一:

¥This method can fail throwing one of the following errors:

  • 如果未提供 2FA 代码,则为 "必须告知 2FA 代码 [no-2fa-code]"。

    ¥"2FA code must be informed [no-2fa-code]" if a 2FA code was not provided.

  • 如果提供的 2FA 代码无效,则为 "无效的 2FA 代码 [invalid-2fa-code]"。

    ¥"Invalid 2FA code [invalid-2fa-code]" if the provided 2FA code is invalid.

将身份验证包与 accounts-2fa 集成

¥Integrating an Authentication Package with accounts-2fa

要将此包与任何其他现有登录方法集成,必须执行以下两个步骤:

¥To integrate this package with any other existing Login method, it's necessary following two steps:

1 - 对于客户端,从当前登录方法创建一个新方法。例如,从方法 Meteor.loginWithPassword 中我们创建了一个名为 Meteor.loginWithPasswordAnd2faCode 的新方法,它们之间的唯一区别是最新的方法接收一个附加参数,即 2FA 代码,但我们在服务器端调用相同的函数。

¥1 - For the client, create a new method from your current login method. So for example, from the method Meteor.loginWithPassword we created a new one called Meteor.loginWithPasswordAnd2faCode, and the only difference between them is that the latest one receives one additional parameter, the 2FA code, but we call the same function on the server side.

2 - 对于服务器,在将用户登录的函数内部,验证函数 Accounts._check2faEnabled 是否存在,如果是,则调用它并提供你想要检查 2FA 是否启用的用户对象,如果这两个语句中的任何一个为假,则继续登录流程。仅当将包 accounts-2fa 添加到项目时,此功能才存在。

¥2 - For the server, inside the function that will log the user in, you verify if the function Accounts._check2faEnabled exists, and if yes, you call it providing the user object you want to check if the 2FA is enabled, and if either of these statements are false, you proceed with the login flow. This function exists only when the package accounts-2fa is added to the project.

如果两个语句都为真,并且登录验证成功,则验证是否提供了代码:如果不是,则抛出错误;如果提供了,请通过调用函数 Accounts._isTokenValid 来验证代码是否有效。如果 Accounts._isTokenValid 返回 false,则抛出错误。

¥If both statements are true, and the login validation succeeds, you verify if a code was provided: if not, throw an error; if it was provided, verify if the code is valid by calling the function Accounts._isTokenValid. if Accounts._isTokenValid returns false, throw an error.

这是一个例子:

¥Here it's an example:

js
const result = validateLogin();
if (!result.error && Accounts._check2faEnabled?.(user)) {
  if (!code) {
    Accounts._handleError("2FA code must be informed.");
  }
  if (
    !Accounts._isTokenValid(user.services.twoFactorAuthentication.secret, code)
  ) {
    Accounts._handleError("Invalid 2FA code.");
  }
}

return result;