X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=www%2Fmanager6%2Fwindow%2FLoginWindow.js;h=4a07f75bf75dcbe50d6950600dbc281ac1422bd6;hb=0489947166d5e9c0f0c04aef43ce334571e0b54b;hp=683fb54cfa3eb515b71049edf1797a78f85a629d;hpb=145806532d4f9a32eebf1d291bafc584ed60271e;p=pve-manager.git diff --git a/www/manager6/window/LoginWindow.js b/www/manager6/window/LoginWindow.js index 683fb54c..4a07f75b 100644 --- a/www/manager6/window/LoginWindow.js +++ b/www/manager6/window/LoginWindow.js @@ -1,11 +1,27 @@ +/*global u2f*/ Ext.define('PVE.window.LoginWindow', { extend: 'Ext.window.Window', + viewModel: { + data: { + openid: false, + }, + formulas: { + button_text: function(get) { + if (get("openid") === true) { + return gettext("Login (OpenID redirect)"); + } else { + return gettext("Login"); + } + }, + }, + }, + controller: { xclass: 'Ext.app.ViewController', - onLogon: function() { + onLogon: async function() { var me = this; var form = this.lookupReference('loginForm'); @@ -13,39 +29,200 @@ 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; + } - // 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){ - 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); + let creds = form.getValues(); + + if (this.getViewModel().data.openid === true) { + const redirectURL = location.origin; + Proxmox.Utils.API2Request({ + url: '/api2/extjs/access/openid/auth-url', + params: { + realm: creds.realm, + "redirect-url": redirectURL, }, - success: function(f, resp){ - view.el.unmask(); + method: 'POST', + success: function(resp, opts) { + window.location = resp.result.data; + }, + failure: function(resp, opts) { + Proxmox.Utils.authClear(); + form.unmask(); + Ext.MessageBox.alert( + gettext('Error'), + gettext('OpenID redirect failed.') + `
${resp.htmlStatus}`, + ); + }, + }); + return; + } - var handler = view.handler || Ext.emptyFn; - handler.call(me, resp.result.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()); + + try { + // Request updated authentication mechanism: + creds['new-format'] = 1; + + let resp = await Proxmox.Async.api2({ + url: '/api2/extjs/access/ticket', + params: creds, + method: 'POST', }); + + let data = resp.result.data; + if (data.ticket.startsWith("PVE:!tfa!")) { + // Store first factor login information first: + data.LoggedOut = true; + Proxmox.Utils.setAuthData(data); + + data = await me.performTFAChallenge(data); + + // Fill in what we copy over from the 1st factor: + data.CSRFPreventionToken = Proxmox.CSRFPreventionToken; + data.username = Proxmox.UserName; + me.success(data); + } else if (Ext.isDefined(data.NeedTFA)) { + // Store first factor login information first: + data.LoggedOut = true; + Proxmox.Utils.setAuthData(data); + + if (Ext.isDefined(data.U2FChallenge)) { + me.perform_u2f(data); + } else { + me.perform_otp(); + } + } else { + me.success(data); + } + } catch (error) { + me.failure(error); + } + }, + + /* START NEW TFA CODE (pbs copy) */ + performTFAChallenge: async function(data) { + let me = this; + + let userid = data.username; + let ticket = data.ticket; + let challenge = JSON.parse(decodeURIComponent( + ticket.split(':')[1].slice("!tfa!".length), + )); + + let resp = await new Promise((resolve, reject) => { + Ext.create('Proxmox.window.TfaLoginWindow', { + userid, + ticket, + challenge, + onResolve: value => resolve(value), + onReject: reject, + }).show(); + }); + + return resp.result.data; + }, + /* END NEW TFA CODE (pbs copy) */ + + failure: function(resp) { + var me = this; + var view = me.getView(); + view.el.unmask(); + var handler = function() { + var uf = me.lookupReference('usernameField'); + uf.focus(true, true); + }; + + let emsg = gettext("Login failed. Please try again"); + + if (resp.failureType === "connect") { + emsg = gettext("Connection failure. Network error or Proxmox VE services not running?"); } + + Ext.MessageBox.alert(gettext('Error'), emsg, handler); + }, + success: function(data) { + var me = this; + var view = me.getView(); + var handler = view.handler || Ext.emptyFn; + handler.call(me, data); + view.close(); + }, + + perform_otp: function() { + var me = this; + var win = Ext.create('PVE.window.TFALoginWindow', { + onLogin: function(value) { + me.finish_tfa(value); + }, + onCancel: function() { + Proxmox.LoggedOut = false; + Proxmox.Utils.authClear(); + me.getView().show(); + }, + }); + win.show(); + }, + + perform_u2f: function(data) { + var me = this; + // 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'), PVE.Utils.render_u2f_error(res.errorCode)); + return; + } + delete res.errorCode; + me.finish_tfa(JSON.stringify(res)); + }); + }, + finish_tfa: function(res) { + var me = this; + var view = me.getView(); + view.el.mask(gettext('Please wait...'), 'x-mask-loading'); + Proxmox.Utils.API2Request({ + url: '/api2/extjs/access/tfa', + params: { + response: res, + }, + method: 'POST', + timeout: 5000, // it'll delay both success & failure + success: function(resp, opts) { + view.el.unmask(); + // 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: + me.success(data); + }, + failure: function(resp, opts) { + Proxmox.Utils.authClear(); + me.failure(resp); + }, + }); }, control: { @@ -57,19 +234,7 @@ Ext.define('PVE.window.LoginWindow', { pf.focus(false); } } - } - }, - 'field[name=realm]': { - change: function(f, value) { - var otp_field = this.lookupReference('otpField'); - if (f.needOTP(value)) { - otp_field.setVisible(true); - otp_field.setDisabled(false); - } else { - otp_field.setVisible(false); - otp_field.setDisabled(true); - } - } + }, }, 'field[name=lang]': { change: function(f, value) { @@ -77,13 +242,23 @@ Ext.define('PVE.window.LoginWindow', { Ext.util.Cookies.set('PVELangCookie', value, dt); this.getView().mask(gettext('Please wait...'), 'x-mask-loading'); window.location.reload(); - } + }, + }, + 'field[name=realm]': { + change: function(f, value) { + let record = f.store.getById(value); + if (record === undefined) return; + let data = record.data; + this.getViewModel().set("openid", data.type === "openid"); + }, + }, + 'button[reference=loginButton]': { + click: 'onLogon', }, - 'button[reference=loginButton]': { - click: 'onLogon' - }, '#': { show: function() { + var me = this; + var sp = Ext.state.Manager.getProvider(); var checkboxField = this.lookupReference('saveunField'); var unField = this.lookupReference('usernameField'); @@ -91,35 +266,63 @@ Ext.define('PVE.window.LoginWindow', { var checked = sp.get(checkboxField.getStateId()); checkboxField.setValue(checked); - if(checked === true) { + if (checked === true) { var username = sp.get(unField.getStateId()); unField.setValue(username); var pwField = this.lookupReference('passwordField'); pwField.focus(); } - } - } - } + + let auth = Proxmox.Utils.getOpenIDRedirectionAuthorization(); + if (auth !== undefined) { + Proxmox.Utils.authClear(); + + let loginForm = this.lookupReference('loginForm'); + loginForm.mask(gettext('OpenID login - please wait...'), 'x-mask-loading'); + + const redirectURL = location.origin; + + Proxmox.Utils.API2Request({ + url: '/api2/extjs/access/openid/login', + params: { + state: auth.state, + code: auth.code, + "redirect-url": redirectURL, + }, + method: 'POST', + failure: function(response) { + loginForm.unmask(); + let error = response.htmlStatus; + Ext.MessageBox.alert( + gettext('Error'), + gettext('OpenID login failed, please try again') + `
${error}`, + () => { window.location = redirectURL; }, + ); + }, + success: function(response, options) { + loginForm.unmask(); + let data = response.result.data; + history.replaceState(null, '', redirectURL); + me.success(data); + }, + }); + } + }, + }, + }, }, width: 400, - modal: true, - border: false, - draggable: true, - closable: false, - resizable: false, - layout: 'auto', title: gettext('Proxmox VE Login'), defaultFocus: 'usernameField', - defaultButton: 'loginButton', items: [{ @@ -130,7 +333,7 @@ Ext.define('PVE.window.LoginWindow', { fieldDefaults: { labelAlign: 'right', - allowBlank: false + allowBlank: false, }, items: [ @@ -140,35 +343,35 @@ Ext.define('PVE.window.LoginWindow', { name: 'username', itemId: 'usernameField', reference: 'usernameField', - stateId: 'login-username' + stateId: 'login-username', + bind: { + visible: "{!openid}", + disabled: "{openid}", + }, }, { xtype: 'textfield', inputType: 'password', fieldLabel: gettext('Password'), name: 'password', - reference: 'passwordField' - }, - { - xtype: 'textfield', - fieldLabel: gettext('OTP'), - name: 'otp', - reference: 'otpField', - allowBlank: false, - hidden: true + reference: 'passwordField', + bind: { + visible: "{!openid}", + disabled: "{openid}", + }, }, { - xtype: 'pveRealmComboBox', - name: 'realm' + xtype: 'pmxRealmComboBox', + name: 'realm', }, { xtype: 'proxmoxLanguageSelector', fieldLabel: gettext('Language'), - value: Ext.util.Cookies.get('PVELangCookie') || 'en', + value: Ext.util.Cookies.get('PVELangCookie') || Proxmox.defaultLang || 'en', name: 'lang', reference: 'langField', - submitValue: false - } + submitValue: false, + }, ], buttons: [ { @@ -177,14 +380,19 @@ Ext.define('PVE.window.LoginWindow', { name: 'saveusername', reference: 'saveunField', stateId: 'login-saveusername', - labelWidth: 'auto', + labelWidth: 250, labelAlign: 'right', - submitValue: false + submitValue: false, + bind: { + visible: "{!openid}", + }, }, { - text: gettext('Login'), - reference: 'loginButton' - } - ] - }] + bind: { + text: "{button_text}", + }, + reference: 'loginButton', + }, + ], + }], });