[pve-devel] [PATCH manager 3/3] ui: support u2f authentication

Wolfgang Bumiller w.bumiller at proxmox.com
Thu May 24 15:28:52 CEST 2018


Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
 www/index.html.tpl                 |   1 +
 www/manager6/Makefile              |   1 +
 www/manager6/Workspace.js          |   6 +-
 www/manager6/dc/U2FEdit.js         | 145 +++++++++++++++++++++++++++++++++++++
 www/manager6/dc/UserView.js        |  15 +++-
 www/manager6/window/LoginWindow.js | 121 ++++++++++++++++++++++++-------
 6 files changed, 257 insertions(+), 32 deletions(-)
 create mode 100644 www/manager6/dc/U2FEdit.js

diff --git a/www/index.html.tpl b/www/index.html.tpl
index a972e3aa..eca75a6f 100644
--- a/www/index.html.tpl
+++ b/www/index.html.tpl
@@ -22,6 +22,7 @@
     [%- ELSE %]
     <script type="text/javascript" src="/pve2/ext6/ext-all.js"></script>
     <script type="text/javascript" src="/pve2/ext6/charts.js"></script>
+    <script type="text/javascript" src="/pve2/js/u2f-lib.js"></script>
     [% END %]
     <script type="text/javascript">
     Proxmox = {
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index a2bd4576..e6d0e698 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -180,6 +180,7 @@ JSSRC= 				                 	\
 	dc/Guests.js					\
 	dc/OptionView.js				\
 	dc/StorageView.js				\
+	dc/U2FEdit.js					\
 	dc/UserEdit.js					\
 	dc/UserView.js					\
 	dc/PoolView.js					\
diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js
index f75356c5..aed61324 100644
--- a/www/manager6/Workspace.js
+++ b/www/manager6/Workspace.js
@@ -19,16 +19,12 @@ Ext.define('PVE.Workspace', {
     updateLoginData: function(loginData) {
 	var me = this;
 	me.loginData = loginData;
-	Proxmox.CSRFPreventionToken = loginData.CSRFPreventionToken;
-	Proxmox.UserName = loginData.username;
+	Proxmox.Utils.setAuthData(loginData);
 
 	if (loginData.cap) {
 	    Ext.state.Manager.set('GuiCap', loginData.cap);
 	}
 
-	// creates a session cookie (expire = null) 
-	// that way the cookie gets deleted after browser window close
-	Ext.util.Cookies.set('PVEAuthCookie', loginData.ticket, null, '/', null, true);
 	me.onLogin(loginData);
     },
 
diff --git a/www/manager6/dc/U2FEdit.js b/www/manager6/dc/U2FEdit.js
new file mode 100644
index 00000000..0cb416f8
--- /dev/null
+++ b/www/manager6/dc/U2FEdit.js
@@ -0,0 +1,145 @@
+Ext.define('PVE.window.U2FEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    initComponent : function() {
+	var me = this;
+
+	if (!me.userid) {
+	    throw "no userid specified";
+	}
+
+	var pwfield;
+	if (Proxmox.UserName !== 'root at pam') {
+	    pwfield = Ext.createWidget('textfield', {
+		inputType: 'password',
+		fieldLabel: gettext('Password'),
+		minLength: 5,
+		name: 'password',
+	    });
+	}
+
+	var delete_btn = new Proxmox.button.Button({
+	    text: gettext('Delete'),
+	    handler: function() {
+		var params = {
+		    userid: me.userid,
+		    action: 'delete'
+		};
+		if (Ext.isDefined(pwfield)) {
+		    var pw = pwfield.getValue();
+		    if (pw.length) {
+			params.password = pw;
+		    }
+		}
+		Proxmox.Utils.API2Request({
+		    url: '/api2/extjs/access/u2f',
+		    params: params,
+		    method: 'PUT',
+		    waitMsgTarget: me,
+		    success: function(response, opts) {
+			me.close();
+		    },
+		    failure: function(response, opts) {
+			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+		    }
+		});
+	    }
+	});
+
+	var finish_fn;
+	var register_fn;
+
+	var register_btn = new Proxmox.button.Button({
+	    text: gettext('Register'),
+	    handler: function() {
+		var params = {
+		    userid: me.userid,
+		    action: 'new'
+		};
+		if (Ext.isDefined(pwfield)) {
+		    var pw = pwfield.getValue();
+		    if (pw.length) {
+			params.password = pw;
+		    }
+		}
+		Proxmox.Utils.API2Request({
+		    url: '/api2/extjs/access/u2f',
+		    params: params,
+		    method: 'PUT',
+		    waitMsgTarget: me,
+		    success: register_fn,
+		    failure: function(response, opts) {
+			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+		    }
+		});
+	    }
+	});
+
+	register_fn = function(response, opts) {
+	    var data = response.result.data;
+	    var msg = Ext.Msg.show({
+	        title: 'U2F: '+gettext('Setup'),
+	        message: gettext('Please press the button on your U2F Device'),
+	        buttons: []
+	    });
+	    Ext.Function.defer(function() {
+		u2f.register(data.appId, [data], [], function(data) {
+		    msg.close();
+		    if (data.errorCode) {
+			Ext.Msg.alert(gettext('Error'), "U2F Error: "+data.errorCode);
+			return;
+		    }
+		    finish_fn(data);
+		});
+	    }, 500, me);
+	};
+
+	finish_fn = function(data) {
+	    var params = {
+		userid: me.userid,
+		action: 'confirm',
+		response: JSON.stringify(data)
+	    };
+	    if (Ext.isDefined(pwfield)) {
+		var pw = pwfield.getValue();
+		if (pw.length) {
+		    params.password = pw;
+		}
+	    }
+	    Proxmox.Utils.API2Request({
+		url: '/api2/extjs/access/u2f',
+		params: params,
+		method: 'PUT',
+		waitMsgTarget: me,
+		success: function() {
+		    me.close();
+		},
+		failure: function(response, opts) {
+		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+		}
+	    });
+	};
+
+
+	var items = [];
+	if (Ext.isDefined(pwfield)) {
+	    items.push(pwfield);
+	}
+	items.push(
+	    delete_btn,
+	    register_btn,
+	    {
+		xtype: 'hiddenfield',
+		name: 'userid',
+		value: me.userid
+	    }
+	);
+	Ext.apply(me, {
+	    subject: 'U2F',
+	    url: '/api2/extjs/access/u2f',
+	    items: items
+	});
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/dc/UserView.js b/www/manager6/dc/UserView.js
index 6dfc1041..c143464e 100644
--- a/www/manager6/dc/UserView.js
+++ b/www/manager6/dc/UserView.js
@@ -78,6 +78,19 @@ Ext.define('PVE.dc.UserView', {
 	    }
 	});
 
+	var u2fchange_btn = new Proxmox.button.Button({
+	    text: gettext('U2F'),
+	    disabled: true,
+	    selModel: sm,
+	    handler: function(btn, event, rec) {
+		var win = Ext.create('PVE.window.U2FEdit',{
+                    userid: rec.data.userid
+		});
+		win.on('destroy', reload);
+		win.show();
+	    }
+	});
+
         var tbar = [
             {
 		text: gettext('Add'),
@@ -89,7 +102,7 @@ Ext.define('PVE.dc.UserView', {
                     win.show();
 		}
             },
-	    edit_btn, remove_btn, pwchange_btn
+	    edit_btn, remove_btn, pwchange_btn, u2fchange_btn
         ];
 
 	var render_full_name = function(firstname, metaData, record) {
diff --git a/www/manager6/window/LoginWindow.js b/www/manager6/window/LoginWindow.js
index 683fb54c..aa16fb52 100644
--- a/www/manager6/window/LoginWindow.js
+++ b/www/manager6/window/LoginWindow.js
@@ -13,39 +13,108 @@ Ext.define('PVE.window.LoginWindow', {
 	    var saveunField = this.lookupReference('saveunField');
 	    var view = this.getView();
 
-	    if(form.isValid()){
-		view.el.mask(gettext('Please wait...'), 'x-mask-loading');
+	    if (!form.isValid()) {
+		return;
+	    }
+
+	    var perform_u2f_fn;
+	    var finish_u2f_fn;
+
+	    var failure_fn = function(resp) {
+		view.el.unmask();
+		var handler = function() {
+		    var uf = me.lookupReference('usernameField');
+		    uf.focus(true, true);
+		};
+
+		Ext.MessageBox.alert(gettext('Error'),
+				     gettext("Login failed. Please try again"),
+				     handler);
+	    };
+
+	    var success_fn = function(data) {
+		var handler = view.handler || Ext.emptyFn;
+		handler.call(me, data);
+		view.close();
+	    };
+
+	    view.el.mask(gettext('Please wait...'), 'x-mask-loading');
+
+	    // set or clear username
+	    var sp = Ext.state.Manager.getProvider();
+	    if (saveunField.getValue() === true) {
+		sp.set(unField.getStateId(), unField.getValue());
+	    } else {
+		sp.clear(unField.getStateId());
+	    }
+	    sp.set(saveunField.getStateId(), saveunField.getValue());
+
+	    form.submit({
+		failure: function(f, resp){
+		    failure_fn(resp);
+		},
+		success: function(f, resp){
+		    view.el.unmask();
 
-		// set or clear username
-		var sp = Ext.state.Manager.getProvider();
-		if (saveunField.getValue() === true) {
-		    sp.set(unField.getStateId(), unField.getValue());
-		} else {
-		    sp.clear(unField.getStateId());
+		    var data = resp.result.data;
+		    if (Ext.isDefined(data.U2FChallenge)) {
+			perform_u2f_fn(data);
+		    } else {
+			success_fn(data);
+		    }
 		}
-		sp.set(saveunField.getStateId(), saveunField.getValue());
+	    });
+
+	    perform_u2f_fn = function(data) {
+		// Store first factor login information first:
+		data.LoggedOut = true;
+		Proxmox.Utils.setAuthData(data);
+		// Show the message:
+		var msg = Ext.Msg.show({
+		    title: 'U2F: '+gettext('Verification'),
+		    message: gettext('Please press the button on your U2F Device'),
+		    buttons: []
+		});
+		var chlg = data.U2FChallenge;
+		var key = {
+		    version: chlg.version,
+		    keyHandle: chlg.keyHandle
+		};
+		u2f.sign(chlg.appId, chlg.challenge, [key], function(res) {
+		    msg.close();
+		    if (res.errorCode) {
+			Proxmox.Utils.authClear();
+			Ext.Msg.alert(gettext('Error'), "U2F Error: "+res.errorCode);
+			return;
+		    }
+		    delete res.errorCode;
+		    finish_u2f_fn(res);
+		});
+	    };
 
-		form.submit({
-		    failure: function(f, resp){
+	    finish_u2f_fn = function(res) {
+		view.el.mask(gettext('Please wait...'), 'x-mask-loading');
+		var params = { response: JSON.stringify(res) };
+		Proxmox.Utils.API2Request({
+		    url: '/api2/extjs/access/u2f',
+		    params: params,
+		    method: 'POST',
+		    timeout: 5000, // it'll delay both success & failure
+		    success: function(resp, opts) {
 			view.el.unmask();
-			var handler = function() {
-			    var uf = me.lookupReference('usernameField');
-			    uf.focus(true, true);
-			};
-
-			Ext.MessageBox.alert(gettext('Error'),
-					     gettext("Login failed. Please try again"),
-					     handler);
+			// Fill in what we copy over from the 1st factor:
+			var data = resp.result.data;
+			data.CSRFPreventionToken = Proxmox.CSRFPreventionToken;
+			data.username = Proxmox.UserName;
+			// Finish logging in:
+			success_fn(data);
 		    },
-		    success: function(f, resp){
-			view.el.unmask();
-
-			var handler = view.handler || Ext.emptyFn;
-			handler.call(me, resp.result.data);
-			view.close();
+		    failure: function(resp, opts) {
+			Proxmox.Utils.authClear();
+			failure_fn(resp);
 		    }
 		});
-	    }
+	    };
 	},
 
 	control: {
-- 
2.11.0





More information about the pve-devel mailing list