]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/dc/TFAEdit.js
ui: tfa: make jsliunt a bit more happy
[pve-manager.git] / www / manager6 / dc / TFAEdit.js
CommitLineData
34c08874 1/*jslint confusion: true*/
24d2ed8c
WB
2Ext.define('PVE.window.TFAEdit', {
3 extend: 'Ext.window.Window',
4 mixins: ['Proxmox.Mixin.CBind'],
5
6 modal: true,
7 resizable: false,
8 title: gettext('Two Factor Authentication'),
9 subject: 'TFA',
10 url: '/api2/extjs/access/tfa',
11 width: 512,
12
13 layout: {
14 type: 'vbox',
15 align: 'stretch'
16 },
17
18 updateQrCode: function() {
19 var me = this;
20 var values = me.lookup('totp-form').getValues();
21 var algorithm = values.algorithm;
22 if (!algorithm) {
23 algorithm = 'SHA1';
24 }
25
26 me.qrcode.makeCode(
27 'otpauth://totp/' + encodeURIComponent(values.name) +
28 '?secret=' + values.secret +
29 '&period=' + values.step +
30 '&digits=' + values.digits +
31 '&algorithm=' + algorithm +
32 '&issuer=' + encodeURIComponent(values.issuer)
33 );
34
35 me.lookup('challenge').setVisible(true);
36 me.down('#qrbox').setVisible(true);
37 },
38
39 showError: function(error) {
40 var ErrorNames = {
41 '1': gettext('Other Error'),
42 '2': gettext('Bad Request'),
43 '3': gettext('Configuration Unsupported'),
44 '4': gettext('Device Ineligible'),
45 '5': gettext('Timeout')
46 };
47 Ext.Msg.alert(
48 gettext('Error'),
49 "U2F Error: " + (ErrorNames[error] || Proxmox.Utils.unknownText)
50 );
51 },
52
53 doU2FChallenge: function(response) {
54 var me = this;
55
56 var data = response.result.data;
57 me.lookup('password').setDisabled(true);
58 var msg = Ext.Msg.show({
59 title: 'U2F: '+gettext('Setup'),
60 message: gettext('Please press the button on your U2F Device'),
61 buttons: []
62 });
63 Ext.Function.defer(function() {
64 u2f.register(data.appId, [data], [], function(data) {
65 msg.close();
66 if (data.errorCode) {
67 me.showError(data.errorCode);
68 } else {
69 me.respondToU2FChallenge(data);
70 }
71 });
72 }, 500, me);
73 },
74
75 respondToU2FChallenge: function(data) {
76 var me = this;
77 var params = {
78 userid: me.userid,
79 action: 'confirm',
80 response: JSON.stringify(data)
81 };
82 if (Proxmox.UserName !== 'root@pam') {
83 params.password = me.lookup('password').value;
84 }
85 Proxmox.Utils.API2Request({
86 url: '/api2/extjs/access/tfa',
87 params: params,
88 method: 'PUT',
89 success: function() {
90 me.close();
91 Ext.Msg.show({
92 title: gettext('Success'),
93 message: gettext('U2F Device successfully connected.'),
94 buttons: Ext.Msg.OK
95 });
96 },
97 failure: function(response, opts) {
98 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
99 }
100 });
101 },
102
103 viewModel: {
104 data: {
105 in_totp_tab: true,
106 tfa_required: false,
34c08874 107 u2f_available: true
24d2ed8c
WB
108 }
109 },
110
111 afterLoadingRealm: function(realm_tfa_type) {
112 var me = this;
113 var viewmodel = me.getViewModel();
114 if (!realm_tfa_type) {
115 // There's no TFA enforced by the realm, everything works.
116 viewmodel.set('u2f_available', true);
117 viewmodel.set('tfa_required', false);
118 } else if (realm_tfa_type === 'oath') {
119 // The realm explicitly requires TOTP
120 viewmodel.set('tfa_required', true);
121 viewmodel.set('u2f_available', false);
122 } else {
123 // The realm enforces some other TFA type (yubico)
124 me.close();
125 Ext.Msg.alert(
126 gettext('Error'),
127 Ext.String.format(
128 gettext("Custom 2nd factor configuration is not supported on realms with '{0}' TFA."),
129 realm_tfa_type
130 )
131 );
132 }
133 //me.lookup('delete-button').setDisabled(has_tfa_configured);
134 //me.lookup('u2f-panel').setDisabled(has_tfa_configured);
135 },
136
137 controller: {
138 xclass: 'Ext.app.ViewController',
139 control: {
140 'field[qrupdate=true]': {
141 change: function() {
142 var me = this.getView();
143 me.updateQrCode();
144 }
145 },
146 '#': {
147 show: function() {
148 var me = this.getView();
149 me.down('#qrbox').getEl().appendChild(me.qrdiv);
150 me.down('#qrbox').setVisible(false);
151
152 if (Proxmox.UserName === 'root@pam') {
153 me.lookup('password').setVisible(false);
154 me.lookup('password').setDisabled(true);
155 }
156 me.lookup('challenge').setVisible(false);
157 }
158 },
159 '#tfatabs': {
160 tabchange: function(panel, newcard) {
161 var viewmodel = this.getViewModel();
162 viewmodel.set('in_totp_tab', newcard.itemId === 'totp-panel');
163 }
164 }
165 },
166
167 applySettings: function() {
168 var me = this;
169 var values = me.lookup('totp-form').getValues();
170 var params = {
171 userid: me.getView().userid,
172 action: 'new',
173 key: values.secret,
174 config: PVE.Parser.printPropertyString({
175 type: 'oath',
176 digits: values.digits,
34c08874 177 step: values.step
24d2ed8c
WB
178 }),
179 // this is used to verify that the client generates the correct codes:
34c08874 180 response: me.lookup('challenge').value
24d2ed8c
WB
181 };
182
183 if (Proxmox.UserName !== 'root@pam') {
184 params.password = me.lookup('password').value;
185 }
186
187 Proxmox.Utils.API2Request({
188 url: '/api2/extjs/access/tfa',
189 params: params,
190 method: 'PUT',
191 waitMsgTarget: me.getView(),
192 success: function(response, opts) {
193 me.getView().close();
194 },
195 failure: function(response, opts) {
196 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
197 }
198 });
199 },
200
201 deleteTFA: function() {
202 var me = this;
203 var values = me.lookup('totp-form').getValues();
204 var params = {
205 userid: me.getView().userid,
34c08874 206 action: 'delete'
24d2ed8c
WB
207 };
208
209 if (Proxmox.UserName !== 'root@pam') {
210 params.password = me.lookup('password').value;
211 }
212
213 Proxmox.Utils.API2Request({
214 url: '/api2/extjs/access/tfa',
215 params: params,
216 method: 'PUT',
217 waitMsgTarget: me.getView(),
218 success: function(response, opts) {
219 me.getView().close();
220 },
221 failure: function(response, opts) {
222 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
223 }
224 });
225 },
226
227 randomizeSecret: function() {
228 var me = this;
229 var rnd = new Uint8Array(16);
230 window.crypto.getRandomValues(rnd);
231 var data = '';
232 rnd.forEach(function(b) {
233 // just use the first 5 bit
234 b = b & 0x1f;
235 if (b < 26) {
236 // A..Z
237 data += String.fromCharCode(b + 0x41);
238 } else {
239 // 2..7
240 data += String.fromCharCode(b-26 + 0x32);
241 }
242 });
243 me.lookup('tfa-secret').setValue(data);
244 },
245
246 startU2FRegistration: function() {
247 var me = this;
248
249 var params = {
250 userid: me.getView().userid,
251 action: 'new'
252 };
253
254 if (Proxmox.UserName !== 'root@pam') {
255 params.password = me.lookup('password').value;
256 }
257
258 Proxmox.Utils.API2Request({
259 url: '/api2/extjs/access/tfa',
260 params: params,
261 method: 'PUT',
262 waitMsgTarget: me.getView(),
263 success: function(response) {
264 me.getView().doU2FChallenge(response);
265 },
266 failure: function(response, opts) {
267 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
268 }
269 });
270 }
271 },
272
273 items: [
274 {
275 xtype: 'tabpanel',
276 itemId: 'tfatabs',
277 border: false,
278 items: [
279 {
280 xtype: 'panel',
281 title: 'TOTP',
282 itemId: 'totp-panel',
283 border: false,
284 layout: {
285 type: 'vbox',
286 align: 'stretch'
287 },
288 items: [
289 {
290 xtype: 'form',
291 layout: 'anchor',
292 border: false,
293 reference: 'totp-form',
294 fieldDefaults: {
295 labelWidth: 120,
296 anchor: '100%',
34c08874 297 padding: '0 5'
24d2ed8c
WB
298 },
299 items: [
300 {
a8740316
TL
301 layout: 'hbox',
302 border: false,
303 padding: '5 0',
304 items: [{
305 xtype: 'textfield',
306 fieldLabel: gettext('Secret'),
307 name: 'secret',
308 reference: 'tfa_secret',
309 validateValue: function(value) {
310 return value.match(/^[A-Z2-7=]$/);
311 },
312 qrupdate: true,
313 flex: 4
24d2ed8c 314 },
a8740316
TL
315 {
316 xtype: 'button',
317 text: gettext('Randomize'),
318 reference: 'randomize_button',
319 handler: 'randomizeSecret',
320 flex: 1
321 }]
24d2ed8c
WB
322 },
323 {
324 xtype: 'numberfield',
325 fieldLabel: gettext('Time period'),
326 name: 'step',
327 value: 30,
328 minValue: 10,
34c08874 329 qrupdate: true
24d2ed8c
WB
330 },
331 {
332 xtype: 'numberfield',
333 fieldLabel: gettext('Digits'),
334 name: 'digits',
335 value: 6,
336 minValue: 6,
337 maxValue: 8,
34c08874 338 qrupdate: true
24d2ed8c
WB
339 },
340 {
341 xtype: 'textfield',
342 fieldLabel: gettext('Issuer Name'),
343 name: 'issuer',
344 value: 'Proxmox Web UI',
34c08874 345 qrupdate: true
24d2ed8c
WB
346 },
347 {
348 xtype: 'textfield',
ef50fb29 349 hidden: true,
24d2ed8c
WB
350 name: 'name',
351 cbind: {
34c08874 352 value: '{userid}'
24d2ed8c 353 },
34c08874 354 qrupdate: true
24d2ed8c
WB
355 }
356 ]
357 },
358 {
359 xtype: 'box',
360 itemId: 'qrbox',
361 visible: false, // will be enabled when generating a qr code
362 style: {
363 'background-color': 'white',
364 padding: '5px',
365 width: '266px',
34c08874 366 height: '266px'
24d2ed8c
WB
367 }
368 },
369 {
370 xtype: 'textfield',
371 fieldLabel: gettext('Code'),
372 labelWidth: 120,
373 reference: 'challenge',
374 padding: '0 5',
375 emptyText: gettext('verify TOTP authentication code')
376 }
377 ]
378 },
379 {
380 title: 'U2F',
381 itemId: 'u2f-panel',
382 reference: 'u2f-panel',
383 border: false,
384 padding: '5 5',
385 layout: {
386 type: 'vbox',
387 align: 'middle'
388 },
389 bind: {
390 disabled: '{!u2f_available}'
391 },
392 items: [
393 {
394 xtype: 'label',
395 width: 500,
34c08874 396 text: gettext('To register a U2F device, connect the device, then click the button and follow the instructions.')
24d2ed8c
WB
397 }
398 ]
399 }
400 ]
401 },
402 {
403 xtype: 'textfield',
404 inputType: 'password',
405 fieldLabel: gettext('Password'),
406 minLength: 5,
407 reference: 'password',
408 padding: '0 5',
409 labelWidth: 120,
410 emptyText: gettext('verify current password')
411 }
412 ],
413
414 buttons: [
24d2ed8c
WB
415 {
416 text: gettext('Apply'),
417 handler: 'applySettings',
418 bind: {
419 hidden: '{!in_totp_tab}',
420 disabled: '{!user_tfa}'
421 }
422 },
423 {
424 xtype: 'button',
425 text: gettext('Register U2F Device'),
426 handler: 'startU2FRegistration',
427 bind: {
428 hidden: '{in_totp_tab}'
429 }
430 },
431 {
432 text: gettext('Delete'),
433 reference: 'delete-button',
434 handler: 'deleteTFA',
435 bind: {
436 disabled: '{tfa_required}'
437 }
438 }
439 ],
440
441 initComponent: function() {
442 var me = this;
443
444 me.qrdiv = document.createElement('center');
445 me.qrcode = new QRCode(me.qrdiv, {
446 //text: "This is not the qr code you're looking for",
447 width: 256,
448 height: 256,
449 correctLevel: QRCode.CorrectLevel.M
450 });
451
452 var store = new Ext.data.Store({
453 model: 'pve-domains',
454 autoLoad: true
455 });
456
457 store.on('load', function() {
458 var user_realm = me.userid.split('@')[1];
459 var realm = me.store.findRecord('realm', user_realm);
460 me.afterLoadingRealm(realm && realm.data && realm.data.tfa);
461 }, me);
462
463 Ext.apply(me, { store: store });
464
465 me.callParent();
466 }
467});