]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/window/LoginWindow.js
ui: resource tree settings: use the safer hash as bogus submit URL
[pve-manager.git] / www / manager6 / window / LoginWindow.js
index 333144470bcf9686a78ca9f24f671a9da4254bd7..4a07f75bf75dcbe50d6950600dbc281ac1422bd6 100644 (file)
+/*global u2f*/
 Ext.define('PVE.window.LoginWindow', {
     extend: 'Ext.window.Window',
 
-    // private
-    onLogon: function() {
-       var me = this;
+    viewModel: {
+       data: {
+           openid: false,
+       },
+       formulas: {
+           button_text: function(get) {
+               if (get("openid") === true) {
+                   return gettext("Login (OpenID redirect)");
+               } else {
+                   return gettext("Login");
+               }
+           },
+       },
+    },
 
-       var form = me.getComponent(0).getForm();
+    controller: {
 
-       if(form.isValid()){
-            me.el.mask(gettext('Please wait...'), 'x-mask-loading');
+       xclass: 'Ext.app.ViewController',
 
-           form.submit({
-               failure: function(f, resp){
-                   me.el.unmask();
-                   Ext.MessageBox.alert(gettext('Error'), 
-                                        gettext("Login failed. Please try again"), 
-                                        function() {
-                       var uf = form.findField('username');
-                       uf.focus(true, true);
-                   });
-               },
-               success: function(f, resp){
-                   me.el.unmask();
-                   
-                   var handler = me.handler || Ext.emptyFn;
-                   handler.call(me, resp.result.data);
-                   me.close();
+       onLogon: async function() {
+           var me = this;
+
+           var form = this.lookupReference('loginForm');
+           var unField = this.lookupReference('usernameField');
+           var saveunField = this.lookupReference('saveunField');
+           var view = this.getView();
+
+           if (!form.isValid()) {
+               return;
+           }
+
+           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,
+                   },
+                   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.') + `<br>${resp.htmlStatus}`,
+                       );
+                   },
+               });
+               return;
+           }
+
+           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();
            });
-       }
-    },
 
-    initComponent: function() {
-       var me = this;
+           return resp.result.data;
+       },
+       /* END NEW TFA CODE (pbs copy) */
 
-       var otp_field = Ext.createWidget('textfield', { 
-           fieldLabel: gettext('OTP'), 
-           name: 'otp',
-           allowBlank: false,
-           hidden: true
-       });
-
-       Ext.apply(me, {
-           width: 400,
-           modal: true,
-           border: false,
-           draggable: true,
-           closable: false,
-           resizable: false,
-           layout: 'auto',
-           title: gettext('Proxmox VE Login'),
-
-           items: [{
-               xtype: 'form',
-               frame: true,
-               url: '/api2/extjs/access/ticket',
-
-               fieldDefaults: {
-                   labelAlign: 'right'
+       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();
+       },
 
-               defaults: {
-                   anchor: '-5',
-                   allowBlank: false
+       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,
                },
-               
-               items: [
-                   { 
-                       xtype: 'textfield', 
-                       fieldLabel: gettext('User name'), 
-                       name: 'username',
-                       blankText: gettext("Enter your user name"),
-                       listeners: {
-                           afterrender: function(f) {
-                               // Note: only works if we pass delay 1000
-                               f.focus(true, 1000);
-                           },
-                           specialkey: function(f, e) {
-                               if (e.getKey() === e.ENTER) {
-                                   var pf = me.query('textfield[name="password"]')[0];
-                                   if (pf.getValue()) {
-                                       me.onLogon();
-                                   } else {
-                                       pf.focus(false);
-                                   }
-                               }
-                           }
-                       }
-                   },
-                   { 
-                       xtype: 'textfield', 
-                       inputType: 'password',
-                       fieldLabel: gettext('Password'), 
-                       name: 'password',
-                       blankText: gettext("Enter your password"),
-                       listeners: {
-                           specialkey: function(field, e) {
-                               if (e.getKey() === e.ENTER) {
-                                   me.onLogon();
-                               }
-                           }
-                       }
-                   },
-                   otp_field,
-                   {
-                       xtype: 'pveRealmComboBox',
-                       name: 'realm',
-                       listeners: {
-                           change: function(f, value) {
-                               if (f.needOTP(value)) {
-                                   otp_field.setVisible(true);
-                                   otp_field.setDisabled(false);
-                               } else {
-                                   otp_field.setVisible(false);
-                                   otp_field.setDisabled(true);
-                               }
-                           }
-                       }
-                   },
-                   {   
-                       xtype: 'pveLanguageSelector',
-                       fieldLabel: gettext('Language'), 
-                       value: Ext.util.Cookies.get('PVELangCookie') || 'en',
-                       name: 'lang',
-                       submitValue: false,
-                       listeners: {
-                           change: function(t, value) {
-                               var dt = Ext.Date.add(new Date(), Ext.Date.YEAR, 10);
-                               Ext.util.Cookies.set('PVELangCookie', value, dt);
-                               me.el.mask(gettext('Please wait...'), 'x-mask-loading');
-                               window.location.reload();
-                           }
+               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: {
+           'field[name=username]': {
+               specialkey: function(f, e) {
+                   if (e.getKey() === e.ENTER) {
+                       var pf = this.lookupReference('passwordField');
+                       if (!pf.getValue()) {
+                           pf.focus(false);
                        }
                    }
-               ],
-               buttons: [
-                   {
-                       text: gettext('Login'),
-                       handler: function(){
-                           me.onLogon();
-                       }
+               },
+           },
+           'field[name=lang]': {
+               change: function(f, value) {
+                   var dt = Ext.Date.add(new Date(), Ext.Date.YEAR, 10);
+                   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',
+           },
+           '#': {
+               show: function() {
+                   var me = this;
+
+                   var sp = Ext.state.Manager.getProvider();
+                   var checkboxField = this.lookupReference('saveunField');
+                   var unField = this.lookupReference('usernameField');
+
+                   var checked = sp.get(checkboxField.getStateId());
+                   checkboxField.setValue(checked);
+
+                   if (checked === true) {
+                       var username = sp.get(unField.getStateId());
+                       unField.setValue(username);
+                       var pwField = this.lookupReference('passwordField');
+                       pwField.focus();
                    }
-               ]
-           }]
-       });
 
-       me.callParent();
-    }
-});
+                   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') + `<br>${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: [{
+       xtype: 'form',
+       layout: 'form',
+       url: '/api2/extjs/access/ticket',
+       reference: 'loginForm',
+
+       fieldDefaults: {
+           labelAlign: 'right',
+           allowBlank: false,
+       },
+
+       items: [
+           {
+               xtype: 'textfield',
+               fieldLabel: gettext('User name'),
+               name: 'username',
+               itemId: 'usernameField',
+               reference: 'usernameField',
+               stateId: 'login-username',
+               bind: {
+                   visible: "{!openid}",
+                   disabled: "{openid}",
+               },
+           },
+           {
+               xtype: 'textfield',
+               inputType: 'password',
+               fieldLabel: gettext('Password'),
+               name: 'password',
+               reference: 'passwordField',
+               bind: {
+                   visible: "{!openid}",
+                   disabled: "{openid}",
+               },
+           },
+           {
+               xtype: 'pmxRealmComboBox',
+               name: 'realm',
+           },
+           {
+               xtype: 'proxmoxLanguageSelector',
+               fieldLabel: gettext('Language'),
+               value: Ext.util.Cookies.get('PVELangCookie') || Proxmox.defaultLang || 'en',
+               name: 'lang',
+               reference: 'langField',
+               submitValue: false,
+           },
+       ],
+       buttons: [
+           {
+               xtype: 'checkbox',
+               fieldLabel: gettext('Save User name'),
+               name: 'saveusername',
+               reference: 'saveunField',
+               stateId: 'login-saveusername',
+               labelWidth: 250,
+               labelAlign: 'right',
+               submitValue: false,
+               bind: {
+                   visible: "{!openid}",
+               },
+           },
+           {
+               bind: {
+                   text: "{button_text}",
+               },
+               reference: 'loginButton',
+           },
+       ],
+    }],
+ });