[pve-devel] [PATCH manager 7/7] www: redirect user TFA button to TFA view
Wolfgang Bumiller
w.bumiller at proxmox.com
Tue Nov 9 12:27:14 CET 2021
Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
www/manager6/Makefile | 1 -
www/manager6/StateProvider.js | 1 +
www/manager6/Workspace.js | 6 +-
www/manager6/dc/TFAEdit.js | 545 ----------------------------------
www/manager6/dc/UserView.js | 27 --
5 files changed, 3 insertions(+), 577 deletions(-)
delete mode 100644 www/manager6/dc/TFAEdit.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 4011d4e5..584c1f2a 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -149,7 +149,6 @@ JSSRC= \
dc/Summary.js \
dc/Support.js \
dc/SyncWindow.js \
- dc/TFAEdit.js \
dc/Tasks.js \
dc/TokenEdit.js \
dc/TokenView.js \
diff --git a/www/manager6/StateProvider.js b/www/manager6/StateProvider.js
index e835f402..fafbb112 100644
--- a/www/manager6/StateProvider.js
+++ b/www/manager6/StateProvider.js
@@ -47,6 +47,7 @@ Ext.define('PVE.StateProvider', {
hprefix: 'v1',
compDict: {
+ tfa: 54,
sdn: 53,
cloudinit: 52,
replication: 51,
diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js
index 0e2a750b..37d772b8 100644
--- a/www/manager6/Workspace.js
+++ b/www/manager6/Workspace.js
@@ -386,10 +386,8 @@ Ext.define('PVE.StdWorkspace', {
itemId: 'tfaitem',
iconCls: 'fa fa-fw fa-lock',
handler: function(btn, event, rec) {
- var win = Ext.create('PVE.window.TFAEdit', {
- userid: Proxmox.UserName,
- });
- win.show();
+ Ext.state.Manager.getProvider().set('dctab', { value: 'tfa' }, true);
+ me.selectById('root');
},
},
{
diff --git a/www/manager6/dc/TFAEdit.js b/www/manager6/dc/TFAEdit.js
deleted file mode 100644
index 57a73b39..00000000
--- a/www/manager6/dc/TFAEdit.js
+++ /dev/null
@@ -1,545 +0,0 @@
-/*global u2f,QRCode*/
-Ext.define('PVE.window.TFAEdit', {
- extend: 'Ext.window.Window',
- mixins: ['Proxmox.Mixin.CBind'],
-
- onlineHelp: 'pveum_tfa_auth', // fake to ensure this gets a link target
-
- modal: true,
- resizable: false,
- title: gettext('Two Factor Authentication'),
- subject: 'TFA',
- url: '/api2/extjs/access/tfa',
- width: 512,
-
- layout: {
- type: 'vbox',
- align: 'stretch',
- },
-
- updateQrCode: function() {
- var me = this;
- var values = me.lookup('totp_form').getValues();
- var algorithm = values.algorithm;
- if (!algorithm) {
- algorithm = 'SHA1';
- }
-
- me.qrcode.makeCode(
- 'otpauth://totp/' +
- encodeURIComponent(values.issuer) +
- ':' +
- encodeURIComponent(me.userid) +
- '?secret=' + values.secret +
- '&period=' + values.step +
- '&digits=' + values.digits +
- '&algorithm=' + algorithm +
- '&issuer=' + encodeURIComponent(values.issuer),
- );
-
- me.lookup('challenge').setVisible(true);
- me.down('#qrbox').setVisible(true);
- },
-
- showError: function(error) {
- Ext.Msg.alert(
- gettext('Error'),
- Proxmox.Utils.render_u2f_error(error),
- );
- },
-
- doU2FChallenge: function(res) {
- let me = this;
-
- let challenge = res.result.data;
- me.lookup('password').setDisabled(true);
- let msg = Ext.Msg.show({
- title: 'U2F: ' + gettext('Setup'),
- message: gettext('Please press the button on your U2F Device'),
- buttons: [],
- });
- Ext.Function.defer(function() {
- u2f.register(challenge.appId, [challenge], [], function(response) {
- msg.close();
- if (response.errorCode) {
- me.showError(response.errorCode);
- } else {
- me.respondToU2FChallenge(response);
- }
- });
- }, 500, me);
- },
-
- respondToU2FChallenge: function(data) {
- var me = this;
- var params = {
- userid: me.userid,
- action: 'confirm',
- response: JSON.stringify(data),
- };
- if (Proxmox.UserName !== 'root at pam') {
- params.password = me.lookup('password').value;
- }
- Proxmox.Utils.API2Request({
- url: '/api2/extjs/access/tfa',
- params: params,
- method: 'PUT',
- success: function() {
- me.close();
- Ext.Msg.show({
- title: gettext('Success'),
- message: gettext('U2F Device successfully connected.'),
- buttons: Ext.Msg.OK,
- });
- },
- failure: function(response, opts) {
- Ext.Msg.alert(gettext('Error'), response.htmlStatus);
- },
- });
- },
-
- viewModel: {
- data: {
- in_totp_tab: true,
- tfa_required: false,
- tfa_type: null, // dependencies of formulas should not be undefined
- valid: false,
- u2f_available: true,
- secret: "",
- },
- formulas: {
- showTOTPVerifiction: function(get) {
- return get('secret').length > 0 && get('canSetupTOTP');
- },
- canDeleteTFA: function(get) {
- return get('tfa_type') !== null && !get('tfa_required');
- },
- canSetupTOTP: function(get) {
- var tfa = get('tfa_type');
- return tfa === null || tfa === 'totp' || tfa === 1;
- },
- canSetupU2F: function(get) {
- var tfa = get('tfa_type');
- return get('u2f_available') && (tfa === null || tfa === 'u2f' || tfa === 1);
- },
- secretEmpty: function(get) {
- return get('secret').length === 0;
- },
- selectedTab: function(get) {
- return (get('tfa_type') || 'totp') + '-panel';
- },
- },
- },
-
- afterLoading: function(realm_tfa_type, user_tfa_type) {
- var me = this;
- var viewmodel = me.getViewModel();
- if (user_tfa_type === 'oath') {
- user_tfa_type = 'totp';
- viewmodel.set('secret', '');
- }
-
- // if the user has no tfa, generate a secret for him
- if (!user_tfa_type) {
- me.getController().randomizeSecret();
- }
-
- viewmodel.set('tfa_type', user_tfa_type || null);
- if (!realm_tfa_type) {
- // There's no TFA enforced by the realm, everything works.
- viewmodel.set('u2f_available', true);
- viewmodel.set('tfa_required', false);
- } else if (realm_tfa_type === 'oath') {
- // The realm explicitly requires TOTP
- if (user_tfa_type !== 'totp' && user_tfa_type !== null) {
- // user had a different tfa method, so
- // we have to change back to the totp tab and
- // generate a secret
- viewmodel.set('tfa_type', 'totp');
- me.getController().randomizeSecret();
- }
- viewmodel.set('tfa_required', true);
- viewmodel.set('u2f_available', false);
- } else {
- // The realm enforces some other TFA type (yubico)
- me.close();
- Ext.Msg.alert(
- gettext('Error'),
- Ext.String.format(
- gettext("Custom 2nd factor configuration is not supported on realms with '{0}' TFA."),
- realm_tfa_type,
- ),
- );
- }
- },
-
- controller: {
- xclass: 'Ext.app.ViewController',
- control: {
- 'field[qrupdate=true]': {
- change: function() {
- this.getView().updateQrCode();
- },
- },
- 'field': {
- validitychange: function(field, valid) {
- var me = this;
- var viewModel = me.getViewModel();
- var form = me.lookup('totp_form');
- var challenge = me.lookup('challenge');
- var password = me.lookup('password');
- viewModel.set('valid', form.isValid() && challenge.isValid() && password.isValid());
- },
- },
- '#': {
- show: function() {
- let view = this.getView();
-
- Proxmox.Utils.API2Request({
- url: '/access/users/' + encodeURIComponent(view.userid) + '/tfa',
- waitMsgTarget: view.down('#tfatabs'),
- method: 'GET',
- success: function(response, opts) {
- let data = response.result.data;
- view.afterLoading(data.realm, data.user);
- },
- failure: function(response, opts) {
- view.close();
- Ext.Msg.alert(gettext('Error'), response.htmlStatus);
- },
- });
-
- view.qrdiv = document.createElement('center');
- view.qrcode = new QRCode(view.qrdiv, {
- width: 256,
- height: 256,
- correctLevel: QRCode.CorrectLevel.M,
- });
- view.down('#qrbox').getEl().appendChild(view.qrdiv);
-
- if (Proxmox.UserName === 'root at pam') {
- view.lookup('password').setVisible(false);
- view.lookup('password').setDisabled(true);
- }
- },
- },
- '#tfatabs': {
- tabchange: function(panel, newcard) {
- this.getViewModel().set('in_totp_tab', newcard.itemId === 'totp-panel');
- },
- },
- },
-
- applySettings: function() {
- let me = this;
- let values = me.lookup('totp_form').getValues();
- let params = {
- userid: me.getView().userid,
- action: 'new',
- key: 'v2-' + values.secret,
- config: PVE.Parser.printPropertyString({
- type: 'oath',
- digits: values.digits,
- step: values.step,
- }),
- // this is used to verify that the client generates the correct codes:
- response: me.lookup('challenge').value,
- };
-
- if (Proxmox.UserName !== 'root at pam') {
- params.password = me.lookup('password').value;
- }
-
- Proxmox.Utils.API2Request({
- url: '/api2/extjs/access/tfa',
- params: params,
- method: 'PUT',
- waitMsgTarget: me.getView(),
- success: function(response, opts) {
- me.getView().close();
- },
- failure: function(response, opts) {
- Ext.Msg.alert(gettext('Error'), response.htmlStatus);
- },
- });
- },
-
- deleteTFA: function() {
- let me = this;
- let params = {
- userid: me.getView().userid,
- action: 'delete',
- };
-
- if (Proxmox.UserName !== 'root at pam') {
- params.password = me.lookup('password').value;
- }
-
- Proxmox.Utils.API2Request({
- url: '/api2/extjs/access/tfa',
- params: params,
- method: 'PUT',
- waitMsgTarget: me.getView(),
- success: function(response, opts) {
- me.getView().close();
- },
- failure: function(response, opts) {
- Ext.Msg.alert(gettext('Error'), response.htmlStatus);
- },
- });
- },
-
- 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) {
- data += String.fromCharCode(b + 0x41); // A..Z
- } else {
- data += String.fromCharCode(b-26 + 0x32); // 2..7
- }
- });
- me.getViewModel().set('secret', data);
- },
-
- startU2FRegistration: function() {
- let me = this;
-
- let params = {
- userid: me.getView().userid,
- action: 'new',
- };
-
- if (Proxmox.UserName !== 'root at pam') {
- params.password = me.lookup('password').value;
- }
-
- Proxmox.Utils.API2Request({
- url: '/api2/extjs/access/tfa',
- params: params,
- method: 'PUT',
- waitMsgTarget: me.getView(),
- success: function(response) {
- me.getView().doU2FChallenge(response);
- },
- failure: function(response, opts) {
- Ext.Msg.alert(gettext('Error'), response.htmlStatus);
- },
- });
- },
- },
-
- items: [
- {
- xtype: 'tabpanel',
- itemId: 'tfatabs',
- reference: 'tfatabs',
- border: false,
- bind: {
- activeTab: '{selectedTab}',
- },
- items: [
- {
- xtype: 'panel',
- title: 'TOTP',
- itemId: 'totp-panel',
- reference: 'totp_panel',
- tfa_type: 'totp',
- border: false,
- bind: {
- disabled: '{!canSetupTOTP}',
- },
- layout: {
- type: 'vbox',
- align: 'stretch',
- },
- items: [
- {
- xtype: 'form',
- layout: 'anchor',
- border: false,
- reference: 'totp_form',
- fieldDefaults: {
- anchor: '100%',
- padding: '0 5',
- },
- items: [
- {
- xtype: 'displayfield',
- fieldLabel: gettext('User name'),
- renderer: Ext.String.htmlEncode,
- cbind: {
- value: '{userid}',
- },
- },
- {
- 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,
- },
- {
- 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',
- value: 'Proxmox Web UI',
- qrupdate: true,
- },
- ],
- },
- {
- xtype: 'box',
- itemId: 'qrbox',
- visible: false, // will be enabled when generating a qr code
- bind: {
- visible: '{!secretEmpty}',
- },
- style: {
- 'background-color': 'white',
- padding: '5px',
- width: '266px',
- height: '266px',
- },
- },
- {
- xtype: 'textfield',
- fieldLabel: gettext('Verification Code'),
- allowBlank: false,
- reference: 'challenge',
- bind: {
- disabled: '{!showTOTPVerifiction}',
- visible: '{showTOTPVerifiction}',
- },
- padding: '0 5',
- emptyText: gettext('Scan QR code and enter TOTP auth. code to verify'),
- },
- ],
- },
- {
- title: 'U2F',
- itemId: 'u2f-panel',
- reference: 'u2f_panel',
- tfa_type: 'u2f',
- border: false,
- padding: '5 5',
- layout: {
- type: 'vbox',
- align: 'middle',
- },
- bind: {
- disabled: '{!canSetupU2F}',
- },
- items: [
- {
- xtype: 'label',
- width: 500,
- text: gettext('To register a U2F device, connect the device, then click the button and follow the instructions.'),
- },
- ],
- },
- ],
- },
- {
- xtype: 'textfield',
- inputType: 'password',
- fieldLabel: gettext('Password'),
- minLength: 5,
- reference: 'password',
- allowBlank: false,
- validateBlank: true,
- padding: '0 0 5 5',
- emptyText: gettext('verify current password'),
- },
- ],
-
- buttons: [
- {
- xtype: 'proxmoxHelpButton',
- },
- '->',
- {
- text: gettext('Apply'),
- handler: 'applySettings',
- bind: {
- hidden: '{!in_totp_tab}',
- disabled: '{!valid}',
- },
- },
- {
- xtype: 'button',
- text: gettext('Register U2F Device'),
- handler: 'startU2FRegistration',
- bind: {
- hidden: '{in_totp_tab}',
- disabled: '{tfa_type}',
- },
- },
- {
- text: gettext('Delete'),
- reference: 'delete_button',
- disabled: true,
- handler: 'deleteTFA',
- bind: {
- disabled: '{!canDeleteTFA}',
- },
- },
- ],
-
- initComponent: function() {
- var me = this;
-
- if (!me.userid) {
- throw "no userid given";
- }
-
- me.callParent();
-
- Ext.GlobalEvents.fireEvent('proxmoxShowHelp', 'pveum_tfa_auth');
- },
-});
diff --git a/www/manager6/dc/UserView.js b/www/manager6/dc/UserView.js
index 9c84bf7d..f397731d 100644
--- a/www/manager6/dc/UserView.js
+++ b/www/manager6/dc/UserView.js
@@ -77,32 +77,6 @@ Ext.define('PVE.dc.UserView', {
});
},
});
- let tfachange_btn = new Proxmox.button.Button({
- text: 'TFA',
- disabled: true,
- selModel: sm,
- enableFn: function(record) {
- let type = record.data['realm-type'];
- if (type) {
- if (PVE.Utils.authSchema[type]) {
- return !!PVE.Utils.authSchema[type].tfa;
- }
- }
- return false;
- },
- handler: function(btn, event, rec) {
- var d = rec.data;
- var tfa_type = PVE.Parser.parseTfaType(d.keys);
- Ext.create('PVE.window.TFAEdit', {
- tfa_type: tfa_type,
- userid: d.userid,
- autoShow: true,
- listeners: {
- destroy: () => reload(),
- },
- });
- },
- });
var perm_btn = new Proxmox.button.Button({
text: gettext('Permissions'),
@@ -140,7 +114,6 @@ Ext.define('PVE.dc.UserView', {
remove_btn,
'-',
pwchange_btn,
- tfachange_btn,
'-',
perm_btn,
],
--
2.30.2
More information about the pve-devel
mailing list