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/AddTotp.js \
|
||||
window/AddWebauthn.js \
|
||||
window/AddYubico.js \
|
||||
window/TfaEdit.js \
|
||||
node/APT.js \
|
||||
node/APTRepositories.js \
|
||||
|
@ -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
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 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'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user