mirror of
https://github.com/gravitational/teleport
synced 2024-10-22 10:13:21 +00:00
implemented web login with U2F
This commit is contained in:
parent
a237d22c15
commit
4beaa2594e
|
@ -22,7 +22,7 @@ var {actions, getters} = require('app/modules/user');
|
|||
var GoogleAuthInfo = require('./googleAuthLogo');
|
||||
var cfg = require('app/config');
|
||||
var {TeleportLogo} = require('./icons.jsx');
|
||||
var {PROVIDER_GOOGLE} = require('app/services/auth');
|
||||
var {PROVIDER_GOOGLE, SECOND_FACTOR_TYPE_HOTP, SECOND_FACTOR_TYPE_OIDC, SECOND_FACTOR_TYPE_U2F} = require('app/services/auth');
|
||||
|
||||
var LoginInputForm = React.createClass({
|
||||
|
||||
|
@ -33,13 +33,17 @@ var LoginInputForm = React.createClass({
|
|||
user: '',
|
||||
password: '',
|
||||
token: '',
|
||||
provider: null
|
||||
provider: null,
|
||||
second_factor_type: SECOND_FACTOR_TYPE_HOTP
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onLogin(e){
|
||||
e.preventDefault();
|
||||
this.state.second_factor_type = SECOND_FACTOR_TYPE_HOTP;
|
||||
// token field is required for Google Authenticator
|
||||
$('input[name=token]').addClass("required");
|
||||
if (this.isValid()) {
|
||||
this.props.onClick(this.state);
|
||||
}
|
||||
|
@ -47,10 +51,21 @@ var LoginInputForm = React.createClass({
|
|||
|
||||
onLoginWithGoogle: function(e) {
|
||||
e.preventDefault();
|
||||
this.state.second_factor_type = SECOND_FACTOR_TYPE_OIDC;
|
||||
this.state.provider = PROVIDER_GOOGLE;
|
||||
this.props.onClick(this.state);
|
||||
},
|
||||
|
||||
onLoginWithU2f: function(e) {
|
||||
e.preventDefault();
|
||||
this.state.second_factor_type = SECOND_FACTOR_TYPE_U2F;
|
||||
// token field not required for U2F
|
||||
$('input[name=token]').removeClass("required");
|
||||
if (this.isValid()) {
|
||||
this.props.onClick(this.state);
|
||||
}
|
||||
},
|
||||
|
||||
isValid: function() {
|
||||
var $form = $(this.refs.form);
|
||||
return $form.length === 0 || $form.valid();
|
||||
|
@ -75,7 +90,9 @@ var LoginInputForm = React.createClass({
|
|||
<input autoComplete="off" valueLink={this.linkState('token')} className="form-control required" name="token" placeholder="Two factor token (Google Authenticator)"/>
|
||||
</div>
|
||||
<button onClick={this.onLogin} disabled={isProcessing} type="submit" className="btn btn-primary block full-width m-b">Login</button>
|
||||
<button onClick={this.onLoginWithU2f} disabled={isProcessing} type="submit" className="btn btn-primary block full-width m-b">Login with U2F</button>
|
||||
{ useGoogle ? <button onClick={this.onLoginWithGoogle} type="submit" className="btn btn-danger block full-width m-b">With Google</button> : null }
|
||||
{ isProcessing && this.state.second_factor_type == SECOND_FACTOR_TYPE_U2F ? (<label className="help-block">Insert your U2F key and press the button on the key</label>) : null }
|
||||
{ isFailed ? (<label className="error">{message}</label>) : null }
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -49,6 +49,10 @@ let cfg = {
|
|||
sessionPath: '/v1/webapi/sessions',
|
||||
invitePath: '/v1/webapi/users/invites/:inviteToken',
|
||||
createUserPath: '/v1/webapi/users',
|
||||
u2fCreateUserChallengePath: '/webapi/u2f/invite_register_request/:inviteToken',
|
||||
u2fCreateUserPath: '/webapi/u2f/new_user',
|
||||
u2fSessionChallengePath: '/webapi/u2f/sign_request',
|
||||
u2fSessionPath: '/webapi/u2f/sessions',
|
||||
nodesPath: '/v1/webapi/sites/-current-/nodes',
|
||||
siteSessionPath: '/v1/webapi/sites/-current-/sessions',
|
||||
sessionEventsPath: '/v1/webapi/sites/-current-/sessions/:sid/events',
|
||||
|
@ -84,6 +88,10 @@ let cfg = {
|
|||
return formatPattern(cfg.api.invitePath, {inviteToken});
|
||||
},
|
||||
|
||||
getU2fCreateUserChallengeUrl(inviteToken){
|
||||
return formatPattern(cfg.api.u2fCreateUserChallengePath, {inviteToken});
|
||||
},
|
||||
|
||||
getEventStreamConnStr(){
|
||||
var hostname = getWsHostName();
|
||||
return `${hostname}/v1/webapi/sites/-current-`;
|
||||
|
|
|
@ -22,6 +22,7 @@ var auth = require('app/services/auth');
|
|||
var session = require('app/services/session');
|
||||
var cfg = require('app/config');
|
||||
var api = require('app/services/api');
|
||||
var {SECOND_FACTOR_TYPE_OIDC, SECOND_FACTOR_TYPE_U2F} = require('app/services/auth');
|
||||
|
||||
export default {
|
||||
|
||||
|
@ -70,14 +71,27 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
login({user, password, token, provider}, redirect){
|
||||
if(provider){
|
||||
login({user, password, token, provider, second_factor_type}, redirect){
|
||||
if(second_factor_type == SECOND_FACTOR_TYPE_OIDC){
|
||||
let fullPath = cfg.getFullUrl(redirect);
|
||||
window.location = cfg.api.getSsoUrl(fullPath, provider);
|
||||
return;
|
||||
}
|
||||
|
||||
restApiActions.start(TRYING_TO_LOGIN);
|
||||
|
||||
if(second_factor_type == SECOND_FACTOR_TYPE_U2F){
|
||||
// Because the U2f API is asynchronous, we have to pass in callbacks
|
||||
auth.u2fLogin(user, password, function(sessionData){
|
||||
restApiActions.success(TRYING_TO_LOGIN);
|
||||
reactor.dispatch(TLPT_RECEIVE_USER, sessionData.user);
|
||||
session.getHistory().push({pathname: redirect});
|
||||
}, function(msg){
|
||||
restApiActions.fail(TRYING_TO_LOGIN, msg);
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
auth.login(user, password, token)
|
||||
.done((sessionData)=>{
|
||||
restApiActions.success(TRYING_TO_LOGIN);
|
||||
|
|
|
@ -18,9 +18,14 @@ var api = require('./api');
|
|||
var session = require('./session');
|
||||
var cfg = require('app/config');
|
||||
var $ = require('jQuery');
|
||||
require('u2f-api-polyfill'); // This puts it in window.u2f
|
||||
|
||||
const PROVIDER_GOOGLE = 'google';
|
||||
|
||||
const SECOND_FACTOR_TYPE_HOTP = 'hotp';
|
||||
const SECOND_FACTOR_TYPE_OIDC = 'oidc';
|
||||
const SECOND_FACTOR_TYPE_U2F = 'u2f';
|
||||
|
||||
const CHECK_TOKEN_REFRESH_RATE = 10*1000; // 10 sec
|
||||
|
||||
var refreshTokenTimerId = null;
|
||||
|
@ -54,6 +59,46 @@ var auth = {
|
|||
});
|
||||
},
|
||||
|
||||
u2fLogin(name, password, successCb, errorCb){
|
||||
auth._stopTokenRefresher();
|
||||
session.clear();
|
||||
|
||||
var data = {
|
||||
user: name,
|
||||
pass: password
|
||||
}
|
||||
|
||||
api.post(cfg.api.u2fSessionChallengePath, data, false).then(data=>{
|
||||
window.u2f.sign(data.appId, data.challenge, [data],function(res){
|
||||
if(res.errorCode){
|
||||
var errorMsg;
|
||||
// lookup error message...
|
||||
for(var err in window.u2f.ErrorCodes){
|
||||
if(window.u2f.ErrorCodes[err] == res.errorCode){
|
||||
errorMsg = err;
|
||||
}
|
||||
}
|
||||
errorCb("U2F Error: " + errorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
var response = {
|
||||
user: name,
|
||||
u2f_sign_response: res
|
||||
}
|
||||
api.post(cfg.api.u2fSessionPath, response, false).then(data=>{
|
||||
session.setUserData(data);
|
||||
auth._startTokenRefresher();
|
||||
successCb(data);
|
||||
}).fail(data=>{
|
||||
errorCb(data.responseJSON);
|
||||
})
|
||||
});
|
||||
}).fail(data=>{
|
||||
errorCb(data.responseJSON);
|
||||
})
|
||||
},
|
||||
|
||||
ensureUser(){
|
||||
this._stopTokenRefresher();
|
||||
|
||||
|
@ -119,3 +164,6 @@ var auth = {
|
|||
|
||||
module.exports = auth;
|
||||
module.exports.PROVIDER_GOOGLE = PROVIDER_GOOGLE;
|
||||
module.exports.SECOND_FACTOR_TYPE_HOTP = SECOND_FACTOR_TYPE_HOTP;
|
||||
module.exports.SECOND_FACTOR_TYPE_OIDC = SECOND_FACTOR_TYPE_OIDC;
|
||||
module.exports.SECOND_FACTOR_TYPE_U2F = SECOND_FACTOR_TYPE_U2F;
|
||||
|
|
Loading…
Reference in a new issue