From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH widget-toolkit 5/7] add totp, wa and recovery creation and tfa edit windows
Date: Tue, 9 Nov 2021 12:27:19 +0100 [thread overview]
Message-ID: <20211109112721.130935-31-w.bumiller@proxmox.com> (raw)
In-Reply-To: <20211109112721.130935-1-w.bumiller@proxmox.com>
plain copy from pbs with s/pbs/pmx/ and s/PBS/Proxmox/
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
src/Makefile | 4 +
src/window/AddTfaRecovery.js | 224 ++++++++++++++++++++++++++
src/window/AddTotp.js | 297 +++++++++++++++++++++++++++++++++++
src/window/AddWebauthn.js | 226 ++++++++++++++++++++++++++
src/window/TfaEdit.js | 93 +++++++++++
5 files changed, 844 insertions(+)
create mode 100644 src/window/AddTfaRecovery.js
create mode 100644 src/window/AddTotp.js
create mode 100644 src/window/AddWebauthn.js
create mode 100644 src/window/TfaEdit.js
diff --git a/src/Makefile b/src/Makefile
index afb0cb2..ad7a3c2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -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
index 0000000..174d553
--- /dev/null
+++ b/src/window/AddTfaRecovery.js
@@ -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
index 0000000..3e0f5b5
--- /dev/null
+++ b/src/window/AddTotp.js
@@ -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
index 0000000..f4a0b10
--- /dev/null
+++ b/src/window/AddWebauthn.js
@@ -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
index 0000000..710f2b9
--- /dev/null
+++ b/src/window/TfaEdit.js
@@ -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;
+ },
+});
--
2.30.2
next prev parent reply other threads:[~2021-11-09 11:28 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-11-09 11:26 [pve-devel] [PATCH multiple 0/9] PBS-like TFA support in PVE Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH proxmox-perl-rs 1/6] import basic skeleton Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH proxmox-perl-rs 2/6] import pve-rs Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH proxmox-perl-rs 3/6] move apt to /perl-apt, use PERLMOD_PRODUCT env var Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH proxmox-perl-rs 4/6] pve: add tfa api Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH proxmox-perl-rs 5/6] build fix: pmg-rs is not here yet Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH proxmox-perl-rs 6/6] Add some dev tips to a README Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH access-control 01/10] use rust parser for TFA config Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH access-control 02/10] update read_user_tfa_type call Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH access-control 03/10] use PBS-like auth api call flow Wolfgang Bumiller
2021-11-09 11:26 ` [pve-devel] [PATCH access-control 04/10] handle yubico authentication in new path Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH access-control 05/10] move TFA api path into its own module Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH access-control 06/10] add pbs-style TFA API implementation Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH access-control 07/10] support registering yubico otp keys Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH access-control 08/10] update tfa cleanup when deleting users Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH access-control 09/10] pveum: update tfa delete command Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH access-control 10/10] set/remove 'x' for tfa keys in user.cfg in new api Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH cluster] add webauthn configuration to datacenter.cfg Wolfgang Bumiller
2021-11-10 10:12 ` [pve-devel] applied: " Thomas Lamprecht
2021-11-09 11:27 ` [pve-devel] [PATCH common] Ticket: uri-escape colons Wolfgang Bumiller
2021-11-09 12:26 ` [pve-devel] applied: " Thomas Lamprecht
2021-11-09 11:27 ` [pve-devel] [PATCH manager 1/7] www: use render_u2f_error from wtk Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH manager 2/7] www: use UserSelector " Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH manager 3/7] use u2f-api.js and qrcode.min.js " Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH manager 4/7] www: switch to new tfa login format Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH manager 5/7] www: use af-address-book-o for realms Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH manager 6/7] www: add TFA view to config Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH manager 7/7] www: redirect user TFA button to TFA view Wolfgang Bumiller
2021-11-09 11:27 ` [pve-devel] [PATCH widget-toolkit 1/7] add pmxUserSelector Wolfgang Bumiller
2021-11-10 8:29 ` [pve-devel] applied: " Dominik Csapak
2021-11-09 11:27 ` [pve-devel] [PATCH widget-toolkit 2/7] add Utils used for u2f and webauthn Wolfgang Bumiller
2021-11-10 8:30 ` [pve-devel] applied: " Dominik Csapak
2021-11-09 11:27 ` [pve-devel] [PATCH widget-toolkit 3/7] add u2f-api.js and qrcode.min.js Wolfgang Bumiller
2021-11-10 8:31 ` Dominik Csapak
2021-11-09 11:27 ` [pve-devel] [PATCH widget-toolkit 4/7] add Proxmox.window.TfaLoginWindow Wolfgang Bumiller
2021-11-10 8:30 ` [pve-devel] applied: " Dominik Csapak
2021-11-09 11:27 ` Wolfgang Bumiller [this message]
2021-11-10 8:30 ` [pve-devel] applied: [PATCH widget-toolkit 5/7] add totp, wa and recovery creation and tfa edit windows Dominik Csapak
2021-11-09 11:27 ` [pve-devel] [PATCH widget-toolkit 6/7] add Proxmox.panel.TfaView Wolfgang Bumiller
2021-11-10 8:30 ` [pve-devel] applied: " Dominik Csapak
2021-11-09 11:27 ` [pve-devel] [PATCH widget-toolkit 7/7] add yubico otp windows & login support Wolfgang Bumiller
2021-11-10 8:30 ` [pve-devel] applied: " Dominik Csapak
2021-11-11 15:52 ` [pve-devel] applied-series: [PATCH multiple 0/9] PBS-like TFA support in PVE Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20211109112721.130935-31-w.bumiller@proxmox.com \
--to=w.bumiller@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal