]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/panel/TfaView.js
tfa view: fix WebAuthn casing
[proxmox-widget-toolkit.git] / src / panel / TfaView.js
1 Ext.define('pmx-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
11 Ext.define('pmx-tfa-entry', {
12 extend: 'Ext.data.Model',
13 fields: ['fullid', 'userid', 'type', 'description', 'created', 'enable'],
14 idProperty: 'fullid',
15 });
16
17
18 Ext.define('Proxmox.panel.TfaView', {
19 extend: 'Ext.grid.GridPanel',
20 alias: 'widget.pmxTfaView',
21 mixins: ['Proxmox.Mixin.CBind'],
22
23 title: gettext('Second Factors'),
24 reference: 'tfaview',
25
26 issuerName: 'Proxmox',
27 yubicoEnabled: false,
28
29 cbindData: function(initialConfig) {
30 let me = this;
31 return {
32 yubicoEnabled: me.yubicoEnabled,
33 };
34 },
35
36 store: {
37 type: 'diff',
38 autoDestroy: true,
39 autoDestroyRstore: true,
40 model: 'pmx-tfa-entry',
41 rstore: {
42 type: 'store',
43 proxy: 'memory',
44 storeid: 'pmx-tfa-entry',
45 model: 'pmx-tfa-entry',
46 },
47 },
48
49 controller: {
50 xclass: 'Ext.app.ViewController',
51
52 init: function(view) {
53 let me = this;
54 view.tfaStore = Ext.create('Proxmox.data.UpdateStore', {
55 autoStart: true,
56 interval: 5 * 1000,
57 storeid: 'pmx-tfa-users',
58 model: 'pmx-tfa-users',
59 });
60 view.tfaStore.on('load', this.onLoad, this);
61 view.on('destroy', view.tfaStore.stopUpdate);
62 Proxmox.Utils.monStoreErrors(view, view.tfaStore);
63 },
64
65 reload: function() { this.getView().tfaStore.load(); },
66
67 onLoad: function(store, data, success) {
68 if (!success) return;
69
70 let records = [];
71 Ext.Array.each(data, user => {
72 Ext.Array.each(user.data.entries, entry => {
73 records.push({
74 fullid: `${user.id}/${entry.id}`,
75 userid: user.id,
76 type: entry.type,
77 description: entry.description,
78 created: entry.created,
79 enable: entry.enable,
80 });
81 });
82 });
83
84 let rstore = this.getView().store.rstore;
85 rstore.loadData(records);
86 rstore.fireEvent('load', rstore, records, true);
87 },
88
89 addTotp: function() {
90 let me = this;
91
92 Ext.create('Proxmox.window.AddTotp', {
93 isCreate: true,
94 issuerName: me.getView().issuerName,
95 listeners: {
96 destroy: function() {
97 me.reload();
98 },
99 },
100 }).show();
101 },
102
103 addWebauthn: function() {
104 let me = this;
105
106 Ext.create('Proxmox.window.AddWebauthn', {
107 isCreate: true,
108 autoShow: true,
109 listeners: {
110 destroy: () => me.reload(),
111 },
112 });
113 },
114
115 addRecovery: async function() {
116 let me = this;
117
118 Ext.create('Proxmox.window.AddTfaRecovery', {
119 autoShow: true,
120 listeners: {
121 destroy: () => me.reload(),
122 },
123 });
124 },
125
126 addYubico: function() {
127 let me = this;
128
129 Ext.create('Proxmox.window.AddYubico', {
130 isCreate: true,
131 autoShow: true,
132 listeners: {
133 destroy: () => me.reload(),
134 },
135 });
136 },
137
138 editItem: function() {
139 let me = this;
140 let view = me.getView();
141 let selection = view.getSelection();
142 if (selection.length !== 1 || selection[0].id.endsWith("/recovery")) {
143 return;
144 }
145
146 Ext.create('Proxmox.window.TfaEdit', {
147 'tfa-id': selection[0].data.fullid,
148 autoShow: true,
149 listeners: {
150 destroy: () => me.reload(),
151 },
152 });
153 },
154
155 renderUser: fullid => fullid.split('/')[0],
156
157 renderEnabled: enabled => {
158 if (enabled === undefined) {
159 return Proxmox.Utils.yesText;
160 } else {
161 return Proxmox.Utils.format_boolean(enabled);
162 }
163 },
164
165 onRemoveButton: function(btn, event, record) {
166 let me = this;
167
168 Ext.create('Proxmox.tfa.confirmRemove', {
169 ...record.data,
170 callback: password => me.removeItem(password, record),
171 autoShow: true,
172 });
173 },
174
175 removeItem: async function(password, record) {
176 let me = this;
177
178 if (password !== null) {
179 password = '?password=' + encodeURIComponent(password);
180 } else {
181 password = '';
182 }
183
184 try {
185 me.getView().mask(gettext('Please wait...'), 'x-mask-loading');
186 await Proxmox.Async.api2({
187 url: `/api2/extjs/access/tfa/${record.id}${password}`,
188 method: 'DELETE',
189 });
190 me.reload();
191 } catch (response) {
192 Ext.Msg.alert(gettext('Error'), response.result.message);
193 } finally {
194 me.getView().unmask();
195 }
196 },
197 },
198
199 viewConfig: {
200 trackOver: false,
201 },
202
203 listeners: {
204 itemdblclick: 'editItem',
205 },
206
207 columns: [
208 {
209 header: gettext('User'),
210 width: 200,
211 sortable: true,
212 dataIndex: 'fullid',
213 renderer: 'renderUser',
214 },
215 {
216 header: gettext('Enabled'),
217 width: 80,
218 sortable: true,
219 dataIndex: 'enable',
220 renderer: 'renderEnabled',
221 },
222 {
223 header: gettext('TFA Type'),
224 width: 80,
225 sortable: true,
226 dataIndex: 'type',
227 },
228 {
229 header: gettext('Created'),
230 width: 150,
231 sortable: true,
232 dataIndex: 'created',
233 renderer: t => !t ? 'N/A' : Proxmox.Utils.render_timestamp(t),
234 },
235 {
236 header: gettext('Description'),
237 width: 300,
238 sortable: true,
239 dataIndex: 'description',
240 renderer: Ext.String.htmlEncode,
241 flex: 1,
242 },
243 ],
244
245 tbar: [
246 {
247 text: gettext('Add'),
248 cbind: {},
249 menu: {
250 xtype: 'menu',
251 items: [
252 {
253 text: gettext('TOTP'),
254 itemId: 'totp',
255 iconCls: 'fa fa-fw fa-clock-o',
256 handler: 'addTotp',
257 },
258 {
259 text: gettext('WebAuthn'),
260 itemId: 'webauthn',
261 iconCls: 'fa fa-fw fa-shield',
262 handler: 'addWebauthn',
263 },
264 {
265 text: gettext('Recovery Keys'),
266 itemId: 'recovery',
267 iconCls: 'fa fa-fw fa-file-text-o',
268 handler: 'addRecovery',
269 },
270 {
271 text: gettext('Yubico OTP'),
272 itemId: 'yubico',
273 iconCls: 'fa fa-fw fa-yahoo', // close enough
274 handler: 'addYubico',
275 cbind: {
276 hidden: '{!yubicoEnabled}',
277 },
278 },
279 ],
280 },
281 },
282 '-',
283 {
284 xtype: 'proxmoxButton',
285 text: gettext('Edit'),
286 handler: 'editItem',
287 enableFn: rec => !rec.id.endsWith("/recovery"),
288 disabled: true,
289 },
290 {
291 xtype: 'proxmoxButton',
292 disabled: true,
293 text: gettext('Remove'),
294 getRecordName: rec => rec.data.description,
295 handler: 'onRemoveButton',
296 },
297 ],
298 });