]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/TFAEdit.js
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(me
.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 Proxmox
.Utils
.render_u2f_error(error
),
51 doU2FChallenge: function(res
) {
54 let challenge
= res
.result
.data
;
55 me
.lookup('password').setDisabled(true);
56 let 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(challenge
.appId
, [challenge
], [], function(response
) {
64 if (response
.errorCode
) {
65 me
.showError(response
.errorCode
);
67 me
.respondToU2FChallenge(response
);
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 this.getView().updateQrCode();
185 validitychange: function(field
, valid
) {
187 var viewModel
= me
.getViewModel();
188 var form
= me
.lookup('totp_form');
189 var challenge
= me
.lookup('challenge');
190 var password
= me
.lookup('password');
191 viewModel
.set('valid', form
.isValid() && challenge
.isValid() && password
.isValid());
196 let view
= this.getView();
198 Proxmox
.Utils
.API2Request({
199 url
: '/access/users/' + encodeURIComponent(view
.userid
) + '/tfa',
200 waitMsgTarget
: view
.down('#tfatabs'),
202 success: function(response
, opts
) {
203 let data
= response
.result
.data
;
204 view
.afterLoading(data
.realm
, data
.user
);
206 failure: function(response
, opts
) {
208 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
212 view
.qrdiv
= document
.createElement('center');
213 view
.qrcode
= new QRCode(view
.qrdiv
, {
216 correctLevel
: QRCode
.CorrectLevel
.M
,
218 view
.down('#qrbox').getEl().appendChild(view
.qrdiv
);
220 if (Proxmox
.UserName
=== 'root@pam') {
221 view
.lookup('password').setVisible(false);
222 view
.lookup('password').setDisabled(true);
227 tabchange: function(panel
, newcard
) {
228 this.getViewModel().set('in_totp_tab', newcard
.itemId
=== 'totp-panel');
233 applySettings: function() {
235 let values
= me
.lookup('totp_form').getValues();
237 userid
: me
.getView().userid
,
239 key
: 'v2-' + values
.secret
,
240 config
: PVE
.Parser
.printPropertyString({
242 digits
: values
.digits
,
245 // this is used to verify that the client generates the correct codes:
246 response
: me
.lookup('challenge').value
,
249 if (Proxmox
.UserName
!== 'root@pam') {
250 params
.password
= me
.lookup('password').value
;
253 Proxmox
.Utils
.API2Request({
254 url
: '/api2/extjs/access/tfa',
257 waitMsgTarget
: me
.getView(),
258 success: function(response
, opts
) {
259 me
.getView().close();
261 failure: function(response
, opts
) {
262 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
267 deleteTFA: function() {
270 userid
: me
.getView().userid
,
274 if (Proxmox
.UserName
!== 'root@pam') {
275 params
.password
= me
.lookup('password').value
;
278 Proxmox
.Utils
.API2Request({
279 url
: '/api2/extjs/access/tfa',
282 waitMsgTarget
: me
.getView(),
283 success: function(response
, opts
) {
284 me
.getView().close();
286 failure: function(response
, opts
) {
287 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
292 randomizeSecret: function() {
294 let rnd
= new Uint8Array(32);
295 window
.crypto
.getRandomValues(rnd
);
297 rnd
.forEach(function(b
) {
298 // secret must be base32, so just use the first 5 bits
301 data
+= String
.fromCharCode(b
+ 0x41); // A..Z
303 data
+= String
.fromCharCode(b
-26 + 0x32); // 2..7
306 me
.getViewModel().set('secret', data
);
309 startU2FRegistration: function() {
313 userid
: me
.getView().userid
,
317 if (Proxmox
.UserName
!== 'root@pam') {
318 params
.password
= me
.lookup('password').value
;
321 Proxmox
.Utils
.API2Request({
322 url
: '/api2/extjs/access/tfa',
325 waitMsgTarget
: me
.getView(),
326 success: function(response
) {
327 me
.getView().doU2FChallenge(response
);
329 failure: function(response
, opts
) {
330 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
340 reference
: 'tfatabs',
343 activeTab
: '{selectedTab}',
349 itemId
: 'totp-panel',
350 reference
: 'totp_panel',
354 disabled
: '{!canSetupTOTP}',
365 reference
: 'totp_form',
372 xtype
: 'displayfield',
373 fieldLabel
: gettext('User name'),
374 renderer
: Ext
.String
.htmlEncode
,
385 fieldLabel
: gettext('Secret'),
386 emptyText
: gettext('Unchanged'),
388 reference
: 'tfa_secret',
389 regex
: /^[A-Z2-7=]+$/,
390 regexText
: 'Must be base32 [A-Z2-7=]',
400 text
: gettext('Randomize'),
401 reference
: 'randomize_button',
402 handler
: 'randomizeSecret',
407 xtype
: 'numberfield',
408 fieldLabel
: gettext('Time period'),
410 // Google Authenticator ignores this and generates bogus data
417 xtype
: 'numberfield',
418 fieldLabel
: gettext('Digits'),
421 // Google Authenticator ignores this and generates bogus data
429 fieldLabel
: gettext('Issuer Name'),
431 value
: 'Proxmox Web UI',
439 visible
: false, // will be enabled when generating a qr code
441 visible
: '{!secretEmpty}',
444 'background-color': 'white',
452 fieldLabel
: gettext('Verification Code'),
454 reference
: 'challenge',
456 disabled
: '{!showTOTPVerifiction}',
457 visible
: '{showTOTPVerifiction}',
460 emptyText
: gettext('Scan QR code and enter TOTP auth. code to verify'),
467 reference
: 'u2f_panel',
476 disabled
: '{!canSetupU2F}',
482 text
: gettext('To register a U2F device, connect the device, then click the button and follow the instructions.'),
490 inputType
: 'password',
491 fieldLabel
: gettext('Password'),
493 reference
: 'password',
497 emptyText
: gettext('verify current password'),
503 xtype
: 'proxmoxHelpButton',
507 text
: gettext('Apply'),
508 handler
: 'applySettings',
510 hidden
: '{!in_totp_tab}',
511 disabled
: '{!valid}',
516 text
: gettext('Register U2F Device'),
517 handler
: 'startU2FRegistration',
519 hidden
: '{in_totp_tab}',
520 disabled
: '{tfa_type}',
524 text
: gettext('Delete'),
525 reference
: 'delete_button',
527 handler
: 'deleteTFA',
529 disabled
: '{!canDeleteTFA}',
534 initComponent: function() {
538 throw "no userid given";
543 Ext
.GlobalEvents
.fireEvent('proxmoxShowHelp', 'pveum_tfa_auth');