mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Changes requested on code review
- improving documentation
This commit is contained in:
@@ -3,25 +3,138 @@ title: accounts-2fa
|
||||
description: Documentation of Meteor's `accounts-2fa` package.
|
||||
---
|
||||
|
||||
The package `accounts-2fa` allows you to easily integrate 2FA with the OTP technology on your login flow.
|
||||
The package allows you to easily integrate 2FA with the OTP technology on your login flow. It uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), which implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)) to provide codes that are exactly compatible with all other Authenticator apps and services that use them.
|
||||
|
||||
> This package is meant to be used with `accounts-password`, so if you still didn't add `account-password` to your project, you'll need to add it too. In the future we want to enable the use of this app with other login methods, like `accounts-passwordless` or our oauth methods (Google, GitHub, etc...).
|
||||
|
||||
<h3 id="activating-2fa">Activating 2FA</h3>
|
||||
|
||||
The first step to use 2FA is to generate a QR code so the user can read it on an authenticator app and start to receiving codes.
|
||||
|
||||
{% apibox "Accounts.generate2faActivationQrCode" "module":"accounts-base" %}
|
||||
|
||||
Receive an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called with a QR code in SVG format on success or a single `Error` argument
|
||||
on failure. Both parameters are optional.
|
||||
|
||||
On success, this function will also add an object to the logged user containing the QR secret:
|
||||
|
||||
```js
|
||||
twoFactorAuthetication: {
|
||||
secret: "***"
|
||||
}
|
||||
```
|
||||
|
||||
Here it's an example on how to call this function:
|
||||
|
||||
```js
|
||||
import { Buffer } from "buffer";
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
--
|
||||
|
||||
const [qrCode, setQrCode] = useState(null);
|
||||
|
||||
--
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
Accounts.generate2faActivationQrCode((err, svg) => {
|
||||
if (err) {console.error("...", err);return;}
|
||||
/*
|
||||
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>
|
||||
```
|
||||
|
||||
|
||||
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`:
|
||||
|
||||
{% apibox "Accounts.enableUser2fa" "module":"accounts-base" %}
|
||||
Once the user has the codes, now the 2FA can enable by calling this function with a code.
|
||||
|
||||
Called with a code the user will receive from the authenticator app once they read the QR code. This function throws an error on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object, and now the 2FA will be considered enabled:
|
||||
|
||||
```js
|
||||
twoFactorAuthetication: {
|
||||
type: "otp",
|
||||
secret: "***",
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="log-in-with-2fa">Log in with 2FA</h3>
|
||||
|
||||
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.
|
||||
|
||||
To verify if a user has or not 2FA enabled you can call the function `Accounts.has2FAEnabled`:
|
||||
|
||||
{% apibox "Accounts.has2faEnabled" "module":"accounts-base" %}
|
||||
|
||||
With this function, you can verify if the has or not 2FA enabled, and based on this information, you can directly log the user in the 2FA is not enabled, or redirect the user to a place where they can provide a code, in case they do have 2FA enabled.
|
||||
|
||||
A way of using it would be:
|
||||
|
||||
```js
|
||||
<button
|
||||
onClick={() => {
|
||||
Accounts.has2FAEnabled(username, (err, isEnabled) => {
|
||||
if (err) {
|
||||
console.error("Error verifying if user has 2fa enabled", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEnabled) {
|
||||
// send user to a page or show a component
|
||||
// where they can provide a 2FA code
|
||||
setShouldAskCode(true);
|
||||
return;
|
||||
}
|
||||
// Normal login when they don't have 2FA enabled.
|
||||
Meteor.loginWithPassword(username, password, error => {
|
||||
if (error) {
|
||||
console.error("Error trying to log in (user without 2fa)", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}>
|
||||
Login
|
||||
</button>
|
||||
```
|
||||
|
||||
If the user has 2FA enabled, and you try to use the function `Meteor.loginWithPassword`, the login will fail, as the user should provide a token to access the app.
|
||||
|
||||
The function you will need to call now to allow the user to log in is `Meteor.loginWithPasswordAnd2faToken`:
|
||||
|
||||
{% apibox "Meteor.loginWithPasswordAnd2faToken" %}
|
||||
|
||||
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.loginWithPasswordAnd2faToken(username, password, code,error => {
|
||||
if (error) {
|
||||
console.error("Error trying to log in (user with 2fa)", error);
|
||||
}
|
||||
})}
|
||||
}>
|
||||
Validate and log in
|
||||
</button>
|
||||
```
|
||||
|
||||
<h3 id="disabling-2fa">Disabling 2FA</h3>
|
||||
|
||||
To disable 2FA you call simply use this function:
|
||||
|
||||
{% apibox "Accounts.disableUser2fa" "module":"accounts-base" %}
|
||||
Use this function to give the users the option of disabling the 2FA.
|
||||
|
||||
{% apibox "Accounts.has2FAEnabled" "module":"accounts-base" %}
|
||||
Use this function to verify if the user has 2FA enabled.
|
||||
|
||||
<h3 id="log-in-with-code">Log in with code</h3>
|
||||
|
||||
Once this package is added, the method `Meteor.loginWithPassword` starts to accept on additional parameter, which is the token: `Meteor.loginWithPassword(selector, password, token, [callback])`.
|
||||
|
||||
If the user has 2FA enabled, they only will be able to log in if they provide a valid code.
|
||||
|
||||
You can see examples on how to use this package on it's [read.me](https://github.com/meteor/meteor/tree/feature/accounts-2fa-package/packages/accounts-2fa).
|
||||
To call this function the user must be already logged in.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Accounts } from "meteor/accounts-base";
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
// Used in the various functions below to handle errors consistently
|
||||
const reportError = (error, callback) => {
|
||||
@@ -13,35 +13,35 @@ const reportError = (error, callback) => {
|
||||
* @summary Verify if the user has 2FA enabled
|
||||
* @locus Client
|
||||
* @param {Object|String} selector Username, email or custom selector to identify the user.
|
||||
* @param {Function} [callback] Optional callback. Called with a boolean on success that indicates whether the user has
|
||||
* @param {Function} [callback] Called with a boolean on success that indicates whether the user has
|
||||
* or not 2FA enabled, or with a single `Error` argument on failure.
|
||||
*/
|
||||
Accounts.has2faEnabled = (selector, callback) => {
|
||||
Accounts.connection.call(
|
||||
'has2faEnabled',
|
||||
selector,
|
||||
callback,
|
||||
);
|
||||
Accounts.connection.call('has2faEnabled', selector, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Generates a svg QR code and save secret on user
|
||||
* @locus Client
|
||||
* @param {String} appName Optional. It's the name of your app that will show up when the user scans the QR code.
|
||||
* @param {Function} [callback] Optional callback.
|
||||
* Called with no arguments on success, or with a single `Error` argument
|
||||
* @param {Function} [callback]
|
||||
* Called with a QR code in SVG format on success, or with a single `Error` argument
|
||||
* on failure.
|
||||
*/
|
||||
Accounts.generate2faActivationQrCode = (appName, callback) => {
|
||||
let cb = callback;
|
||||
if (typeof appName === "function") {
|
||||
if (typeof appName === 'function') {
|
||||
cb = appName;
|
||||
}
|
||||
Accounts.connection.call(
|
||||
'generate2faActivationQrCode',
|
||||
appName,
|
||||
cb,
|
||||
);
|
||||
|
||||
if (!cb) {
|
||||
throw new Meteor.Error(
|
||||
500,
|
||||
'A callback is necessary when calling the function generate2faActivationQrCode so a QR code can be provided'
|
||||
);
|
||||
}
|
||||
|
||||
Accounts.connection.call('generate2faActivationQrCode', appName, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -54,13 +54,12 @@ Accounts.generate2faActivationQrCode = (appName, callback) => {
|
||||
*/
|
||||
Accounts.enableUser2fa = (code, callback) => {
|
||||
if (!code) {
|
||||
return reportError(new Meteor.Error(400, 'Must provide a code to validate'), callback);
|
||||
return reportError(
|
||||
new Meteor.Error(400, 'Must provide a code to validate'),
|
||||
callback
|
||||
);
|
||||
}
|
||||
Accounts.connection.call(
|
||||
'enableUser2fa',
|
||||
code,
|
||||
callback,
|
||||
);
|
||||
Accounts.connection.call('enableUser2fa', code, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -71,8 +70,5 @@ Accounts.enableUser2fa = (code, callback) => {
|
||||
* on failure.
|
||||
*/
|
||||
Accounts.disableUser2fa = callback => {
|
||||
Accounts.connection.call(
|
||||
'disableUser2fa',
|
||||
callback,
|
||||
);
|
||||
Accounts.connection.call('disableUser2fa', callback);
|
||||
};
|
||||
|
||||
@@ -1,172 +1,9 @@
|
||||
#accounts-2fa
|
||||
# accounts-2fa
|
||||
|
||||
---
|
||||
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/accounts-2fa)
|
||||
| [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/accounts-2fa)
|
||||
***
|
||||
|
||||
Easy 2-Factor Integration For Meteor Apps
|
||||
|
||||
This package uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), which implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)) to provide codes that are exactly compatible with all other Authenticator apps and services that use them.
|
||||
|
||||
|
||||
#Using it
|
||||
|
||||
```shell
|
||||
meteor add accounts-2fa
|
||||
```
|
||||
|
||||
When the package is added, the meteor `Meteor.loginWithPassword()` starts to accept 3 parameters: `Meteor.loginWithPassword(selector, password, token)`. The token is required when the user has 2FA enabled. Examples:
|
||||
|
||||
**Generating a new QR code**
|
||||
```js
|
||||
import { Buffer } from "buffer";
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
--
|
||||
|
||||
const [qrCode, setQrCode] = useState(null);
|
||||
|
||||
--
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
Accounts.generate2faActivationQrCode((err, svg) => {
|
||||
if (err) {console.error("...", err);return;}
|
||||
/*
|
||||
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>
|
||||
```
|
||||
|
||||
**Enabling 2FA**
|
||||
```js
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
--
|
||||
|
||||
const [code, setCode] = useState(null);
|
||||
|
||||
--
|
||||
|
||||
const handleValidateCodeFromQr = () => {
|
||||
try {
|
||||
Accounts.enableUser2fa(code);
|
||||
console.log("2FA enabled");
|
||||
} catch (err) {
|
||||
console.error('Error verifying code from qr', err);
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
|
||||
<div>
|
||||
<img
|
||||
alt="qr code"
|
||||
width="200"
|
||||
src={`data:image/svg+xml;base64,${qrCode}`}
|
||||
/>
|
||||
<input onChange={({target: {value}}) => setCode(value)}/>
|
||||
<button onClick={handleValidateCodeFromQr}>validate</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Disabling 2FA**
|
||||
```js
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
---
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
Accounts.disableUser2fa()
|
||||
}}
|
||||
>
|
||||
Disable 2FA
|
||||
</button>
|
||||
```
|
||||
|
||||
**Login**
|
||||
|
||||
```js
|
||||
// Verify with the user has 2FA enabled. If no, performe normal login.
|
||||
<button
|
||||
onClick={() => {
|
||||
Accounts.has2FAEnabled(username, (err, isEnabled) => {
|
||||
if (err) {
|
||||
console.error("Error verifying if user has 2fa enabled", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEnabled) {
|
||||
// send user to a page or show a component
|
||||
// where they can provide a 2FA code
|
||||
setShouldAskCode(true);
|
||||
return;
|
||||
}
|
||||
// Normal login when they don't have 2FA enabled.
|
||||
Meteor.loginWithPassword(username, password, error => {
|
||||
if (error) {
|
||||
console.error("Error trying to log in (user without 2fa)", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}>
|
||||
Login
|
||||
</button>
|
||||
|
||||
// If 2FA is enabled, inform a token, with username and password.
|
||||
<button onClick={() => {
|
||||
Meteor.loginWithPassword(username, password, code,error => {
|
||||
if (error) {
|
||||
console.error("Error trying to log in (user with 2fa)", error);
|
||||
}
|
||||
})}
|
||||
}>
|
||||
Validate
|
||||
</button>
|
||||
```
|
||||
|
||||
#API
|
||||
|
||||
####Accounts.generate2faActivationQrCode({String} appName, {Function} [callback])
|
||||
|
||||
Receive an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback that's called with no arguments on success, or with a single `Error` argument
|
||||
on failure. Both parameters are optional.
|
||||
|
||||
On success, this function will add an object to the logged user containing the QR secret:
|
||||
|
||||
```js
|
||||
twoFactorAuthetication: {
|
||||
secret: "***"
|
||||
}
|
||||
```
|
||||
|
||||
> Avoid using spaces in the `appName`. Today, there's an open issue on `node-2fa` with token invalidation when there's a space on these variables: [node-2fa/issues/6](https://github.com/jeremyscalpello/node-2fa/issues/6).
|
||||
|
||||
####Accounts.enableUser2fa({String} code)
|
||||
|
||||
Called with a code the user will receive from the authenticator app once they read the QR code. This function throws an error on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object:
|
||||
|
||||
```js
|
||||
twoFactorAuthetication: {
|
||||
type: "otp",
|
||||
secret: "***",
|
||||
}
|
||||
```
|
||||
|
||||
#### Accounts.disableUser2fa()
|
||||
|
||||
Called with no arguments. Remove the object `twoFactorAuthentication` from the user. Throws an error on failure.
|
||||
|
||||
|
||||
#### Accounts.has2FAEnabled({String} username, {Function} [callback])
|
||||
|
||||
Called with two arguments: Username, and a callback function. The `username` is the user you want to verify if the 2FA is enabled. The callback is called with a boolean on success that indicates whether the user has or not the 2FA enabled, or with a single `Error` argument on failure.
|
||||
A package to implement 2FA in login flows that use `Meteor.loginWithPassword`. See
|
||||
the [project page](https://docs.meteor.com/packages/accounts-2fa.html) on Meteor Accounts for more
|
||||
details.
|
||||
|
||||
Reference in New Issue
Block a user