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');
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
}
sp.set(saveunField.getStateId(), saveunField.getValue());
- form.submit({
- failure: function(f, resp){
- me.failure(resp);
- },
- success: function(f, resp){
- view.el.unmask();
+ 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);
- var data = resp.result.data;
if (Ext.isDefined(data.U2FChallenge)) {
me.perform_u2f(data);
} else {
- me.success(data);
+ 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();
uf.focus(true, true);
};
- Ext.MessageBox.alert(gettext('Error'),
- gettext("Login failed. Please try again"),
- handler);
+ 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;
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;
- // 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: []
+ buttons: [],
});
var chlg = data.U2FChallenge;
var key = {
version: chlg.version,
- keyHandle: chlg.keyHandle
+ 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);
+ Ext.Msg.alert(gettext('Error'), PVE.Utils.render_u2f_error(res.errorCode));
return;
}
delete res.errorCode;
- me.finish_u2f(res);
+ me.finish_tfa(JSON.stringify(res));
});
},
- finish_u2f: function(res) {
+ finish_tfa: function(res) {
var me = this;
var view = me.getView();
view.el.mask(gettext('Please wait...'), 'x-mask-loading');
- var params = { response: JSON.stringify(res) };
Proxmox.Utils.API2Request({
url: '/api2/extjs/access/tfa',
- params: params,
+ params: {
+ response: res,
+ },
method: 'POST',
timeout: 5000, // it'll delay both success & failure
success: function(resp, opts) {
failure: function(resp, opts) {
Proxmox.Utils.authClear();
me.failure(resp);
- }
+ },
});
},
pf.focus(false);
}
}
- }
- },
- 'field[name=realm]': {
- change: function(f, value) {
- var otp_field = this.lookupReference('otpField');
- if (f.needOTP(value)) {
- otp_field.setConfig('allowBlank', false);
- otp_field.setEmptyText(gettext('2nd factor'));
- } else {
- otp_field.setConfig('allowBlank', true);
- otp_field.setEmptyText(gettext('2nd factor, if required'));
- }
- otp_field.validate();
- }
+ },
},
'field[name=lang]': {
change: function(f, value) {
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');
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') + `<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: [{
fieldDefaults: {
labelAlign: 'right',
- allowBlank: false
+ allowBlank: false,
},
items: [
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: true,
- emptyText: gettext('2nd factor, if required')
+ reference: 'passwordField',
+ bind: {
+ visible: "{!openid}",
+ disabled: "{openid}",
+ },
},
{
- xtype: 'pveRealmComboBox',
- name: 'realm'
+ xtype: 'pmxRealmComboBox',
+ name: 'realm',
},
{
xtype: 'proxmoxLanguageSelector',
value: Ext.util.Cookies.get('PVELangCookie') || Proxmox.defaultLang || 'en',
name: 'lang',
reference: 'langField',
- submitValue: false
- }
+ submitValue: false,
+ },
],
buttons: [
{
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',
+ },
+ ],
+ }],
});