[pve-devel] [PATCH v2 manager] ui: auth: add api token authentication to login window

Thomas Lamprecht t.lamprecht at proxmox.com
Tue Jun 30 14:50:35 CEST 2020


On 07.05.20 14:28, Tim Marx wrote:
> Signed-off-by: Tim Marx <t.marx at proxmox.com>
> ---
> 
> Notes:
>     changed since v1:
>     * store token including the product prefix
>     * add token id to username to make it more obvious that it's note the actual user
> 
>  www/manager6/Workspace.js          |   5 ++
>  www/manager6/window/LoginWindow.js | 129 ++++++++++++++++++++++-------
>  2 files changed, 105 insertions(+), 29 deletions(-)
> 
> diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js
> index 57cb1bb9..b03443cb 100644
> --- a/www/manager6/Workspace.js
> +++ b/www/manager6/Workspace.js
> @@ -73,6 +73,11 @@ Ext.define('PVE.Workspace', {
>  
>  	me.callParent();
>  
> +	var storedUser = Proxmox.Utils.getStoredUser();

please use `let` and rebase to match the rename to getStoredAuth

> +	if (storedUser.username) {
> +	    Proxmox.UserName = storedUser.username;
> +	}
> +
>          if (!Proxmox.Utils.authOK()) {
>  	    me.showLogin();
>  	} else {
> diff --git a/www/manager6/window/LoginWindow.js b/www/manager6/window/LoginWindow.js
> index e29b7352..223ad581 100644
> --- a/www/manager6/window/LoginWindow.js
> +++ b/www/manager6/window/LoginWindow.js
> @@ -12,6 +12,8 @@ Ext.define('PVE.window.LoginWindow', {
>  	    var form = this.lookupReference('loginForm');
>  	    var unField = this.lookupReference('usernameField');
>  	    var saveunField = this.lookupReference('saveunField');
> +	    var tField = this.lookupReference('apitokenField');

tField is not an acceptable variable name, I mean, the existing unField
isn't either but I'd rather change that too instead of adapting to it.
use something like 'tokenField'

> +
>  	    var view = this.getView();
>  
>  	    if (!form.isValid()) {
> @@ -20,38 +22,60 @@ Ext.define('PVE.window.LoginWindow', {
>  
>  	    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());
> +	    if (tField.value !== '') {
> +		var splitToken = tField.value.match(/^(.*)=(.*)$/);
> +		Proxmox.Utils.API2Request({
> +		    url: '/api2/extjs/access/uicapabilities',

Hmm, do I miss a access-control API patch? As uicapabilities isn't available.

> +		    headers:{
> +			Authorization: 'PVEAPIToken=' + tField.value
> +		    },
> +		    success: function(response, opts) {
> +			var data = {
> +			    username: splitToken[1],
> +			    token: 'PVEAPIToken=' + tField.value,
> +			    cap: response.result.data.cap
> +			};
> +			me.success(data);
> +		    },
> +
> +		    failure: function(response, opts) {
> +			me.failure(response);
> +		    }
> +		});
>  	    } else {
> -		sp.clear(unField.getStateId());
> -	    }
> -	    sp.set(saveunField.getStateId(), saveunField.getValue());
> +		// 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){
> -		    me.failure(resp);
> -		},
> -		success: function(f, resp){
> -		    view.el.unmask();
> +		form.submit({
> +		    failure: function(f, resp){
> +			me.failure(resp);
> +		    },
> +		    success: function(f, resp){
> +			view.el.unmask();
>  
> -		    var data = resp.result.data;
> -		    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.NeedTFA)) {
> +			    // Store first factor login information first:
> +			    data.LoggedOut = true;
> +			    Proxmox.Utils.setAuthData(data);
>  
> -			if (Ext.isDefined(data.U2FChallenge)) {
> -			    me.perform_u2f(data);
> +			    if (Ext.isDefined(data.U2FChallenge)) {
> +				me.perform_u2f(data);
> +			    } else {
> +				me.perform_otp();
> +			    }
>  			} else {
> -			    me.perform_otp();
> +			    me.success(data);
>  			}
> -		    } else {
> -			me.success(data);
>  		    }
> -		}
> -	    });
> +		});
> +	    }
>  
>  	},
>  	failure: function(resp) {
> @@ -143,6 +167,31 @@ Ext.define('PVE.window.LoginWindow', {
>  		}
>  	    });
>  	},
> +	onUsetokenChange: function(value) {
> +	    var uField = this.lookupReference('usernameField');
> +	    var svunField = this.lookupReference('saveunField');
> +	    var pField = this.lookupReference('passwordField');
> +	    var rField = this.lookupReference('realmField');
> +	    var tField = this.lookupReference('apitokenField');

argh, ugly stuff, I do not want to guess around what variables could be, from
top-of-my-head suggesting:

usernameField
savedUsernameField
passwordField
realmField
tokenField

> +
> +	    uField.setVisible(!value.checked);
> +	    uField.setDisabled(value.checked);
> +
> +	    svunField.setVisible(!value.checked);
> +	    svunField.setDisabled(value.checked);
> +
> +	    pField.setVisible(!value.checked);
> +	    pField.setDisabled(value.checked);
> +
> +	    rField.setVisible(!value.checked);
> +	    rField.setDisabled(value.checked);
> +
> +
> +
> +	    tField.setVisible(value.checked);
> +	    tField.setDisabled(!value.checked);
> +
> +	},
>  
>  	control: {
>  	    'field[name=username]': {
> @@ -198,7 +247,20 @@ Ext.define('PVE.window.LoginWindow', {
>  
>      defaultFocus: 'usernameField',
>      defaultButton: 'loginButton',
> -
> +    tools: [
> +	{
> +	    xtype: 'checkbox',
> +	    fieldLabel: gettext('Use API Token'),
> +	    name: 'usetoken',
> +	    reference: 'usetoken',
> +	    stateId: 'login-usetoken',
> +	    labelAlign: 'right',
> +	    submitValue: false,
> +	    listeners: {
> +		change: 'onUsetokenChange'
> +	    }
> +	}

IMO the login field is already complicated enough. I'd rather detect that
from the username field, i.e., if something matching the token format is entered
there.

> +    ],
>      items: [{
>  	xtype: 'form',
>  	layout: 'form',
> @@ -209,7 +271,6 @@ Ext.define('PVE.window.LoginWindow', {
>  	    labelAlign: 'right',
>  	    allowBlank: false
>  	},
> -
>  	items: [
>  	    {
>  		xtype: 'textfield',
> @@ -228,7 +289,17 @@ Ext.define('PVE.window.LoginWindow', {
>  	    },
>  	    {
>  		xtype: 'pveRealmComboBox',
> -		name: 'realm'
> +		name: 'realm',
> +		reference: 'realmField'
> +	    },
> +	    {
> +		xtype: 'textfield',
> +		fieldLabel: gettext('API Token'),
> +		name: 'apitoken',
> +		reference: 'apitokenField',
> +		emptyText: 'USER at REALM!TOKENID=UUID',
> +		hidden: true,
> +		disabled: true
>  	    },
>  	    {
>  		xtype: 'proxmoxLanguageSelector',
> @@ -246,8 +317,8 @@ Ext.define('PVE.window.LoginWindow', {
>  		name: 'saveusername',
>  		reference: 'saveunField',
>  		stateId: 'login-saveusername',
> -		labelWidth: 250,
>  		labelAlign: 'right',
> +		labelWidth: 250,
>  		submitValue: false
>  	    },
>  	    {
> 





More information about the pve-devel mailing list