From 70e60fe48524dfbf865784d04aa73ef4909919af Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Sun, 20 Mar 2022 18:51:06 +0100 Subject: [PATCH] Use userId instead of username & simple server test --- packages/accounts-2fa/2fa-server.js | 51 +++++++++++-------- packages/accounts-2fa/package.js | 16 +++++- packages/accounts-2fa/server_tests.js | 15 ++++++ .../accounts-base/accounts_client_tests.js | 2 +- packages/check/match.js | 8 +-- 5 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 packages/accounts-2fa/server_tests.js diff --git a/packages/accounts-2fa/2fa-server.js b/packages/accounts-2fa/2fa-server.js index ff40304dd6..94ea426570 100644 --- a/packages/accounts-2fa/2fa-server.js +++ b/packages/accounts-2fa/2fa-server.js @@ -2,6 +2,7 @@ import { Accounts } from 'meteor/accounts-base'; import twofactor from 'node-2fa'; import QRCode from 'qrcode-svg'; import { Meteor } from 'meteor/meteor'; +import { check, Match } from 'meteor/check'; Accounts._is2faEnabledForUser = selector => { if (!Meteor.isServer) { @@ -13,7 +14,7 @@ Accounts._is2faEnabledForUser = selector => { if (typeof selector === 'string') { if (!selector.includes('@')) { - selector = { username: selector }; + selector = { $or: [{ _id: selector }, { username: selector }] }; } else { selector = { email: selector }; } @@ -21,7 +22,7 @@ Accounts._is2faEnabledForUser = selector => { const user = Meteor.users.findOne(selector) || {}; const { services: { twoFactorAuthentication } = {} } = user; - return ( + return !!( twoFactorAuthentication && twoFactorAuthentication.secret && twoFactorAuthentication.type === 'otp' @@ -43,6 +44,7 @@ Accounts._isTokenValid = (secret, code) => { Meteor.methods({ generate2faActivationQrCode(appName) { + check(appName, String); const user = Meteor.user(); if (!user) { @@ -52,28 +54,27 @@ Meteor.methods({ ); } - const { username } = user; - const { secret, uri } = twofactor.generateSecret({ name: appName.trim(), - account: username, + account: user.username || user._id }); const svg = new QRCode(uri).svg(); Meteor.users.update( - { username }, + { _id: user._id }, { $set: { 'services.twoFactorAuthentication': { - secret, - }, - }, + secret + } + } } ); return svg; }, enableUser2fa(code) { + check(code, String); const user = Meteor.user(); if (!user) { @@ -81,8 +82,7 @@ Meteor.methods({ } const { - services: { twoFactorAuthentication }, - username, + services: { twoFactorAuthentication } } = user; if (!twoFactorAuthentication || !twoFactorAuthentication.secret) { @@ -96,37 +96,44 @@ Meteor.methods({ } Meteor.users.update( - { username }, + { _id: user._id }, { $set: { 'services.twoFactorAuthentication': { ...twoFactorAuthentication, - type: 'otp', - }, - }, + type: 'otp' + } + } } ); }, disableUser2fa() { - const user = Meteor.user(); + const userId = Meteor.userId(); - if (!user) { + if (!userId) { throw new Meteor.Error(400, 'No user logged in.'); } Meteor.users.update( - { username: user.username }, + { _id: userId }, { $unset: { - 'services.twoFactorAuthentication': 1, - }, + 'services.twoFactorAuthentication': 1 + } } ); }, has2faEnabled(selector) { - if (!Meteor.user()) { + check(selector, Match.Maybe(Match.OneOf(String, Object))); + const userId = Meteor.userId(); + if (!userId) { throw new Meteor.Error(400, 'No user logged in.'); } + + if (!selector) { + selector = { $or: [{ _id: userId }, { username: userId }] }; + } + return Accounts._is2faEnabledForUser(selector); - }, + } }); diff --git a/packages/accounts-2fa/package.js b/packages/accounts-2fa/package.js index c20def2c9a..8598eac324 100644 --- a/packages/accounts-2fa/package.js +++ b/packages/accounts-2fa/package.js @@ -12,11 +12,25 @@ Npm.depends({ Package.onUse(function(api) { api.use(['accounts-base'], ['client', 'server']); - // Export Accounts (etc) to packages using this one. + // Export Accounts (etc.) to packages using this one. api.imply('accounts-base', ['client', 'server']); api.use('ecmascript'); + api.use('check', 'server'); api.addFiles(['2fa-client.js'], 'client'); api.addFiles(['2fa-server.js'], 'server'); }); + +Package.onTest(function(api) { + api.use([ + 'accounts-base', + 'accounts-password', + 'ecmascript', + 'tinytest', + 'random', + 'accounts-2fa', + ]); + + api.mainModule('server_tests.js', 'server'); +}); diff --git a/packages/accounts-2fa/server_tests.js b/packages/accounts-2fa/server_tests.js new file mode 100644 index 0000000000..9f4e089026 --- /dev/null +++ b/packages/accounts-2fa/server_tests.js @@ -0,0 +1,15 @@ +import { Accounts } from 'meteor/accounts-base'; +import { Random } from 'meteor/random'; + +Tinytest.add('account - 2fa - has2faEnabled', test => { + // Create users + const userWithout2FA = Accounts.insertUserDoc({}, { emails: [{address: `${Random.id()}@meteorapp.com`, verified: true}] }); + const userWith2FA = Accounts.insertUserDoc({}, { emails: [{address: `${Random.id()}@meteorapp.com`, verified: true}], services: { twoFactorAuthentication: { type: 'otp', secret: 'superSecret' } } }); + + test.equal(Accounts._is2faEnabledForUser(userWithout2FA), false); + test.equal(Accounts._is2faEnabledForUser(userWith2FA), true); + + // cleanup + Accounts.users.remove(userWithout2FA); + Accounts.users.remove(userWith2FA); +}); diff --git a/packages/accounts-base/accounts_client_tests.js b/packages/accounts-base/accounts_client_tests.js index a94c18cea7..193df719fb 100644 --- a/packages/accounts-base/accounts_client_tests.js +++ b/packages/accounts-base/accounts_client_tests.js @@ -10,7 +10,7 @@ const profile = { name: username, [excludeField]: excludeValue, [defaultExcludeField]: excludeValue, -} +}; const logoutAndCreateUser = (test, done, nextTests) => { Meteor.logout(() => { diff --git a/packages/check/match.js b/packages/check/match.js index 4c1e2bd94d..fe6d32f70d 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -114,7 +114,7 @@ export const Match = { _failIfArgumentsAreNotAllChecked(f, context, args, description) { const argChecker = new ArgumentChecker(args, description); const result = currentArgumentChecker.withValue( - argChecker, + argChecker, () => f.apply(context, args) ); @@ -261,7 +261,7 @@ const testSubtree = (value, pattern) => { if (typeof value === 'number' && (value | 0) === value) { return false; } - + return { message: `Expected Integer, got ${stringForErrorMessage(value)}`, path: '', @@ -296,7 +296,7 @@ const testSubtree = (value, pattern) => { return result; } } - + return false; } @@ -310,7 +310,7 @@ const testSubtree = (value, pattern) => { if (!(err instanceof Match.Error)) { throw err; } - + return { message: err.message, path: err.path