]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/TFAEdit.js
1 /*global u2f,QRCode,Uint8Array*/
2 Ext
.define('PVE.window.TFAEdit', {
3 extend
: 'Ext.window.Window',
4 mixins
: ['Proxmox.Mixin.CBind'],
6 onlineHelp
: 'pveum_tfa_auth', // fake to ensure this gets a link target
10 title
: gettext('Two Factor Authentication'),
12 url
: '/api2/extjs/access/tfa',
20 updateQrCode: function() {
22 var values
= me
.lookup('totp_form').getValues();
23 var algorithm
= values
.algorithm
;
30 encodeURIComponent(values
.issuer
) +
32 encodeURIComponent(values
.userid
) +
33 '?secret=' + values
.secret
+
34 '&period=' + values
.step
+
35 '&digits=' + values
.digits
+
36 '&algorithm=' + algorithm
+
37 '&issuer=' + encodeURIComponent(values
.issuer
),
40 me
.lookup('challenge').setVisible(true);
41 me
.down('#qrbox').setVisible(true);
44 showError: function(error
) {
47 PVE
.Utils
.render_u2f_error(error
),
51 doU2FChallenge: function(response
) {
54 var data
= response
.result
.data
;
55 me
.lookup('password').setDisabled(true);
56 var msg
= Ext
.Msg
.show({
57 title
: 'U2F: '+gettext('Setup'),
58 message
: gettext('Please press the button on your U2F Device'),
61 Ext
.Function
.defer(function() {
62 u2f
.register(data
.appId
, [data
], [], function(data
) {
65 me
.showError(data
.errorCode
);
67 me
.respondToU2FChallenge(data
);
73 respondToU2FChallenge: function(data
) {
78 response
: JSON
.stringify(data
),
80 if (Proxmox
.UserName
!== 'root@pam') {
81 params
.password
= me
.lookup('password').value
;
83 Proxmox
.Utils
.API2Request({
84 url
: '/api2/extjs/access/tfa',
90 title
: gettext('Success'),
91 message
: gettext('U2F Device successfully connected.'),
95 failure: function(response
, opts
) {
96 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
105 tfa_type
: null, // dependencies of formulas should not be undefined
111 showTOTPVerifiction: function(get) {
112 return get('secret').length
> 0 && get('canSetupTOTP');
114 canDeleteTFA: function(get) {
115 return (get('tfa_type') !== null && !get('tfa_required'));
117 canSetupTOTP: function(get) {
118 var tfa
= get('tfa_type');
119 return (tfa
=== null || tfa
=== 'totp' || tfa
=== 1);
121 canSetupU2F: function(get) {
122 var tfa
= get('tfa_type');
123 return (get('u2f_available') && (tfa
=== null || tfa
=== 'u2f' || tfa
=== 1));
125 secretEmpty: function(get) {
126 return get('secret').length
=== 0;
128 selectedTab: function(get) {
129 return (get('tfa_type') || 'totp') + '-panel';
134 afterLoading: function(realm_tfa_type
, user_tfa_type
) {
136 var viewmodel
= me
.getViewModel();
137 if (user_tfa_type
=== 'oath') {
138 user_tfa_type
= 'totp';
139 viewmodel
.set('secret', '');
142 // if the user has no tfa, generate a secret for him
143 if (!user_tfa_type
) {
144 me
.getController().randomizeSecret();
147 viewmodel
.set('tfa_type', user_tfa_type
|| null);
148 if (!realm_tfa_type
) {
149 // There's no TFA enforced by the realm, everything works.
150 viewmodel
.set('u2f_available', true);
151 viewmodel
.set('tfa_required', false);
152 } else if (realm_tfa_type
=== 'oath') {
153 // The realm explicitly requires TOTP
154 if (user_tfa_type
!== 'totp' && user_tfa_type
!== null) {
155 // user had a different tfa method, so
156 // we have to change back to the totp tab and
158 viewmodel
.set('tfa_type', 'totp');
159 me
.getController().randomizeSecret();
161 viewmodel
.set('tfa_required', true);
162 viewmodel
.set('u2f_available', false);
164 // The realm enforces some other TFA type (yubico)
169 gettext("Custom 2nd factor configuration is not supported on realms with '{0}' TFA."),
177 xclass
: 'Ext.app.ViewController',
179 'field[qrupdate=true]': {
181 var me
= this.getView();
186 validitychange: function(field
, valid
) {
188 var viewModel
= me
.getViewModel();
189 var form
= me
.lookup('totp_form');
190 var challenge
= me
.lookup('challenge');
191 var password
= me
.lookup('password');
192 viewModel
.set('valid', form
.isValid() && challenge
.isValid() && password
.isValid());
197 var me
= this.getView();
198 var viewmodel
= this.getViewModel();
200 var loadMaskContainer
= me
.down('#tfatabs');
201 Proxmox
.Utils
.API2Request({
202 url
: '/access/users/' + encodeURIComponent(me
.userid
) + '/tfa',
203 waitMsgTarget
: loadMaskContainer
,
205 success: function(response
, opts
) {
206 var data
= response
.result
.data
;
207 me
.afterLoading(data
.realm
, data
.user
);
209 failure: function(response
, opts
) {
211 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
215 me
.qrdiv
= document
.createElement('center');
216 me
.qrcode
= new QRCode(me
.qrdiv
, {
219 correctLevel
: QRCode
.CorrectLevel
.M
,
221 me
.down('#qrbox').getEl().appendChild(me
.qrdiv
);
223 if (Proxmox
.UserName
=== 'root@pam') {
224 me
.lookup('password').setVisible(false);
225 me
.lookup('password').setDisabled(true);
230 tabchange: function(panel
, newcard
) {
231 var viewmodel
= this.getViewModel();
232 viewmodel
.set('in_totp_tab', newcard
.itemId
=== 'totp-panel');
237 applySettings: function() {
239 var values
= me
.lookup('totp_form').getValues();
241 userid
: me
.getView().userid
,
243 key
: 'v2-' + values
.secret
,
244 config
: PVE
.Parser
.printPropertyString({
246 digits
: values
.digits
,
249 // this is used to verify that the client generates the correct codes:
250 response
: me
.lookup('challenge').value
,
253 if (Proxmox
.UserName
!== 'root@pam') {
254 params
.password
= me
.lookup('password').value
;
257 Proxmox
.Utils
.API2Request({
258 url
: '/api2/extjs/access/tfa',
261 waitMsgTarget
: me
.getView(),
262 success: function(response
, opts
) {
263 me
.getView().close();
265 failure: function(response
, opts
) {
266 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
271 deleteTFA: function() {
273 var values
= me
.lookup('totp_form').getValues();
275 userid
: me
.getView().userid
,
279 if (Proxmox
.UserName
!== 'root@pam') {
280 params
.password
= me
.lookup('password').value
;
283 Proxmox
.Utils
.API2Request({
284 url
: '/api2/extjs/access/tfa',
287 waitMsgTarget
: me
.getView(),
288 success: function(response
, opts
) {
289 me
.getView().close();
291 failure: function(response
, opts
) {
292 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
297 randomizeSecret: function() {
299 var rnd
= new Uint8Array(32);
300 window
.crypto
.getRandomValues(rnd
);
302 rnd
.forEach(function(b
) {
303 // secret must be base32, so just use the first 5 bits
307 data
+= String
.fromCharCode(b
+ 0x41);
310 data
+= String
.fromCharCode(b
-26 + 0x32);
313 me
.getViewModel().set('secret', data
);
316 startU2FRegistration: function() {
320 userid
: me
.getView().userid
,
324 if (Proxmox
.UserName
!== 'root@pam') {
325 params
.password
= me
.lookup('password').value
;
328 Proxmox
.Utils
.API2Request({
329 url
: '/api2/extjs/access/tfa',
332 waitMsgTarget
: me
.getView(),
333 success: function(response
) {
334 me
.getView().doU2FChallenge(response
);
336 failure: function(response
, opts
) {
337 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
347 reference
: 'tfatabs',
350 activeTab
: '{selectedTab}',
356 itemId
: 'totp-panel',
357 reference
: 'totp_panel',
361 disabled
: '{!canSetupTOTP}',
372 reference
: 'totp_form',
379 xtype
: 'displayfield',
380 fieldLabel
: gettext('User name'),
381 renderer
: Ext
.String
.htmlEncode
,
392 fieldLabel
: gettext('Secret'),
393 emptyText
: gettext('Unchanged'),
395 reference
: 'tfa_secret',
396 regex
: /^[A-Z2-7=]+$/,
397 regexText
: 'Must be base32 [A-Z2-7=]',
407 text
: gettext('Randomize'),
408 reference
: 'randomize_button',
409 handler
: 'randomizeSecret',
414 xtype
: 'numberfield',
415 fieldLabel
: gettext('Time period'),
417 // Google Authenticator ignores this and generates bogus data
424 xtype
: 'numberfield',
425 fieldLabel
: gettext('Digits'),
428 // Google Authenticator ignores this and generates bogus data
436 fieldLabel
: gettext('Issuer Name'),
438 value
: 'Proxmox Web UI',
446 visible
: false, // will be enabled when generating a qr code
448 visible
: '{!secretEmpty}',
451 'background-color': 'white',
459 fieldLabel
: gettext('Verification Code'),
461 reference
: 'challenge',
463 disabled
: '{!showTOTPVerifiction}',
464 visible
: '{showTOTPVerifiction}',
467 emptyText
: gettext('Scan QR code and enter TOTP auth. code to verify'),
474 reference
: 'u2f_panel',
483 disabled
: '{!canSetupU2F}',
489 text
: gettext('To register a U2F device, connect the device, then click the button and follow the instructions.'),
497 inputType
: 'password',
498 fieldLabel
: gettext('Password'),
500 reference
: 'password',
504 emptyText
: gettext('verify current password'),
510 xtype
: 'proxmoxHelpButton',
514 text
: gettext('Apply'),
515 handler
: 'applySettings',
517 hidden
: '{!in_totp_tab}',
518 disabled
: '{!valid}',
523 text
: gettext('Register U2F Device'),
524 handler
: 'startU2FRegistration',
526 hidden
: '{in_totp_tab}',
527 disabled
: '{tfa_type}',
531 text
: gettext('Delete'),
532 reference
: 'delete_button',
534 handler
: 'deleteTFA',
536 disabled
: '{!canDeleteTFA}',
541 initComponent: function() {
545 throw "no userid given";
550 Ext
.GlobalEvents
.fireEvent('proxmoxShowHelp', 'pveum_tfa_auth');