]> git.proxmox.com Git - proxmox-backup.git/blame - www/config/TfaView.js
ui: webauthn config: use ID instead of Id/id
[proxmox-backup.git] / www / config / TfaView.js
CommitLineData
fbeac4ea
WB
1Ext.define('pbs-tfa-users', {
2 extend: 'Ext.data.Model',
3 fields: ['userid'],
4 idProperty: 'userid',
5 proxy: {
6 type: 'proxmox',
7 url: '/api2/json/access/tfa',
8 },
9});
10
11Ext.define('pbs-tfa-entry', {
12 extend: 'Ext.data.Model',
13 fields: ['fullid', 'type', 'description', 'enable'],
14 idProperty: 'fullid',
15});
16
17
18Ext.define('PBS.config.TfaView', {
19 extend: 'Ext.grid.GridPanel',
20 alias: 'widget.pbsTfaView',
21
22 title: gettext('Second Factors'),
23 reference: 'tfaview',
24
25 store: {
26 type: 'diff',
27 autoDestroy: true,
28 autoDestroyRstore: true,
29 model: 'pbs-tfa-entry',
30 rstore: {
31 type: 'store',
32 proxy: 'memory',
33 storeid: 'pbs-tfa-entry',
34 model: 'pbs-tfa-entry',
35 },
36 },
37
38 controller: {
39 xclass: 'Ext.app.ViewController',
40
41 init: function(view) {
42 let me = this;
43 view.tfaStore = Ext.create('Proxmox.data.UpdateStore', {
44 autoStart: true,
45 interval: 5 * 1000,
46 storeid: 'pbs-tfa-users',
47 model: 'pbs-tfa-users',
48 });
49 view.tfaStore.on('load', this.onLoad, this);
50 view.on('destroy', view.tfaStore.stopUpdate);
51 Proxmox.Utils.monStoreErrors(view, view.tfaStore);
52 },
53
54 reload: function() { this.getView().tfaStore.load(); },
55
56 onLoad: function(store, data, success) {
57 if (!success) return;
58
59 let records = [];
60 Ext.Array.each(data, user => {
61 Ext.Array.each(user.data.entries, entry => {
62 records.push({
63 fullid: `${user.id}/${entry.id}`,
64 type: entry.type,
65 description: entry.description,
66 enable: entry.enable,
67 });
68 });
69 });
70
71 let rstore = this.getView().store.rstore;
72 rstore.loadData(records);
73 rstore.fireEvent('load', rstore, records, true);
74 },
75
76 addTotp: function() {
77 let me = this;
78
79 Ext.create('PBS.window.AddTotp', {
80 isCreate: true,
81 listeners: {
82 destroy: function() {
83 me.reload();
84 },
85 },
86 }).show();
87 },
88
89 addWebauthn: function() {
90 let me = this;
91
92 Ext.create('PBS.window.AddWebauthn', {
93 isCreate: true,
94 listeners: {
95 destroy: function() {
96 me.reload();
97 },
98 },
99 }).show();
100 },
101
102 addRecovery: async function() {
103 let me = this;
104
105 Ext.create('PBS.window.AddTfaRecovery', {
106 listeners: {
107 destroy: function() {
108 me.reload();
109 },
110 },
111 }).show();
112 },
113
114 editItem: function() {
115 let me = this;
116 let view = me.getView();
117 let selection = view.getSelection();
118 if (selection.length !== 1 || selection[0].id.endsWith("/recovery")) {
119 return;
120 }
121
122 Ext.create('PBS.window.TfaEdit', {
123 'tfa-id': selection[0].data.fullid,
124 listeners: {
125 destroy: function() {
126 me.reload();
127 },
128 },
129 }).show();
130 },
131
132 renderUser: fullid => fullid.split('/')[0],
133
134 renderEnabled: enabled => {
135 if (enabled === undefined) {
136 return Proxmox.Utils.yesText;
137 } else {
138 return Proxmox.Utils.format_boolean(enabled);
139 }
140 },
141
142 onRemoveButton: function(btn, event, record) {
143 let me = this;
144
145 Ext.create('PBS.tfa.confirmRemove', {
146 message: Ext.String.format(
147 gettext('Are you sure you want to remove entry {0}'),
148 record.data.description,
149 ),
150 callback: password => me.removeItem(password, record),
151 })
152 .show();
153 },
154
155 removeItem: async function(password, record) {
156 let me = this;
157
158 let params = {};
159 if (password !== null) {
160 params.password = password;
161 }
162
163 try {
63fd8e58 164 me.getView().mask(gettext('Please wait...'), 'x-mask-loading');
fbeac4ea
WB
165 await PBS.Async.api2({
166 url: `/api2/extjs/access/tfa/${record.id}`,
167 method: 'DELETE',
168 params,
169 });
170 me.reload();
171 } catch (error) {
172 Ext.Msg.alert(gettext('Error'), error);
63fd8e58
WB
173 } finally {
174 me.getView().unmask();
175 }
fbeac4ea
WB
176 },
177 },
178
179 viewConfig: {
180 trackOver: false,
181 },
182
183 listeners: {
184 itemdblclick: 'editItem',
185 },
186
187 columns: [
188 {
189 header: gettext('User'),
190 width: 200,
191 sortable: true,
192 dataIndex: 'fullid',
193 renderer: 'renderUser',
194 },
195 {
196 header: gettext('Enabled'),
197 width: 80,
198 sortable: true,
199 dataIndex: 'enable',
200 renderer: 'renderEnabled',
201 },
202 {
203 header: gettext('TFA Type'),
204 width: 80,
205 sortable: true,
206 dataIndex: 'type',
207 },
208 {
209 header: gettext('Description'),
210 width: 300,
211 sortable: true,
212 dataIndex: 'description',
30fb19be 213 renderer: Ext.String.htmlEncode,
fbeac4ea
WB
214 },
215 ],
216
217 tbar: [
218 {
219 text: gettext('Add'),
220 menu: {
221 xtype: 'menu',
222 items: [
223 {
224 text: gettext('TOTP'),
225 itemId: 'totp',
226 iconCls: 'fa fa-fw fa-clock-o',
227 handler: 'addTotp',
228 },
229 {
230 text: gettext('Webauthn'),
231 itemId: 'webauthn',
232 iconCls: 'fa fa-fw fa-shield',
233 handler: 'addWebauthn',
234 },
235 {
236 text: gettext('Recovery Keys'),
237 itemId: 'recovery',
238 iconCls: 'fa fa-fw fa-file-text-o',
239 handler: 'addRecovery',
240 },
241 ],
242 },
243 },
244 {
245 xtype: 'proxmoxButton',
246 text: gettext('Edit'),
247 handler: 'editItem',
248 enableFn: rec => !rec.id.endsWith("/recovery"),
249 disabled: true,
250 },
251 {
252 xtype: 'proxmoxButton',
1cb89f30 253 disabled: true,
fbeac4ea
WB
254 text: gettext('Remove'),
255 getRecordName: rec => rec.data.description,
256 handler: 'onRemoveButton',
257 },
258 ],
259});
260
261Ext.define('PBS.tfa.confirmRemove', {
262 extend: 'Proxmox.window.Edit',
263
264 modal: true,
265 resizable: false,
266 title: gettext("Confirm Password"),
267 width: 512,
268 isCreate: true, // logic
269 isRemove: true,
270
271 url: '/access/tfa',
272
273 initComponent: function() {
274 let me = this;
275
276 if (!me.message) {
277 throw "missing message";
278 }
279
280 if (!me.callback) {
281 throw "missing callback";
282 }
283
284 me.callParent();
285
286 if (Proxmox.UserName === 'root@pam') {
287 me.lookup('password').setVisible(false);
288 me.lookup('password').setDisabled(true);
289 }
290
291 me.lookup('message').setHtml(Ext.String.htmlEncode(me.message));
292 },
293
294 submit: function() {
295 let me = this;
296 if (Proxmox.UserName === 'root@pam') {
297 me.callback(null);
298 } else {
299 me.callback(me.lookup('password').getValue());
300 }
301 me.close();
302 },
303
304 items: [
305 {
306 xtype: 'box',
307 padding: '5 5',
308 reference: 'message',
d91c6fd4 309 html: '',
fbeac4ea
WB
310 style: {
311 textAlign: "center",
312 },
313 },
314 {
315 xtype: 'textfield',
316 inputType: 'password',
317 fieldLabel: gettext('Password'),
318 minLength: 5,
319 reference: 'password',
320 name: 'password',
321 allowBlank: false,
322 validateBlank: true,
323 padding: '0 0 5 5',
324 emptyText: gettext('verify current password'),
325 },
326 ],
327});