-
Notifications
You must be signed in to change notification settings - Fork 10.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[NEW] Two Factor Auth #6476
Merged
Merged
[NEW] Two Factor Auth #6476
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
5079f5d
Initial implementation of two-factor authentication
sampaiodiego 843d061
Init TOTP login verification
rodrigok 5e94f40
Implement intial 2FA validation
rodrigok 749c3c6
Add verification step for two-factor
sampaiodiego 43a3021
Add ability to disable two-factor authentication
sampaiodiego afa8489
Integrate login with 2FA TOTP
rodrigok 7b62d7a
Merge branch 'improvements/2fa-implementation' of https://github.com/…
rodrigok ba74f9b
Improve 2FA interface
rodrigok 1617fde
Validates current two-factor for disabling
sampaiodiego 4bf9e16
Merge remote-tracking branch 'origin/develop' into improvements/2fa-i…
rodrigok 5bd7d0f
Fix ESLint
rodrigok 590a66a
Fix eslint
sampaiodiego 56aa03b
Add warning for native apps
rodrigok e0614dd
Merge branch 'improvements/2fa-implementation' of https://github.com/…
rodrigok e93b39e
Fix Swal alignment
rodrigok 9b9df51
Implement 2FA backup codes
sampaiodiego 3b960d4
Merge branch 'improvements/2fa-implementation' of github.com:RocketCh…
sampaiodiego aacf485
Lint fixes
rodrigok 23d1b94
Add UI to regenerate 2FA backup codes
sampaiodiego 8f3ebac
Rename 2FA methods
sampaiodiego 77c239f
Add mention to backup notes to translation
sampaiodiego 8abc137
Fix backup codes remaining not showing
sampaiodiego 2641efd
Merge remote-tracking branch 'origin/develop' into improvements/2fa-i…
rodrigok af873c3
Add to HISTORY.md
rodrigok File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change this a bit? Maybe something like:
Feels less critical :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this was only meant to be temporary until the mobile applications actually support it, which is why it isn't translated and is only in english.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not too worried about the translation. If for what ever reason this makes it to a release before its supported. This is much better lingo to have its less critical toward the mobile guys :)