gui: tfa support
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
193
www/window/AddWebauthn.js
Normal file
193
www/window/AddWebauthn.js
Normal file
@ -0,0 +1,193 @@
|
||||
Ext.define('PBS.window.AddWebauthn', {
|
||||
extend: 'Ext.window.Window',
|
||||
alias: 'widget.pbsAddWebauthn',
|
||||
mixins: ['Proxmox.Mixin.CBind'],
|
||||
|
||||
onlineHelp: 'user_mgmt',
|
||||
|
||||
modal: true,
|
||||
resizable: false,
|
||||
title: gettext('Add a Webauthn login token'),
|
||||
width: 512,
|
||||
|
||||
user: undefined,
|
||||
fixedUser: false,
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
me.callParent();
|
||||
Ext.GlobalEvents.fireEvent('proxmoxShowHelp', me.onlineHelp);
|
||||
},
|
||||
|
||||
viewModel: {
|
||||
data: {
|
||||
valid: false,
|
||||
},
|
||||
},
|
||||
|
||||
controller: {
|
||||
xclass: 'Ext.app.ViewController',
|
||||
|
||||
control: {
|
||||
'field': {
|
||||
validitychange: function(field, valid) {
|
||||
let me = this;
|
||||
let viewmodel = me.getViewModel();
|
||||
let form = me.lookup('webauthn_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);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
registerWebauthn: async function() {
|
||||
let me = this;
|
||||
let values = me.lookup('webauthn_form').getValues();
|
||||
values.type = "webauthn";
|
||||
|
||||
let userid = values.user;
|
||||
delete values.user;
|
||||
|
||||
try {
|
||||
let register_response = await PBS.Async.api2({
|
||||
url: `/api2/extjs/access/tfa/${userid}`,
|
||||
method: 'POST',
|
||||
params: values,
|
||||
});
|
||||
|
||||
let data = register_response.result.data;
|
||||
if (!data.challenge) {
|
||||
throw "server did not respond with a challenge";
|
||||
}
|
||||
|
||||
let challenge_obj = JSON.parse(data.challenge);
|
||||
|
||||
// Fix this up before passing it to the browser, but keep a copy of the original
|
||||
// string to pass in the response:
|
||||
let challenge_str = challenge_obj.publicKey.challenge;
|
||||
challenge_obj.publicKey.challenge = PBS.Utils.base64url_to_bytes(challenge_str);
|
||||
challenge_obj.publicKey.user.id =
|
||||
PBS.Utils.base64url_to_bytes(challenge_obj.publicKey.user.id);
|
||||
|
||||
let msg = Ext.Msg.show({
|
||||
title: `Webauthn: ${gettext('Setup')}`,
|
||||
message: gettext('Please press the button on your Webauthn Device'),
|
||||
buttons: [],
|
||||
});
|
||||
|
||||
let token_response = await navigator.credentials.create(challenge_obj);
|
||||
|
||||
// We cannot pass ArrayBuffers to the API, so extract & convert the data.
|
||||
let response = {
|
||||
id: token_response.id,
|
||||
type: token_response.type,
|
||||
rawId: PBS.Utils.bytes_to_base64url(token_response.rawId),
|
||||
response: {
|
||||
attestationObject: PBS.Utils.bytes_to_base64url(
|
||||
token_response.response.attestationObject,
|
||||
),
|
||||
clientDataJSON: PBS.Utils.bytes_to_base64url(
|
||||
token_response.response.clientDataJSON,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
msg.close();
|
||||
|
||||
let params = {
|
||||
type: "webauthn",
|
||||
challenge: challenge_str,
|
||||
value: JSON.stringify(response),
|
||||
};
|
||||
|
||||
if (values.password) {
|
||||
params.password = values.password;
|
||||
}
|
||||
|
||||
await PBS.Async.api2({
|
||||
url: `/api2/extjs/access/tfa/${userid}`,
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error); // for debugging if it's not displayable...
|
||||
Ext.Msg.alert(gettext('Error'), error);
|
||||
}
|
||||
|
||||
me.getView().close();
|
||||
},
|
||||
},
|
||||
|
||||
items: [
|
||||
{
|
||||
xtype: 'form',
|
||||
reference: 'webauthn_form',
|
||||
layout: 'anchor',
|
||||
bodyPadding: 10,
|
||||
fieldDefaults: {
|
||||
anchor: '100%',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
xtype: 'pmxDisplayEditField',
|
||||
name: 'user',
|
||||
cbind: {
|
||||
editable: (get) => !get('fixedUser'),
|
||||
},
|
||||
fieldLabel: gettext('User'),
|
||||
editConfig: {
|
||||
xtype: 'pbsUserSelector',
|
||||
allowBlank: false,
|
||||
},
|
||||
renderer: Ext.String.htmlEncode,
|
||||
value: Proxmox.UserName,
|
||||
},
|
||||
{
|
||||
xtype: 'textfield',
|
||||
fieldLabel: gettext('Description'),
|
||||
allowBlank: false,
|
||||
name: 'description',
|
||||
maxLength: 256,
|
||||
emptyText: gettext('a short distinguishing description'),
|
||||
},
|
||||
{
|
||||
xtype: 'textfield',
|
||||
inputType: 'password',
|
||||
fieldLabel: gettext('Password'),
|
||||
minLength: 5,
|
||||
reference: 'password',
|
||||
name: 'password',
|
||||
allowBlank: false,
|
||||
validateBlank: true,
|
||||
padding: '0 0 5 5',
|
||||
emptyText: gettext('verify current password'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
buttons: [
|
||||
{
|
||||
xtype: 'proxmoxHelpButton',
|
||||
},
|
||||
'->',
|
||||
{
|
||||
xtype: 'button',
|
||||
text: gettext('Register Webauthn Device'),
|
||||
handler: 'registerWebauthn',
|
||||
bind: {
|
||||
disabled: '{!valid}',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
Reference in New Issue
Block a user