]> git.proxmox.com Git - proxmox-widget-toolkit.git/commitdiff
add yubico otp windows & login support
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 9 Nov 2021 11:27:21 +0000 (12:27 +0100)
committerDominik Csapak <d.csapak@proxmox.com>
Wed, 10 Nov 2021 07:58:44 +0000 (08:58 +0100)
has to be explicitly enabled since this is only supported in
PVE

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
src/Makefile
src/panel/TfaView.js
src/window/AddYubico.js [new file with mode: 0644]
src/window/TfaWindow.js

index 5e47bf4bd7a49e11c857bac242dda91550039beb..c245a53254ee68c203e87525f68273ddd4713972 100644 (file)
@@ -83,6 +83,7 @@ JSSRC=                                        \
        window/AddTfaRecovery.js        \
        window/AddTotp.js               \
        window/AddWebauthn.js           \
+       window/AddYubico.js             \
        window/TfaEdit.js               \
        node/APT.js                     \
        node/APTRepositories.js         \
index a0cb04a7dc7c149b7459962facabf842e9faaf70..712cdfe131871326b268f4d62bd060a442e461ef 100644 (file)
@@ -18,11 +18,20 @@ Ext.define('pmx-tfa-entry', {
 Ext.define('Proxmox.panel.TfaView', {
     extend: 'Ext.grid.GridPanel',
     alias: 'widget.pmxTfaView',
+    mixins: ['Proxmox.Mixin.CBind'],
 
     title: gettext('Second Factors'),
     reference: 'tfaview',
 
     issuerName: 'Proxmox',
+    yubicoEnabled: false,
+
+    cbindData: function(initialConfig) {
+       let me = this;
+       return {
+           yubicoEnabled: me.yubicoEnabled,
+       };
+    },
 
     store: {
        type: 'diff',
@@ -116,6 +125,19 @@ Ext.define('Proxmox.panel.TfaView', {
            }).show();
        },
 
+       addYubico: function() {
+           let me = this;
+
+           Ext.create('Proxmox.window.AddYubico', {
+               isCreate: true,
+               listeners: {
+                   destroy: function() {
+                       me.reload();
+                   },
+               },
+           }).show();
+       },
+
        editItem: function() {
            let me = this;
            let view = me.getView();
@@ -227,6 +249,7 @@ Ext.define('Proxmox.panel.TfaView', {
     tbar: [
        {
            text: gettext('Add'),
+           cbind: {},
            menu: {
                xtype: 'menu',
                items: [
@@ -248,6 +271,15 @@ Ext.define('Proxmox.panel.TfaView', {
                        iconCls: 'fa fa-fw fa-file-text-o',
                        handler: 'addRecovery',
                    },
+                   {
+                       text: gettext('Yubico'),
+                       itemId: 'yubico',
+                       iconCls: 'fa fa-fw fa-yahoo',
+                       handler: 'addYubico',
+                       cbind: {
+                           hidden: '{!yubicoEnabled}',
+                       },
+                   },
                ],
            },
        },
diff --git a/src/window/AddYubico.js b/src/window/AddYubico.js
new file mode 100644 (file)
index 0000000..22b884b
--- /dev/null
@@ -0,0 +1,148 @@
+Ext.define('Proxmox.window.AddYubico', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pmxAddYubico',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onlineHelp: 'user_mgmt',
+
+    modal: true,
+    resizable: false,
+    title: gettext('Add a Yubico key'),
+    width: 512,
+
+    isAdd: true,
+    userid: undefined,
+    fixedUser: false,
+
+    initComponent: function() {
+       let me = this;
+       me.url = '/api2/extjs/access/tfa/';
+       me.method = 'POST';
+       me.callParent();
+    },
+
+    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('yubico_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);
+                   }
+               },
+           },
+       },
+    },
+
+    items: [
+       {
+           xtype: 'form',
+           reference: 'yubico_form',
+           layout: 'anchor',
+           border: false,
+           bodyPadding: 10,
+           fieldDefaults: {
+               anchor: '100%',
+           },
+           items: [
+               {
+                   xtype: 'pmxDisplayEditField',
+                   name: 'userid',
+                   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',
+                   fieldLabel: gettext('Yubico OTP Key'),
+                   emptyText: gettext('A currently valid Yubico OTP value'),
+                   name: 'otp_value',
+                   maxLength: 44,
+                   enforceMaxLength: true,
+                   regex: /^[a-zA-Z0-9]{44}$/,
+                   regexText: '44 characters',
+                   maskRe: /^[a-zA-Z0-9]$/,
+               },
+               {
+                   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),
+                   },
+               },
+           ],
+       },
+    ],
+
+    getValues: function(dirtyOnly) {
+       let me = this;
+
+       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: "yubico",
+           value: values.otp_value,
+       };
+
+       if (values.password) {
+           data.password = values.password;
+       }
+
+       return data;
+    },
+});
index 5026fb8d5d8c86ce950fe55d76a3b7c3131b749c..d568f9b123e193a5737203a3bf2bab802ab0b788 100644 (file)
@@ -45,7 +45,7 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
 
            let lastTabId = me.getLastTabUsed();
            let initialTab = -1, i = 0;
-           for (const k of ['webauthn', 'totp', 'recovery', 'u2f']) {
+           for (const k of ['webauthn', 'totp', 'recovery', 'u2f', 'yubico']) {
                const available = !!challenge[k];
                vm.set(`availableChallenge.${k}`, available);
 
@@ -143,6 +143,13 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
            let _promise = me.finishChallenge(`totp:${code}`);
        },
 
+       loginYubico: function() {
+           let me = this;
+
+           let code = me.lookup('yubico').getValue();
+           let _promise = me.finishChallenge(`yubico:${code}`);
+       },
+
        loginWebauthn: async function() {
            let me = this;
            let view = me.getView();
@@ -412,6 +419,28 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
                    },
                ],
            },
+           {
+               xtype: 'panel',
+               title: gettext('Yubico OTP'),
+               iconCls: 'fa fa-fw fa-yahoo',
+               handler: 'loginYubico',
+               bind: {
+                   disabled: '{!availableChallenge.yubico}',
+               },
+               items: [
+                   {
+                       xtype: 'textfield',
+                       fieldLabel: gettext('Please enter your Yubico OTP code'),
+                       labelWidth: 300,
+                       name: 'yubico',
+                       disabled: true,
+                       reference: 'yubico',
+                       allowBlank: false,
+                       regex: /^[a-z0-9]{30,60}$/, // *should* be 44 but not sure if that's "fixed"
+                       regexText: gettext('TOTP codes consist of six decimal digits'),
+                   },
+               ],
+           },
        ],
     }],