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:
parent
ab2538f5e5
commit
20b39dd8f6
@ -83,6 +83,7 @@ JSSRC= \
|
|||||||
window/AddTfaRecovery.js \
|
window/AddTfaRecovery.js \
|
||||||
window/AddTotp.js \
|
window/AddTotp.js \
|
||||||
window/AddWebauthn.js \
|
window/AddWebauthn.js \
|
||||||
|
window/AddYubico.js \
|
||||||
window/TfaEdit.js \
|
window/TfaEdit.js \
|
||||||
node/APT.js \
|
node/APT.js \
|
||||||
node/APTRepositories.js \
|
node/APTRepositories.js \
|
||||||
|
@ -18,11 +18,20 @@ Ext.define('pmx-tfa-entry', {
|
|||||||
Ext.define('Proxmox.panel.TfaView', {
|
Ext.define('Proxmox.panel.TfaView', {
|
||||||
extend: 'Ext.grid.GridPanel',
|
extend: 'Ext.grid.GridPanel',
|
||||||
alias: 'widget.pmxTfaView',
|
alias: 'widget.pmxTfaView',
|
||||||
|
mixins: ['Proxmox.Mixin.CBind'],
|
||||||
|
|
||||||
title: gettext('Second Factors'),
|
title: gettext('Second Factors'),
|
||||||
reference: 'tfaview',
|
reference: 'tfaview',
|
||||||
|
|
||||||
issuerName: 'Proxmox',
|
issuerName: 'Proxmox',
|
||||||
|
yubicoEnabled: false,
|
||||||
|
|
||||||
|
cbindData: function(initialConfig) {
|
||||||
|
let me = this;
|
||||||
|
return {
|
||||||
|
yubicoEnabled: me.yubicoEnabled,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
store: {
|
store: {
|
||||||
type: 'diff',
|
type: 'diff',
|
||||||
@ -116,6 +125,19 @@ Ext.define('Proxmox.panel.TfaView', {
|
|||||||
}).show();
|
}).show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addYubico: function() {
|
||||||
|
let me = this;
|
||||||
|
|
||||||
|
Ext.create('Proxmox.window.AddYubico', {
|
||||||
|
isCreate: true,
|
||||||
|
listeners: {
|
||||||
|
destroy: function() {
|
||||||
|
me.reload();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).show();
|
||||||
|
},
|
||||||
|
|
||||||
editItem: function() {
|
editItem: function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
let view = me.getView();
|
let view = me.getView();
|
||||||
@ -227,6 +249,7 @@ Ext.define('Proxmox.panel.TfaView', {
|
|||||||
tbar: [
|
tbar: [
|
||||||
{
|
{
|
||||||
text: gettext('Add'),
|
text: gettext('Add'),
|
||||||
|
cbind: {},
|
||||||
menu: {
|
menu: {
|
||||||
xtype: 'menu',
|
xtype: 'menu',
|
||||||
items: [
|
items: [
|
||||||
@ -248,6 +271,15 @@ Ext.define('Proxmox.panel.TfaView', {
|
|||||||
iconCls: 'fa fa-fw fa-file-text-o',
|
iconCls: 'fa fa-fw fa-file-text-o',
|
||||||
handler: 'addRecovery',
|
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
148
src/window/AddYubico.js
Normal 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;
|
||||||
|
},
|
||||||
|
});
|
@ -45,7 +45,7 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
|||||||
|
|
||||||
let lastTabId = me.getLastTabUsed();
|
let lastTabId = me.getLastTabUsed();
|
||||||
let initialTab = -1, i = 0;
|
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];
|
const available = !!challenge[k];
|
||||||
vm.set(`availableChallenge.${k}`, available);
|
vm.set(`availableChallenge.${k}`, available);
|
||||||
|
|
||||||
@ -143,6 +143,13 @@ Ext.define('Proxmox.window.TfaLoginWindow', {
|
|||||||
let _promise = me.finishChallenge(`totp:${code}`);
|
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() {
|
loginWebauthn: async function() {
|
||||||
let me = this;
|
let me = this;
|
||||||
let view = me.getView();
|
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'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user