-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Two Factor Authentication #6977
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
Closed
Closed
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
d2041ff
Initial
dblythy bd6aab1
Fix tests
dblythy 9349b17
Fix more tests
dblythy 7f0eac3
Change to MFA
dblythy cba9835
Update UsersRouter.js
dblythy e02d339
Check for existing MFA
dblythy 86f9196
Update UsersRouter.js
dblythy 8b10e20
Recovery Keys
dblythy 2a54a25
Merge branch '2FA' of https://github.com/dblythy/parse-server into 2FA
dblythy f4c808a
Update PostgresStorageAdapter.js
dblythy 782677e
Fix failing tests
dblythy 9781856
Add Definitions
dblythy 5aa540c
Merge branch 'master' into 2FA
dblythy 817ef66
change errors and increase coverage
dblythy 77d8b21
reorderMFA
dblythy de00a68
more tests and renaming
dblythy 1c81ba8
Update UsersRouter.js
dblythy 5e53a42
Update UsersRouter.js
dblythy c18519c
log failing test
dblythy 9113c99
undo log error
dblythy 1c8306a
Revert "undo log error"
dblythy f5dcc06
consistent recoveryKeys naming
dblythy cc499fd
stringify recovery keys
dblythy 580754c
Update UsersRouter.js
dblythy 260aa24
change 2fa to mfa
dblythy 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or 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 hidden or 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,208 @@ | ||
'use strict'; | ||
|
||
const request = require('../lib/request'); | ||
const otplib = require('otplib'); | ||
|
||
describe('2FA', () => { | ||
function enable2FA(user) { | ||
return request({ | ||
method: 'GET', | ||
url: 'http://localhost:8378/1/users/me/enable2FA', | ||
json: true, | ||
headers: { | ||
'X-Parse-Session-Token': user.getSessionToken(), | ||
'X-Parse-Application-Id': Parse.applicationId, | ||
'X-Parse-REST-API-Key': 'rest', | ||
}, | ||
}); | ||
} | ||
|
||
function validate2FA(user, token) { | ||
return request({ | ||
method: 'POST', | ||
url: 'http://localhost:8378/1/users/me/verify2FA', | ||
body: { | ||
token, | ||
}, | ||
headers: { | ||
'X-Parse-Session-Token': user.getSessionToken(), | ||
'X-Parse-Application-Id': Parse.applicationId, | ||
'X-Parse-REST-API-Key': 'rest', | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
} | ||
|
||
function loginWith2Fa(username, password, token) { | ||
let req = `http://localhost:8378/1/login?username=${username}&password=${password}`; | ||
if (token) { | ||
req += `&token=${token}`; | ||
} | ||
return request({ | ||
method: 'POST', | ||
url: req, | ||
headers: { | ||
'X-Parse-Application-Id': Parse.applicationId, | ||
'X-Parse-REST-API-Key': 'rest', | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
} | ||
|
||
it('should enable 2FA tokens', async () => { | ||
await reconfigureServer({ | ||
twoFactor: { | ||
enabled: true, | ||
}, | ||
appName: 'testApp', | ||
}); | ||
const user = await Parse.User.signUp('username', 'password'); | ||
const { | ||
data: { secret, qrcodeURL }, | ||
} = await enable2FA(user); | ||
expect(qrcodeURL).toBeDefined(); | ||
expect(qrcodeURL).toContain('otpauth://totp/testApp'); | ||
expect(qrcodeURL).toContain('secret'); | ||
expect(qrcodeURL).toContain('username'); | ||
expect(qrcodeURL).toContain('period'); | ||
expect(qrcodeURL).toContain('digits'); | ||
expect(qrcodeURL).toContain('algorithm'); | ||
const token = otplib.authenticator.generate(secret); | ||
await validate2FA(user, token); | ||
await Parse.User.logOut(); | ||
let verifytoken = ''; | ||
const mfaLogin = async () => { | ||
try { | ||
const result = await loginWith2Fa('username', 'password', verifytoken); | ||
if (!verifytoken) { | ||
throw 'Should not have been able to login.'; | ||
} | ||
const newUser = result.data; | ||
expect(newUser.objectId).toBe(user.id); | ||
expect(newUser.username).toBe('username'); | ||
expect(newUser.createdAt).toBe(user.createdAt.toISOString()); | ||
expect(newUser.MFAEnabled).toBe(true); | ||
} catch (err) { | ||
expect(err.text).toMatch('{"code":211,"error":"Please provide your 2FA token."}'); | ||
verifytoken = otplib.authenticator.generate(secret); | ||
if (err.text.includes('211')) { | ||
await mfaLogin(); | ||
} | ||
} | ||
}; | ||
await mfaLogin(); | ||
}); | ||
|
||
it('can reject 2FA', async () => { | ||
await reconfigureServer({ | ||
twoFactor: { | ||
enabled: true, | ||
}, | ||
}); | ||
const user = await Parse.User.signUp('username', 'password'); | ||
const { | ||
data: { secret }, | ||
} = await enable2FA(user); | ||
const token = otplib.authenticator.generate(secret); | ||
await validate2FA(user, token); | ||
await Parse.User.logOut(); | ||
try { | ||
await loginWith2Fa('username', 'password', '123102'); | ||
throw 'should not be able to login.'; | ||
} catch (e) { | ||
expect(e.text).toBe('{"code":212,"error":"Invalid 2FA token"}'); | ||
} | ||
}); | ||
|
||
it('can encrypt 2FA tokens', async () => { | ||
await reconfigureServer({ | ||
twoFactor: { | ||
enabled: true, | ||
encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD', | ||
}, | ||
}); | ||
const user = await Parse.User.signUp('username', 'password'); | ||
const { | ||
data: { secret }, | ||
} = await enable2FA(user); | ||
const token = otplib.authenticator.generate(secret); | ||
await validate2FA(user, token); | ||
await Parse.User.logOut(); | ||
let verifytoken = ''; | ||
const mfaLogin = async () => { | ||
try { | ||
const result = await loginWith2Fa('username', 'password', verifytoken); | ||
if (!verifytoken) { | ||
throw 'Should not have been able to login.'; | ||
} | ||
const newUser = result.data; | ||
expect(newUser.objectId).toBe(user.id); | ||
expect(newUser.username).toBe('username'); | ||
expect(newUser.createdAt).toBe(user.createdAt.toISOString()); | ||
expect(newUser._mfa).toBeUndefined(); | ||
} catch (err) { | ||
expect(err.text).toMatch('{"code":211,"error":"Please provide your 2FA token."}'); | ||
verifytoken = otplib.authenticator.generate(secret); | ||
if (err.text.includes('211')) { | ||
await mfaLogin(); | ||
} | ||
} | ||
}; | ||
await mfaLogin(); | ||
}); | ||
it('cannot set _mfa or mfa', async () => { | ||
await reconfigureServer({ | ||
twoFactor: { | ||
enabled: true, | ||
encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD', | ||
}, | ||
}); | ||
const user = await Parse.User.signUp('username', 'password'); | ||
const { | ||
data: { secret }, | ||
} = await enable2FA(user); | ||
const token = otplib.authenticator.generate(secret); | ||
await validate2FA(user, token); | ||
user.set('_mfa', 'foo'); | ||
user.set('mfa', 'foo'); | ||
await user.save(null, { sessionToken: user.getSessionToken() }); | ||
await Parse.User.logOut(); | ||
let verifytoken = ''; | ||
const mfaLogin = async () => { | ||
try { | ||
const result = await loginWith2Fa('username', 'password', verifytoken); | ||
if (!verifytoken) { | ||
throw 'Should not have been able to login.'; | ||
} | ||
const newUser = result.data; | ||
expect(newUser.objectId).toBe(user.id); | ||
expect(newUser.username).toBe('username'); | ||
expect(newUser.createdAt).toBe(user.createdAt.toISOString()); | ||
expect(newUser._mfa).toBeUndefined(); | ||
} catch (err) { | ||
expect(err.text).toMatch('{"code":211,"error":"Please provide your 2FA token."}'); | ||
verifytoken = otplib.authenticator.generate(secret); | ||
if (err.text.includes('211')) { | ||
await mfaLogin(); | ||
} | ||
} | ||
}; | ||
await mfaLogin(); | ||
}); | ||
it('prevent setting on mfw / 2fa tokens', async () => { | ||
const user = await Parse.User.signUp('username', 'password'); | ||
user.set('MFAEnabled', true); | ||
user.set('mfa', true); | ||
user.set('_mfa', true); | ||
await user.save(null, { sessionToken: user.getSessionToken() }); | ||
await user.fetch({ sessionToken: user.getSessionToken() }); | ||
expect(user.get('MFAEnabled')).toBeUndefined(); | ||
expect(user.get('mfa')).toBeUndefined(); | ||
expect(user.get('_mfa')).toBeUndefined(); | ||
await reconfigureServer({ | ||
twoFactor: { | ||
enabled: false, | ||
}, | ||
}); | ||
}); | ||
}); |
This file contains hidden or 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
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.
Uh oh!
There was an error while loading. Please reload this page.