]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/TFAEdit.js
1 /*global u2f,QRCode,Uint8Array*/
2 /*jslint confusion: true*/
3 Ext
.define('PVE.window.TFAEdit', {
4 extend
: 'Ext.window.Window',
5 mixins
: ['Proxmox.Mixin.CBind'],
7 onlineHelp
: 'pveum_tfa_auth', // fake to ensure this gets a link target
11 title
: gettext('Two Factor Authentication'),
13 url
: '/api2/extjs/access/tfa',
21 updateQrCode: function() {
23 var values
= me
.lookup('totp_form').getValues();
24 var algorithm
= values
.algorithm
;
30 'otpauth://totp/' + encodeURIComponent(me
.userid
) +
31 '?secret=' + values
.secret
+
32 '&period=' + values
.step
+
33 '&digits=' + values
.digits
+
34 '&algorithm=' + algorithm
+
35 '&issuer=' + encodeURIComponent(values
.issuer
)
38 me
.lookup('challenge').setVisible(true);
39 me
.down('#qrbox').setVisible(true);
42 showError: function(error
) {
45 PVE
.Utils
.render_u2f_error(error
)
49 doU2FChallenge: function(response
) {
52 var data
= response
.result
.data
;
53 me
.lookup('password').setDisabled(true);
54 var msg
= Ext
.Msg
.show({
55 title
: 'U2F: '+gettext('Setup'),
56 message
: gettext('Please press the button on your U2F Device'),
59 Ext
.Function
.defer(function() {
60 u2f
.register(data
.appId
, [data
], [], function(data
) {
63 me
.showError(data
.errorCode
);
65 me
.respondToU2FChallenge(data
);
71 respondToU2FChallenge: function(data
) {
76 response
: JSON
.stringify(data
)
78 if (Proxmox
.UserName
!== 'root@pam') {
79 params
.password
= me
.lookup('password').value
;
81 Proxmox
.Utils
.API2Request({
82 url
: '/api2/extjs/access/tfa',
88 title
: gettext('Success'),
89 message
: gettext('U2F Device successfully connected.'),
93 failure: function(response
, opts
) {
94 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
103 tfa_type
: null, // dependencies of formulas should not be undefined
109 showTOTPVerifiction: function(get) {
110 return get('secret').length
> 0 && get('canSetupTOTP');
112 canDeleteTFA: function(get) {
113 return (get('tfa_type') !== null && !get('tfa_required'));
115 canSetupTOTP: function(get) {
116 var tfa
= get('tfa_type');
117 return (tfa
=== null || tfa
=== 'totp' || tfa
=== 1);
119 canSetupU2F: function(get) {
120 var tfa
= get('tfa_type');
121 return (get('u2f_available') && (tfa
=== null || tfa
=== 'u2f' || tfa
=== 1));
123 secretEmpty: function(get) {
124 return get('secret').length
=== 0;
126 selectedTab: function(get) {
127 return (get('tfa_type') || 'totp') + '-panel';
132 afterLoading: function(realm_tfa_type
, user_tfa_type
) {
134 var viewmodel
= me
.getViewModel();
135 if (user_tfa_type
=== 'oath') {
136 user_tfa_type
= 'totp';
137 viewmodel
.set('secret', '');
140 // if the user has no tfa, generate a secret for him
141 if (!user_tfa_type
) {
142 me
.getController().randomizeSecret();
145 viewmodel
.set('tfa_type', user_tfa_type
|| null);
146 if (!realm_tfa_type
) {
147 // There's no TFA enforced by the realm, everything works.
148 viewmodel
.set('u2f_available', true);
149 viewmodel
.set('tfa_required', false);
150 } else if (realm_tfa_type
=== 'oath') {
151 // The realm explicitly requires TOTP
152 if (user_tfa_type
!== 'totp' && user_tfa_type
!== null) {
153 // user had a different tfa method, so
154 // we have to change back to the totp tab and
156 viewmodel
.set('tfa_type', 'totp');
157 me
.getController().randomizeSecret();
159 viewmodel
.set('tfa_required', true);
160 viewmodel
.set('u2f_available', false);
162 // The realm enforces some other TFA type (yubico)
167 gettext("Custom 2nd factor configuration is not supported on realms with '{0}' TFA."),
175 xclass
: 'Ext.app.ViewController',
177 'field[qrupdate=true]': {
179 var me
= this.getView();
184 validitychange: function(field
, valid
) {
186 var viewModel
= me
.getViewModel();
187 var form
= me
.lookup('totp_form');
188 var challenge
= me
.lookup('challenge');
189 var password
= me
.lookup('password');
190 viewModel
.set('valid', form
.isValid() && challenge
.isValid() && password
.isValid());
195 var me
= this.getView();
196 var viewmodel
= this.getViewModel();
198 var loadMaskContainer
= me
.down('#tfatabs');
199 Proxmox
.Utils
.API2Request({
200 url
: '/access/users/' + encodeURIComponent(me
.userid
) + '/tfa',
201 waitMsgTarget
: loadMaskContainer
,
203 success: function(response
, opts
) {
204 var data
= response
.result
.data
;
205 me
.afterLoading(data
.realm
, data
.user
);
207 failure: function(response
, opts
) {
209 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
213 me
.qrdiv
= document
.createElement('center');
214 me
.qrcode
= new QRCode(me
.qrdiv
, {
217 correctLevel
: QRCode
.CorrectLevel
.M
219 me
.down('#qrbox').getEl().appendChild(me
.qrdiv
);
221 if (Proxmox
.UserName
=== 'root@pam') {
222 me
.lookup('password').setVisible(false);
223 me
.lookup('password').setDisabled(true);
228 tabchange: function(panel
, newcard
) {
229 var viewmodel
= this.getViewModel();
230 viewmodel
.set('in_totp_tab', newcard
.itemId
=== 'totp-panel');
235 applySettings: function() {
237 var values
= me
.lookup('totp_form').getValues();
239 userid
: me
.getView().userid
,
241 key
: 'v2-' + values
.secret
,
242 config
: PVE
.Parser
.printPropertyString({
244 digits
: values
.digits
,
247 // this is used to verify that the client generates the correct codes:
248 response
: me
.lookup('challenge').value
251 if (Proxmox
.UserName
!== 'root@pam') {
252 params
.password
= me
.lookup('password').value
;
255 Proxmox
.Utils
.API2Request({
256 url
: '/api2/extjs/access/tfa',
259 waitMsgTarget
: me
.getView(),
260 success: function(response
, opts
) {
261 me
.getView().close();
263 failure: function(response
, opts
) {
264 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
269 deleteTFA: function() {
271 var values
= me
.lookup('totp_form').getValues();
273 userid
: me
.getView().userid
,
277 if (Proxmox
.UserName
!== 'root@pam') {
278 params
.password
= me
.lookup('password').value
;
281 Proxmox
.Utils
.API2Request({
282 url
: '/api2/extjs/access/tfa',
285 waitMsgTarget
: me
.getView(),
286 success: function(response
, opts
) {
287 me
.getView().close();
289 failure: function(response
, opts
) {
290 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
295 randomizeSecret: function() {
297 var rnd
= new Uint8Array(32);
298 window
.crypto
.getRandomValues(rnd
);
300 rnd
.forEach(function(b
) {
301 // secret must be base32, so just use the first 5 bits
305 data
+= String
.fromCharCode(b
+ 0x41);
308 data
+= String
.fromCharCode(b
-26 + 0x32);
311 me
.getViewModel().set('secret', data
);
314 startU2FRegistration: function() {
318 userid
: me
.getView().userid
,
322 if (Proxmox
.UserName
!== 'root@pam') {
323 params
.password
= me
.lookup('password').value
;
326 Proxmox
.Utils
.API2Request({
327 url
: '/api2/extjs/access/tfa',
330 waitMsgTarget
: me
.getView(),
331 success: function(response
) {
332 me
.getView().doU2FChallenge(response
);
334 failure: function(response
, opts
) {
335 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
345 reference
: 'tfatabs',
348 activeTab
: '{selectedTab}',
354 itemId
: 'totp-panel',
355 reference
: 'totp_panel',
359 disabled
: '{!canSetupTOTP}'
370 reference
: 'totp_form',
377 xtype
: 'displayfield',
378 fieldLabel
: gettext('User name'),
379 renderer
: Ext
.String
.htmlEncode
,
390 fieldLabel
: gettext('Secret'),
391 emptyText
: gettext('Unchanged'),
393 reference
: 'tfa_secret',
394 regex
: /^[A-Z2-7=]+$/,
395 regexText
: 'Must be base32 [A-Z2-7=]',
405 text
: gettext('Randomize'),
406 reference
: 'randomize_button',
407 handler
: 'randomizeSecret',
412 xtype
: 'numberfield',
413 fieldLabel
: gettext('Time period'),
415 // Google Authenticator ignores this and generates bogus data
422 xtype
: 'numberfield',
423 fieldLabel
: gettext('Digits'),
426 // Google Authenticator ignores this and generates bogus data
434 fieldLabel
: gettext('Issuer Name'),
436 value
: 'Proxmox Web UI',
444 visible
: false, // will be enabled when generating a qr code
446 visible
: '{!secretEmpty}',
449 'background-color': 'white',
457 fieldLabel
: gettext('Verification Code'),
459 reference
: 'challenge',
461 disabled
: '{!showTOTPVerifiction}',
462 visible
: '{showTOTPVerifiction}',
465 emptyText
: gettext('Scan QR code and enter TOTP auth. code to verify')
472 reference
: 'u2f_panel',
481 disabled
: '{!canSetupU2F}'
487 text
: gettext('To register a U2F device, connect the device, then click the button and follow the instructions.')
495 inputType
: 'password',
496 fieldLabel
: gettext('Password'),
498 reference
: 'password',
502 emptyText
: gettext('verify current password')
508 xtype
: 'proxmoxHelpButton'
512 text
: gettext('Apply'),
513 handler
: 'applySettings',
515 hidden
: '{!in_totp_tab}',
521 text
: gettext('Register U2F Device'),
522 handler
: 'startU2FRegistration',
524 hidden
: '{in_totp_tab}',
525 disabled
: '{tfa_type}'
529 text
: gettext('Delete'),
530 reference
: 'delete_button',
532 handler
: 'deleteTFA',
534 disabled
: '{!canDeleteTFA}'
539 initComponent: function() {
543 throw "no userid given";
548 Ext
.GlobalEvents
.fireEvent('proxmoxShowHelp', 'pveum_tfa_auth');