-
Notifications
You must be signed in to change notification settings - Fork 10.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6476 from RocketChat/improvements/2fa-implementation
[NEW] Two Factor Auth
- Loading branch information
Showing
26 changed files
with
619 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
This directory and the files immediately inside it are automatically generated | ||
when you change this package's NPM dependencies. Commit the files in this | ||
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control | ||
so that others run the same versions of sub-dependencies. | ||
|
||
You should NOT check in the node_modules directory that Meteor automatically | ||
creates; if you are using git, the .gitignore file tells git to ignore it. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import toastr from 'toastr'; | ||
|
||
function reportError(error, callback) { | ||
if (callback) { | ||
callback(error); | ||
} else { | ||
throw error; | ||
} | ||
} | ||
|
||
Meteor.loginWithPasswordAndTOTP = function(selector, password, code, callback) { | ||
if (typeof selector === 'string') { | ||
if (selector.indexOf('@') === -1) { | ||
selector = {username: selector}; | ||
} else { | ||
selector = {email: selector}; | ||
} | ||
} | ||
|
||
Accounts.callLoginMethod({ | ||
methodArguments: [{ | ||
totp: { | ||
login: { | ||
user: selector, | ||
password: Accounts._hashPassword(password) | ||
}, | ||
code | ||
} | ||
}], | ||
userCallback(error) { | ||
if (error) { | ||
reportError(error, callback); | ||
} else { | ||
callback && callback(); | ||
} | ||
} | ||
}); | ||
}; | ||
|
||
const loginWithPassword = Meteor.loginWithPassword; | ||
|
||
Meteor.loginWithPassword = function(email, password, cb) { | ||
loginWithPassword(email, password, (error) => { | ||
if (!error || error.error !== 'totp-required') { | ||
return cb(error); | ||
} | ||
|
||
swal({ | ||
title: t('Two-factor_authentication'), | ||
text: t('Open_your_authentication_app_and_enter_the_code'), | ||
type: 'input', | ||
inputType: 'text', | ||
showCancelButton: true, | ||
closeOnConfirm: true, | ||
confirmButtonText: t('Verify'), | ||
cancelButtonText: t('Cancel') | ||
}, (code) => { | ||
if (code === false) { | ||
return cb(); | ||
} | ||
|
||
Meteor.loginWithPasswordAndTOTP(email, password, code, (error) => { | ||
if (error && error.error === 'totp-invalid') { | ||
toastr.error(t('Invalid_two_factor_code')); | ||
cb(); | ||
} else { | ||
cb(error); | ||
} | ||
}); | ||
}); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<template name="accountSecurity"> | ||
<section class="page-container page-home page-static"> | ||
<header class="fixed-title border-component-color"> | ||
{{> burger}} | ||
<h2> | ||
<span class="room-title">{{_ "Security"}}</span> | ||
</h2> | ||
</header> | ||
<div class="content"> | ||
<div class="rocket-form"> | ||
<fieldset> | ||
<div class="section"> | ||
<h1>{{_ "Two-factor_authentication"}}</h1> | ||
<div class="section-content border-component-color"> | ||
<div class="alert pending-background pending-color pending-border"> | ||
<strong> | ||
WARNING: Once you enable this, you will not be able to login on the native mobile apps (Rocket.Chat+) using your password until they implement the 2FA. | ||
</strong> | ||
</div> | ||
{{#if isEnabled}} | ||
<button class="button danger disable-2fa">{{_ "Disable_two-factor_authentication"}}</button> | ||
{{else}} | ||
{{#unless isRegistering}} | ||
<p>{{_ "Two-factor_authentication_is_currently_disabled"}}</p> | ||
|
||
<button class="button primary enable-2fa">{{_ "Enable_two-factor_authentication"}}</button> | ||
{{else}} | ||
<p>{{_ "Scan_QR_code"}}</p> | ||
|
||
<img src="{{imageData}}"> | ||
|
||
<form class="inline verify-code"> | ||
<input type="text" id="testCode" placeholder="{{_ "Enter_authentication_code"}}"> | ||
<button type="submit" class="button primary">{{_ "Verify"}}</button> | ||
</form> | ||
{{/unless}} | ||
{{/if}} | ||
</div> | ||
</div> | ||
</fieldset> | ||
|
||
|
||
{{#if isEnabled}} | ||
<fieldset> | ||
<div class="section"> | ||
<h1>{{_ "Backup_codes"}}</h1> | ||
<div class="section-content border-component-color"> | ||
<p>{{codesRemaining}}</p> | ||
<button class="button regenerate-codes">{{_ "Regenerate_codes"}}</button> | ||
</div> | ||
</div> | ||
</fieldset> | ||
{{/if}} | ||
</div> | ||
</div> | ||
</section> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import toastr from 'toastr'; | ||
import qrcode from 'yaqrcode'; | ||
|
||
window.qrcode = qrcode; | ||
|
||
Template.accountSecurity.helpers({ | ||
showImage() { | ||
return Template.instance().showImage.get(); | ||
}, | ||
imageData() { | ||
return Template.instance().imageData.get(); | ||
}, | ||
isEnabled() { | ||
const user = Meteor.user(); | ||
return user && user.services && user.services.totp && user.services.totp.enabled; | ||
}, | ||
isRegistering() { | ||
return Template.instance().state.get() === 'registering'; | ||
}, | ||
codesRemaining() { | ||
if (Template.instance().codesRemaining.get()) { | ||
return t('You_have_n_codes_remaining', { number: Template.instance().codesRemaining.get() }); | ||
} | ||
} | ||
}); | ||
|
||
Template.accountSecurity.events({ | ||
'click .enable-2fa'(event, instance) { | ||
Meteor.call('2fa:enable', (error, result) => { | ||
instance.imageData.set(qrcode(result.url, { size: 200 })); | ||
|
||
instance.state.set('registering'); | ||
|
||
Meteor.defer(() => { | ||
instance.find('#testCode').focus(); | ||
}); | ||
}); | ||
}, | ||
|
||
'click .disable-2fa'() { | ||
swal({ | ||
title: t('Two-factor_authentication'), | ||
text: t('Open_your_authentication_app_and_enter_the_code'), | ||
type: 'input', | ||
inputType: 'text', | ||
showCancelButton: true, | ||
closeOnConfirm: true, | ||
confirmButtonText: t('Verify'), | ||
cancelButtonText: t('Cancel') | ||
}, (code) => { | ||
if (code === false) { | ||
return; | ||
} | ||
|
||
Meteor.call('2fa:disable', code, (error, result) => { | ||
if (error) { | ||
return toastr.error(t(error.error)); | ||
} | ||
|
||
if (result) { | ||
toastr.success(t('Two-factor_authentication_disabled')); | ||
} else { | ||
return toastr.error(t('Invalid_two_factor_code')); | ||
} | ||
}); | ||
}); | ||
}, | ||
|
||
'submit .verify-code'(event, instance) { | ||
event.preventDefault(); | ||
|
||
Meteor.call('2fa:validateTempToken', instance.find('#testCode').value, (error, result) => { | ||
if (result) { | ||
instance.showBackupCodes(result.codes); | ||
|
||
instance.find('#testCode').value = ''; | ||
instance.state.set(); | ||
toastr.success(t('Two-factor_authentication_enabled')); | ||
} else { | ||
toastr.error(t('Invalid_two_factor_code')); | ||
} | ||
}); | ||
}, | ||
|
||
'click .regenerate-codes'(event, instance) { | ||
swal({ | ||
title: t('Two-factor_authentication'), | ||
text: t('Open_your_authentication_app_and_enter_the_code'), | ||
type: 'input', | ||
inputType: 'text', | ||
showCancelButton: true, | ||
closeOnConfirm: false, | ||
confirmButtonText: t('Verify'), | ||
cancelButtonText: t('Cancel') | ||
}, (code) => { | ||
if (code === false) { | ||
return; | ||
} | ||
|
||
Meteor.call('2fa:regenerateCodes', code, (error, result) => { | ||
if (error) { | ||
return toastr.error(t(error.error)); | ||
} | ||
|
||
if (result) { | ||
instance.showBackupCodes(result.codes); | ||
} else { | ||
return toastr.error(t('Invalid_two_factor_code')); | ||
} | ||
}); | ||
}); | ||
} | ||
}); | ||
|
||
Template.accountSecurity.onCreated(function() { | ||
this.showImage = new ReactiveVar(false); | ||
this.imageData = new ReactiveVar(); | ||
|
||
this.state = new ReactiveVar(); | ||
|
||
this.codesRemaining = new ReactiveVar(); | ||
|
||
this.showBackupCodes = (userCodes) => { | ||
const backupCodes = userCodes.map((value, index) => { | ||
return (index + 1) % 4 === 0 && index < 11 ? `${ value }\n` : `${ value } `; | ||
}).join(''); | ||
const codes = `<code class="text-center allow-text-selection">${ backupCodes }</code>`; | ||
swal({ | ||
title: t('Backup_codes'), | ||
text: `${ t('Make_sure_you_have_a_copy_of_your_codes', { codes }) }`, | ||
html: true | ||
}); | ||
}; | ||
|
||
this.autorun(() => { | ||
const user = Meteor.user(); | ||
if (user && user.services && user.services.totp && user.services.totp.enabled) { | ||
Meteor.call('2fa:checkCodesRemaining', (error, result) => { | ||
if (result) { | ||
this.codesRemaining.set(result.remaining); | ||
} | ||
}); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
Package.describe({ | ||
name: 'rocketchat:2fa', | ||
version: '0.0.1', | ||
summary: '', | ||
git: '', | ||
documentation: 'README.md' | ||
}); | ||
|
||
Npm.depends({ | ||
speakeasy: '2.0.0', | ||
yaqrcode: '0.2.1' | ||
}); | ||
|
||
Package.onUse(function(api) { | ||
api.use([ | ||
'accounts-base', | ||
'ecmascript', | ||
'templating', | ||
'rocketchat:lib', | ||
'sha', | ||
'random' | ||
]); | ||
|
||
api.addFiles('client/accountSecurity.html', 'client'); | ||
api.addFiles('client/accountSecurity.js', 'client'); | ||
api.addFiles('client/TOTPPassword.js', 'client'); | ||
|
||
api.addFiles('server/lib/totp.js', 'server'); | ||
|
||
api.addFiles('server/methods/checkCodesRemaining.js', 'server'); | ||
api.addFiles('server/methods/disable.js', 'server'); | ||
api.addFiles('server/methods/enable.js', 'server'); | ||
api.addFiles('server/methods/regenerateCodes.js', 'server'); | ||
api.addFiles('server/methods/validateTempToken.js', 'server'); | ||
|
||
api.addFiles('server/models/users.js', 'server'); | ||
|
||
api.addFiles('server/loginHandler.js', 'server'); | ||
}); |
Oops, something went wrong.