]> git.proxmox.com Git - proxmox-widget-toolkit.git/commitdiff
add totp, wa and recovery creation and tfa edit windows
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 9 Nov 2021 11:27:19 +0000 (12:27 +0100)
committerDominik Csapak <d.csapak@proxmox.com>
Wed, 10 Nov 2021 07:58:44 +0000 (08:58 +0100)
plain copy from pbs with s/pbs/pmx/ and s/PBS/Proxmox/

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
src/Makefile
src/window/AddTfaRecovery.js [new file with mode: 0644]
src/window/AddTotp.js [new file with mode: 0644]
src/window/AddWebauthn.js [new file with mode: 0644]
src/window/TfaEdit.js [new file with mode: 0644]

index 059b5c65d4f58ccc02b1fbf314e65a5795d4290a..fa36d06e4356dd0b0f7689de62b8a1f0dd97b3e1 100644 (file)
@@ -79,6 +79,10 @@ JSSRC=                                       \
        window/AuthEditBase.js          \
        window/AuthEditOpenId.js        \
        window/TfaWindow.js             \
+       window/AddTfaRecovery.js        \
+       window/AddTotp.js               \
+       window/AddWebauthn.js           \
+       window/TfaEdit.js               \
        node/APT.js                     \
        node/APTRepositories.js         \
        node/NetworkEdit.js             \
diff --git a/src/window/AddTfaRecovery.js b/src/window/AddTfaRecovery.js
new file mode 100644 (file)
index 0000000..174d553
--- /dev/null
@@ -0,0 +1,224 @@
+Ext.define('Proxmox.window.AddTfaRecovery', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pmxAddTfaRecovery',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onlineHelp: 'user_mgmt',
+    isCreate: true,
+    isAdd: true,
+    subject: gettext('TFA recovery keys'),
+    width: 512,
+    method: 'POST',
+
+    fixedUser: false,
+
+    url: '/api2/extjs/access/tfa',
+    submitUrl: function(url, values) {
+       let userid = values.userid;
+       delete values.userid;
+       return `${url}/${userid}`;
+    },
+
+    apiCallDone: function(success, response) {
+       if (!success) {
+           return;
+       }
+
+       let values = response
+           .result
+           .data
+           .recovery
+           .map((v, i) => `${i}: ${v}`)
+           .join("\n");
+       Ext.create('Proxmox.window.TfaRecoveryShow', {
+           autoShow: true,
+           userid: this.getViewModel().get('userid'),
+           values,
+       });
+    },
+
+    viewModel: {
+       data: {
+           has_entry: false,
+           userid: null,
+       },
+    },
+
+    controller: {
+       xclass: 'Ext.app.ViewController',
+       hasEntry: async function(userid) {
+           let me = this;
+           let view = me.getView();
+
+           try {
+               await Proxmox.Async.api2({
+                   url: `${view.url}/${userid}/recovery`,
+                   method: 'GET',
+               });
+               return true;
+           } catch (_response) {
+               return false;
+           }
+       },
+
+       init: function(view) {
+           this.onUseridChange(null, Proxmox.UserName);
+       },
+
+       onUseridChange: async function(field, userid) {
+           let me = this;
+           let vm = me.getViewModel();
+
+           me.userid = userid;
+           vm.set('userid', userid);
+
+           let has_entry = await me.hasEntry(userid);
+           vm.set('has_entry', has_entry);
+       },
+    },
+
+    items: [
+       {
+           xtype: 'pmxDisplayEditField',
+           name: 'userid',
+           cbind: {
+               editable: (get) => !get('fixedUser'),
+               value: () => Proxmox.UserName,
+           },
+           fieldLabel: gettext('User'),
+           editConfig: {
+               xtype: 'pmxUserSelector',
+               allowBlank: false,
+               validator: function(_value) {
+                   return !this.up('window').getViewModel().get('has_entry');
+               },
+           },
+           renderer: Ext.String.htmlEncode,
+           listeners: {
+               change: 'onUseridChange',
+           },
+       },
+       {
+           xtype: 'hiddenfield',
+           name: 'type',
+           value: 'recovery',
+       },
+       {
+           xtype: 'displayfield',
+           bind: {
+               hidden: '{!has_entry}',
+           },
+           hidden: true,
+           userCls: 'pmx-hint',
+           value: gettext('User already has recovery keys.'),
+       },
+       {
+           xtype: 'textfield',
+           name: 'password',
+           reference: 'password',
+           fieldLabel: gettext('Verify Password'),
+           inputType: 'password',
+           minLength: 5,
+           allowBlank: false,
+           validateBlank: true,
+           cbind: {
+               hidden: () => Proxmox.UserName === 'root@pam',
+               disabled: () => Proxmox.UserName === 'root@pam',
+               emptyText: () =>
+                   Ext.String.format(gettext("Confirm your ({0}) password"), Proxmox.UserName),
+           },
+       },
+    ],
+});
+
+Ext.define('Proxmox.window.TfaRecoveryShow', {
+    extend: 'Ext.window.Window',
+    alias: ['widget.pmxTfaRecoveryShow'],
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    width: 600,
+    modal: true,
+    resizable: false,
+    title: gettext('Recovery Keys'),
+    onEsc: Ext.emptyFn,
+
+    items: [
+       {
+           xtype: 'form',
+           layout: 'anchor',
+           bodyPadding: 10,
+           border: false,
+           fieldDefaults: {
+               anchor: '100%',
+            },
+           items: [
+               {
+                   xtype: 'textarea',
+                   editable: false,
+                   inputId: 'token-secret-value',
+                   cbind: {
+                       value: '{values}',
+                   },
+                   fieldStyle: {
+                       'fontFamily': 'monospace',
+                   },
+                   height: '160px',
+               },
+               {
+                   xtype: 'displayfield',
+                   border: false,
+                   padding: '5 0 0 0',
+                   userCls: 'pmx-hint',
+                   value: gettext('Please record recovery keys - they will only be displayed now'),
+               },
+           ],
+       },
+    ],
+    buttons: [
+       {
+           handler: function(b) {
+               document.getElementById('token-secret-value').select();
+               document.execCommand("copy");
+           },
+           iconCls: 'fa fa-clipboard',
+           text: gettext('Copy Recovery Keys'),
+       },
+       {
+           handler: function(b) {
+               let win = this.up('window');
+               win.paperkeys(win.values, win.userid);
+           },
+           iconCls: 'fa fa-print',
+           text: gettext('Print Recovery Keys'),
+       },
+    ],
+    paperkeys: function(keyString, userid) {
+       let me = this;
+
+       let printFrame = document.createElement("iframe");
+       Object.assign(printFrame.style, {
+           position: "fixed",
+           right: "0",
+           bottom: "0",
+           width: "0",
+           height: "0",
+           border: "0",
+       });
+       const host = document.location.host;
+       const title = document.title;
+       const html = `<html><head><script>
+           window.addEventListener('DOMContentLoaded', (ev) => window.print());
+       </script><style>@media print and (max-height: 150mm) {
+         h4, p { margin: 0; font-size: 1em; }
+       }</style></head><body style="padding: 5px;">
+       <h4>Recovery Keys for '${userid}' - ${title} (${host})</h4>
+<p style="font-size:1.5em;line-height:1.5em;font-family:monospace;
+   white-space:pre-wrap;overflow-wrap:break-word;">
+${keyString}
+</p>
+       </body></html>`;
+
+       printFrame.src = "data:text/html;base64," + btoa(html);
+       document.body.appendChild(printFrame);
+    },
+});
diff --git a/src/window/AddTotp.js b/src/window/AddTotp.js
new file mode 100644 (file)
index 0000000..3e0f5b5
--- /dev/null
@@ -0,0 +1,297 @@
+/*global QRCode*/
+Ext.define('Proxmox.window.AddTotp', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pmxAddTotp',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onlineHelp: 'user_mgmt',
+
+    modal: true,
+    resizable: false,
+    title: gettext('Add a TOTP login factor'),
+    width: 512,
+    layout: {
+       type: 'vbox',
+       align: 'stretch',
+    },
+
+    isAdd: true,
+    userid: undefined,
+    tfa_id: undefined,
+    issuerName: 'Proxmox',
+    fixedUser: false,
+
+    updateQrCode: function() {
+       let me = this;
+       let values = me.lookup('totp_form').getValues();
+       let algorithm = values.algorithm;
+       if (!algorithm) {
+           algorithm = 'SHA1';
+       }
+
+       let otpuri =
+           'otpauth://totp/' +
+           encodeURIComponent(values.issuer) +
+           ':' +
+           encodeURIComponent(values.userid) +
+           '?secret=' + values.secret +
+           '&period=' + values.step +
+           '&digits=' + values.digits +
+           '&algorithm=' + algorithm +
+           '&issuer=' + encodeURIComponent(values.issuer);
+
+       me.getController().getViewModel().set('otpuri', otpuri);
+       me.qrcode.makeCode(otpuri);
+       me.lookup('challenge').setVisible(true);
+       me.down('#qrbox').setVisible(true);
+    },
+
+    viewModel: {
+       data: {
+           valid: false,
+           secret: '',
+           otpuri: '',
+           userid: null,
+       },
+
+       formulas: {
+           secretEmpty: function(get) {
+               return get('secret').length === 0;
+           },
+       },
+    },
+
+    controller: {
+       xclass: 'Ext.app.ViewController',
+       control: {
+           'field[qrupdate=true]': {
+               change: function() {
+                   this.getView().updateQrCode();
+               },
+           },
+           'field': {
+               validitychange: function(field, valid) {
+                   let me = this;
+                   let viewModel = me.getViewModel();
+                   let form = me.lookup('totp_form');
+                   let challenge = me.lookup('challenge');
+                   let password = me.lookup('password');
+                   viewModel.set('valid', form.isValid() && challenge.isValid() && password.isValid());
+               },
+           },
+           '#': {
+               show: function() {
+                   let me = this;
+                   let view = me.getView();
+
+                   view.qrdiv = document.createElement('div');
+                   view.qrcode = new QRCode(view.qrdiv, {
+                       width: 256,
+                       height: 256,
+                       correctLevel: QRCode.CorrectLevel.M,
+                   });
+                   view.down('#qrbox').getEl().appendChild(view.qrdiv);
+
+                   view.getController().randomizeSecret();
+               },
+           },
+       },
+
+       randomizeSecret: function() {
+           let me = this;
+           let rnd = new Uint8Array(32);
+           window.crypto.getRandomValues(rnd);
+           let data = '';
+           rnd.forEach(function(b) {
+               // secret must be base32, so just use the first 5 bits
+               b = b & 0x1f;
+               if (b < 26) {
+                   // A..Z
+                   data += String.fromCharCode(b + 0x41);
+               } else {
+                   // 2..7
+                   data += String.fromCharCode(b-26 + 0x32);
+               }
+           });
+           me.getViewModel().set('secret', data);
+       },
+    },
+
+    items: [
+       {
+           xtype: 'form',
+           layout: 'anchor',
+           border: false,
+           reference: 'totp_form',
+           fieldDefaults: {
+               anchor: '100%',
+           },
+           items: [
+               {
+                   xtype: 'pmxDisplayEditField',
+                   name: 'userid',
+                   cbind: {
+                       editable: (get) => get('isAdd') && !get('fixedUser'),
+                       value: () => Proxmox.UserName,
+                   },
+                   fieldLabel: gettext('User'),
+                   editConfig: {
+                       xtype: 'pmxUserSelector',
+                       allowBlank: false,
+                   },
+                   renderer: Ext.String.htmlEncode,
+                   listeners: {
+                       change: function(field, newValue, oldValue) {
+                           let vm = this.up('window').getViewModel();
+                           vm.set('userid', newValue);
+                       },
+                   },
+                   qrupdate: true,
+               },
+               {
+                   xtype: 'textfield',
+                   fieldLabel: gettext('Description'),
+                   emptyText: gettext('For example: TFA device ID, required to identify multiple factors.'),
+                   allowBlank: false,
+                   name: 'description',
+                   maxLength: 256,
+               },
+               {
+                   layout: 'hbox',
+                   border: false,
+                   padding: '0 0 5 0',
+                   items: [
+                       {
+                           xtype: 'textfield',
+                           fieldLabel: gettext('Secret'),
+                           emptyText: gettext('Unchanged'),
+                           name: 'secret',
+                           reference: 'tfa_secret',
+                           regex: /^[A-Z2-7=]+$/,
+                           regexText: 'Must be base32 [A-Z2-7=]',
+                           maskRe: /[A-Z2-7=]/,
+                           qrupdate: true,
+                           bind: {
+                               value: "{secret}",
+                           },
+                           flex: 4,
+                           padding: '0 5 0 0',
+                       },
+                       {
+                           xtype: 'button',
+                           text: gettext('Randomize'),
+                           reference: 'randomize_button',
+                           handler: 'randomizeSecret',
+                           flex: 1,
+                       },
+                   ],
+               },
+               {
+                   xtype: 'numberfield',
+                   fieldLabel: gettext('Time period'),
+                   name: 'step',
+                   // Google Authenticator ignores this and generates bogus data
+                   hidden: true,
+                   value: 30,
+                   minValue: 10,
+                   qrupdate: true,
+               },
+               {
+                   xtype: 'numberfield',
+                   fieldLabel: gettext('Digits'),
+                   name: 'digits',
+                   value: 6,
+                   // Google Authenticator ignores this and generates bogus data
+                   hidden: true,
+                   minValue: 6,
+                   maxValue: 8,
+                   qrupdate: true,
+               },
+               {
+                   xtype: 'textfield',
+                   fieldLabel: gettext('Issuer Name'),
+                   name: 'issuer',
+                   cbind: {
+                       value: '{issuerName}',
+                   },
+                   qrupdate: true,
+               },
+               {
+                   xtype: 'box',
+                   itemId: 'qrbox',
+                   visible: false, // will be enabled when generating a qr code
+                   bind: {
+                       visible: '{!secretEmpty}',
+                   },
+                   style: {
+                       'background-color': 'white',
+                       'margin-left': 'auto',
+                       'margin-right': 'auto',
+                       padding: '5px',
+                       width: '266px',
+                       height: '266px',
+                   },
+               },
+               {
+                   xtype: 'textfield',
+                   fieldLabel: gettext('Verify Code'),
+                   allowBlank: false,
+                   reference: 'challenge',
+                   name: 'challenge',
+                   bind: {
+                       disabled: '{!showTOTPVerifiction}',
+                       visible: '{showTOTPVerifiction}',
+                   },
+                   emptyText: gettext('Scan QR code in a TOTP app and enter an auth. code here'),
+               },
+               {
+                   xtype: 'textfield',
+                   name: 'password',
+                   reference: 'password',
+                   fieldLabel: gettext('Verify Password'),
+                   inputType: 'password',
+                   minLength: 5,
+                   allowBlank: false,
+                   validateBlank: true,
+                   cbind: {
+                       hidden: () => Proxmox.UserName === 'root@pam',
+                       disabled: () => Proxmox.UserName === 'root@pam',
+                       emptyText: () =>
+                           Ext.String.format(gettext("Confirm your ({0}) password"), Proxmox.UserName),
+                   },
+               },
+           ],
+       },
+    ],
+
+    initComponent: function() {
+       let me = this;
+       me.url = '/api2/extjs/access/tfa/';
+       me.method = 'POST';
+       me.callParent();
+    },
+
+    getValues: function(dirtyOnly) {
+       let me = this;
+       let viewmodel = me.getController().getViewModel();
+
+       let values = me.callParent(arguments);
+
+       let uid = encodeURIComponent(values.userid);
+       me.url = `/api2/extjs/access/tfa/${uid}`;
+       delete values.userid;
+
+       let data = {
+           description: values.description,
+           type: "totp",
+           totp: viewmodel.get('otpuri'),
+           value: values.challenge,
+       };
+
+       if (values.password) {
+           data.password = values.password;
+       }
+
+       return data;
+    },
+});
diff --git a/src/window/AddWebauthn.js b/src/window/AddWebauthn.js
new file mode 100644 (file)
index 0000000..f4a0b10
--- /dev/null
@@ -0,0 +1,226 @@
+Ext.define('Proxmox.window.AddWebauthn', {
+    extend: 'Ext.window.Window',
+    alias: 'widget.pmxAddWebauthn',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onlineHelp: 'user_mgmt',
+
+    modal: true,
+    resizable: false,
+    title: gettext('Add a Webauthn login token'),
+    width: 512,
+
+    user: undefined,
+    fixedUser: false,
+
+    initComponent: function() {
+       let me = this;
+       me.callParent();
+       Ext.GlobalEvents.fireEvent('proxmoxShowHelp', me.onlineHelp);
+    },
+
+    viewModel: {
+       data: {
+           valid: false,
+           userid: null,
+       },
+    },
+
+    controller: {
+       xclass: 'Ext.app.ViewController',
+
+       control: {
+           'field': {
+               validitychange: function(field, valid) {
+                   let me = this;
+                   let viewmodel = me.getViewModel();
+                   let form = me.lookup('webauthn_form');
+                   viewmodel.set('valid', form.isValid());
+               },
+           },
+           '#': {
+               show: function() {
+                   let me = this;
+                   let view = me.getView();
+
+                   if (Proxmox.UserName === 'root@pam') {
+                       view.lookup('password').setVisible(false);
+                       view.lookup('password').setDisabled(true);
+                   }
+               },
+           },
+       },
+
+       registerWebauthn: async function() {
+           let me = this;
+           let values = me.lookup('webauthn_form').getValues();
+           values.type = "webauthn";
+
+           let userid = values.user;
+           delete values.user;
+
+           me.getView().mask(gettext('Please wait...'), 'x-mask-loading');
+
+           try {
+               let register_response = await Proxmox.Async.api2({
+                   url: `/api2/extjs/access/tfa/${userid}`,
+                   method: 'POST',
+                   params: values,
+               });
+
+               let data = register_response.result.data;
+               if (!data.challenge) {
+                   throw "server did not respond with a challenge";
+               }
+
+               let creds = JSON.parse(data.challenge);
+
+               // Fix this up before passing it to the browser, but keep a copy of the original
+               // string to pass in the response:
+               let challenge_str = creds.publicKey.challenge;
+               creds.publicKey.challenge = Proxmox.Utils.base64url_to_bytes(challenge_str);
+               creds.publicKey.user.id =
+                   Proxmox.Utils.base64url_to_bytes(creds.publicKey.user.id);
+
+               // convert existing authenticators structure
+               creds.publicKey.excludeCredentials =
+                   (creds.publicKey.excludeCredentials || [])
+                   .map((credential) => ({
+                       id: Proxmox.Utils.base64url_to_bytes(credential.id),
+                       type: credential.type,
+                   }));
+
+               let msg = Ext.Msg.show({
+                   title: `Webauthn: ${gettext('Setup')}`,
+                   message: gettext('Please press the button on your Webauthn Device'),
+                   buttons: [],
+               });
+
+               let token_response;
+               try {
+                   token_response = await navigator.credentials.create(creds);
+               } catch (error) {
+                   let errmsg = error.message;
+                   if (error.name === 'InvalidStateError') {
+                       errmsg = gettext('Is this token already registered?');
+                   }
+                   throw gettext('An error occurred during token registration.') +
+                       `<br>${error.name}: ${errmsg}`;
+               }
+
+               // We cannot pass ArrayBuffers to the API, so extract & convert the data.
+               let response = {
+                   id: token_response.id,
+                   type: token_response.type,
+                   rawId: Proxmox.Utils.bytes_to_base64url(token_response.rawId),
+                   response: {
+                       attestationObject: Proxmox.Utils.bytes_to_base64url(
+                           token_response.response.attestationObject,
+                       ),
+                       clientDataJSON: Proxmox.Utils.bytes_to_base64url(
+                           token_response.response.clientDataJSON,
+                       ),
+                   },
+               };
+
+               msg.close();
+
+               let params = {
+                   type: "webauthn",
+                   challenge: challenge_str,
+                   value: JSON.stringify(response),
+               };
+
+               if (values.password) {
+                   params.password = values.password;
+               }
+
+               await Proxmox.Async.api2({
+                   url: `/api2/extjs/access/tfa/${userid}`,
+                   method: 'POST',
+                   params,
+               });
+           } catch (response) {
+               let error = response.result.message;
+               console.error(error); // for debugging if it's not displayable...
+               Ext.Msg.alert(gettext('Error'), error);
+           }
+
+           me.getView().close();
+       },
+    },
+
+    items: [
+       {
+           xtype: 'form',
+           reference: 'webauthn_form',
+           layout: 'anchor',
+           border: false,
+           bodyPadding: 10,
+           fieldDefaults: {
+               anchor: '100%',
+           },
+           items: [
+               {
+                   xtype: 'pmxDisplayEditField',
+                   name: 'user',
+                   cbind: {
+                       editable: (get) => !get('fixedUser'),
+                       value: () => Proxmox.UserName,
+                   },
+                   fieldLabel: gettext('User'),
+                   editConfig: {
+                       xtype: 'pmxUserSelector',
+                       allowBlank: false,
+                   },
+                   renderer: Ext.String.htmlEncode,
+                   listeners: {
+                       change: function(field, newValue, oldValue) {
+                           let vm = this.up('window').getViewModel();
+                           vm.set('userid', newValue);
+                       },
+                   },
+               },
+               {
+                   xtype: 'textfield',
+                   fieldLabel: gettext('Description'),
+                   allowBlank: false,
+                   name: 'description',
+                   maxLength: 256,
+                   emptyText: gettext('For example: TFA device ID, required to identify multiple factors.'),
+               },
+               {
+                   xtype: 'textfield',
+                   name: 'password',
+                   reference: 'password',
+                   fieldLabel: gettext('Verify Password'),
+                   inputType: 'password',
+                   minLength: 5,
+                   allowBlank: false,
+                   validateBlank: true,
+                   cbind: {
+                       hidden: () => Proxmox.UserName === 'root@pam',
+                       disabled: () => Proxmox.UserName === 'root@pam',
+                       emptyText: () =>
+                           Ext.String.format(gettext("Confirm your ({0}) password"), Proxmox.UserName),
+                   },
+               },
+           ],
+       },
+    ],
+
+    buttons: [
+       {
+           xtype: 'proxmoxHelpButton',
+       },
+       '->',
+       {
+           xtype: 'button',
+           text: gettext('Register Webauthn Device'),
+           handler: 'registerWebauthn',
+           bind: {
+               disabled: '{!valid}',
+           },
+       },
+    ],
+});
diff --git a/src/window/TfaEdit.js b/src/window/TfaEdit.js
new file mode 100644 (file)
index 0000000..710f2b9
--- /dev/null
@@ -0,0 +1,93 @@
+Ext.define('Proxmox.window.TfaEdit', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pmxTfaEdit',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onlineHelp: 'user_mgmt',
+
+    modal: true,
+    resizable: false,
+    title: gettext("Modify a TFA entry's description"),
+    width: 512,
+
+    layout: {
+       type: 'vbox',
+       align: 'stretch',
+    },
+
+    cbindData: function(initialConfig) {
+       let me = this;
+
+       let tfa_id = initialConfig['tfa-id'];
+       me.tfa_id = tfa_id;
+       me.defaultFocus = 'textfield[name=description]';
+       me.url = `/api2/extjs/access/tfa/${tfa_id}`;
+       me.method = 'PUT';
+       me.autoLoad = true;
+       return {};
+    },
+
+    initComponent: function() {
+       let me = this;
+       me.callParent();
+
+       if (Proxmox.UserName === 'root@pam') {
+           me.lookup('password').setVisible(false);
+           me.lookup('password').setDisabled(true);
+       }
+
+       let userid = me.tfa_id.split('/')[0];
+       me.lookup('userid').setValue(userid);
+    },
+
+    items: [
+       {
+           xtype: 'displayfield',
+           reference: 'userid',
+           editable: false,
+           fieldLabel: gettext('User'),
+           editConfig: {
+               xtype: 'pmxUserSelector',
+               allowBlank: false,
+           },
+           cbind: {
+               value: () => Proxmox.UserName,
+           },
+       },
+       {
+           xtype: 'proxmoxtextfield',
+           name: 'description',
+           allowBlank: false,
+           fieldLabel: gettext('Description'),
+       },
+       {
+           xtype: 'proxmoxcheckbox',
+           fieldLabel: gettext('Enabled'),
+           name: 'enable',
+           uncheckedValue: 0,
+           defaultValue: 1,
+           checked: true,
+       },
+       {
+           xtype: 'textfield',
+           inputType: 'password',
+           fieldLabel: gettext('Password'),
+           minLength: 5,
+           reference: 'password',
+           name: 'password',
+           allowBlank: false,
+           validateBlank: true,
+           emptyText: gettext('verify current password'),
+       },
+    ],
+
+    getValues: function() {
+       var me = this;
+
+       var values = me.callParent(arguments);
+
+       delete values.userid;
+
+       return values;
+    },
+});