add yubico otp windows & login support

has to be explicitly enabled since this is only supported in
PVE

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2021-11-09 12:27:21 +01:00 committed by Dominik Csapak
parent ab2538f5e5
commit 20b39dd8f6
4 changed files with 211 additions and 1 deletions

View 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 \

View 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}',
},
},
],
},
},

148
src/window/AddYubico.js Normal file
View File

@ -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;
},
});

View 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'),
},
],
},
],
}],