Signed-off-by: Wolfgang Bumiller <w.bumil...@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@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 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel