Skip to content

Commit fd8234f

Browse files
author
EC2 Default User
committed
Add pre-release @meteorrn/ndev-mfa companion package
1 parent c3531ca commit fd8234f

File tree

6 files changed

+164
-2
lines changed

6 files changed

+164
-2
lines changed

.npmignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
example
1+
examples
2+
3+
companion-packages
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
**This is a pre-release package, it is not yet ready for production**
2+
3+
# ndev:mfa for MeteorRN
4+
5+
Currently supporting U2F only. This package exposes the following client methods for MFA.
6+
- useU2FAuthorizationCode
7+
- finishLogin
8+
- loginWithMFA
9+
- login
10+
11+
Here's a simple login flow:
12+
13+
````
14+
import MFA from '@meteorrn/ndev-mfa';
15+
16+
MFA.login(username, password).then(r => {
17+
if(r.method === null) {
18+
// Login Complete
19+
}
20+
else {
21+
let code = await collectTheCodeSomehow();
22+
MFA.finishLogin(r.finishLoginParams, MFA.useU2FAuthorizationCode(code)).then(() => {
23+
// Login Complete
24+
}).catch(err => {
25+
// Error (Invalid Code?)
26+
});
27+
}
28+
}).catch(err => {
29+
// Error (Incorrect Password? Invalid Account?)
30+
});
31+
32+
````
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import Meteor, { Accounts } from '@meteorrn/core';
2+
3+
import {authorizeActionChallengeHandler, authorizeActionCompletionHandler, resetPasswordCheckMFARequired, registrationChallengeHandlerTOTP, registrationCompletionHandlerTOTP, resetPasswordChallengeHandler, registrationChallengeHandlerU2F, registerCompletionHandlerU2F, loginChallengeHandler, loginCompletionHandler } from './method-names';
4+
5+
let useU2FAuthorizationCode = function (code) {
6+
if(typeof(code) !== "string" || code.length !== 6) {
7+
throw new Error("Invalid Code");
8+
}
9+
10+
return {U2FAuthorizationCode:code};
11+
};
12+
13+
let assembleChallengeCompletionArguments = async function (finishLoginParams, code) {
14+
let {res} = finishLoginParams;
15+
let methodArguments = [];
16+
17+
if(res.method === "u2f") {
18+
let assertion;
19+
if(code && code.U2FAuthorizationCode) {
20+
/*
21+
We require that the MFA.useU2FAuthorizationCode method is used
22+
even though we just pull the code out to make sure the code isn't
23+
actually an OTP due to a coding error.
24+
*/
25+
let {challengeId, challengeSecret} = finishLoginParams.res;
26+
assertion = {challengeId, challengeSecret, ...code};
27+
}
28+
else {
29+
assertion = await solveU2FChallenge(res);
30+
}
31+
methodArguments.push(assertion);
32+
}
33+
34+
if(res.method === "otp" || res.method === "totp") {
35+
if(!code) {
36+
throw new Meteor.Error("otp-required", "An OTP is required");
37+
}
38+
39+
methodArguments.push({...res, code});
40+
}
41+
42+
return methodArguments;
43+
};
44+
45+
let finishLogin = (finishLoginParams, code) => new Promise(async (resolve, reject) => {
46+
let methodName = loginCompletionHandler();
47+
let methodArguments = await assembleChallengeCompletionArguments(finishLoginParams, code);
48+
49+
Meteor._startLoggingIn();
50+
Meteor.call(
51+
methodName,
52+
methodArguments,
53+
(err, result) => {
54+
this._endLoggingIn();
55+
this._handleLoginCallback(err, result);
56+
57+
if(err) {
58+
reject(err);
59+
}
60+
else {
61+
resolve();
62+
}
63+
},
64+
);
65+
});
66+
67+
let loginWithMFA = (username, password) => new Promise((resolve, reject) => {
68+
Meteor.call(loginChallengeHandler(), username, Accounts._hashPassword(password), async (err, res) => {
69+
if(err) {
70+
return reject(err);
71+
}
72+
73+
let finishLoginParams = {res, _type:"login"};
74+
let doesSupportU2FLogin = false;
75+
76+
resolve({supportsU2FLogin:doesSupportU2FLogin, method:res.method, finishLoginParams, finishParams:finishLoginParams});
77+
});
78+
});
79+
80+
let login = (username, password) => new Promise((resolve, reject) => {
81+
Meteor.loginWithPassword(username, password, err => {
82+
if(err) {
83+
if(err.error === "mfa-required") {
84+
loginWithMFA(username, password).then(resolve).catch(reject);
85+
}
86+
else {
87+
reject(err);
88+
}
89+
}
90+
else {
91+
resolve({method:null});
92+
}
93+
});
94+
});
95+
96+
export default {
97+
useU2FAuthorizationCode,
98+
finishLogin,
99+
loginWithMFA,
100+
login,
101+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export let registrationChallengeHandlerU2F = () => "registrationChallengeHandlerU2F";
2+
export let registerCompletionHandlerU2F = () => "registerCompletionHandlerU2F";
3+
export let loginChallengeHandler = () => "loginChallengeHandler";
4+
export let loginCompletionHandlerU2F = () => "loginCompletionHandlerU2F";
5+
export let loginCompletionHandlerOTP = () => "loginCompletionHandlerOTP";
6+
export let resetPasswordChallengeHandler = () => "resetPasswordChallengeHandler";
7+
export let checkResetPasswordHasMFA = () => "checkResetPasswordHasMFA";
8+
export let registrationChallengeHandlerTOTP = () => "registrationChallengeHandlerTOTP";
9+
export let registrationCompletionHandlerTOTP = () => "registrationCompletionHandlerTOTP";
10+
export let loginCompletionHandler = () => "loginCompletionHandler";
11+
export let resetPasswordCheckMFARequired = () => "resetPasswordCheckMFARequired";
12+
export let authorizeActionChallengeHandler = () => "authorizeActionChallengeHandler";
13+
export let authorizeActionCompletionHandler = () => "authorizeActionCompletionHandler";
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@meteorrn/ndev-mfa",
3+
"version": "0.0.1",
4+
"description": "Official ndev:mfa package for MeteorRN",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "",
10+
"license": "ISC",
11+
"peerDependencies": {
12+
"@meteorrn/core":">=2.0.7"
13+
}
14+
}

src/Meteor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,4 @@ module.exports = {
306306

307307
return handle;
308308
},
309-
};
309+
};

0 commit comments

Comments
 (0)