]>
Commit | Line | Data |
---|---|---|
fbeac4ea WB |
1 | Ext.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 | ||
11 | Ext.define('pbs-tfa-entry', { | |
12 | extend: 'Ext.data.Model', | |
13 | fields: ['fullid', 'type', 'description', 'enable'], | |
14 | idProperty: 'fullid', | |
15 | }); | |
16 | ||
17 | ||
18 | Ext.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 | ||
261 | Ext.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 | }); |