]>
Commit | Line | Data |
---|---|---|
6a1c9c29 | 1 | /*global u2f*/ |
88d5be7d DM |
2 | Ext.define('PVE.window.LoginWindow', { |
3 | extend: 'Ext.window.Window', | |
4 | ||
3dd99bab DM |
5 | viewModel: { |
6 | data: { | |
7 | openid: false, | |
8 | }, | |
9 | formulas: { | |
10 | button_text: function(get) { | |
11 | if (get("openid") === true) { | |
12 | return gettext("Login (OpenID redirect)"); | |
13 | } else { | |
14 | return gettext("Login"); | |
15 | } | |
16 | }, | |
17 | }, | |
18 | }, | |
19 | ||
04237985 DM |
20 | controller: { |
21 | ||
22 | xclass: 'Ext.app.ViewController', | |
da096950 DM |
23 | |
24 | onLogon: function() { | |
25 | var me = this; | |
26 | ||
27 | var form = this.lookupReference('loginForm'); | |
65532495 DC |
28 | var unField = this.lookupReference('usernameField'); |
29 | var saveunField = this.lookupReference('saveunField'); | |
da096950 DM |
30 | var view = this.getView(); |
31 | ||
24d2ed8c WB |
32 | if (!form.isValid()) { |
33 | return; | |
34 | } | |
35 | ||
3dd99bab DM |
36 | let creds = form.getValues(); |
37 | ||
38 | if (this.getViewModel().data.openid === true) { | |
39 | const redirectURL = location.origin; | |
40 | Proxmox.Utils.API2Request({ | |
41 | url: '/api2/extjs/access/openid/auth-url', | |
42 | params: { | |
43 | realm: creds.realm, | |
44 | "redirect-url": redirectURL, | |
45 | }, | |
46 | method: 'POST', | |
47 | success: function(resp, opts) { | |
48 | window.location = resp.result.data; | |
49 | }, | |
50 | failure: function(resp, opts) { | |
51 | Proxmox.Utils.authClear(); | |
52 | form.unmask(); | |
53 | Ext.MessageBox.alert( | |
54 | gettext('Error'), | |
cfc9f960 | 55 | gettext('OpenID redirect failed.') + `<br>${resp.htmlStatus}`, |
3dd99bab DM |
56 | ); |
57 | }, | |
58 | }); | |
59 | return; | |
60 | } | |
61 | ||
24d2ed8c WB |
62 | view.el.mask(gettext('Please wait...'), 'x-mask-loading'); |
63 | ||
64 | // set or clear username | |
65 | var sp = Ext.state.Manager.getProvider(); | |
66 | if (saveunField.getValue() === true) { | |
67 | sp.set(unField.getStateId(), unField.getValue()); | |
68 | } else { | |
69 | sp.clear(unField.getStateId()); | |
70 | } | |
71 | sp.set(saveunField.getStateId(), saveunField.getValue()); | |
72 | ||
73 | form.submit({ | |
8058410f | 74 | failure: function(f, resp) { |
4c86db12 | 75 | me.failure(resp); |
24d2ed8c | 76 | }, |
8058410f | 77 | success: function(f, resp) { |
24d2ed8c | 78 | view.el.unmask(); |
da096950 | 79 | |
24d2ed8c | 80 | var data = resp.result.data; |
313eb589 WB |
81 | if (Ext.isDefined(data.NeedTFA)) { |
82 | // Store first factor login information first: | |
83 | data.LoggedOut = true; | |
84 | Proxmox.Utils.setAuthData(data); | |
85 | ||
86 | if (Ext.isDefined(data.U2FChallenge)) { | |
87 | me.perform_u2f(data); | |
88 | } else { | |
89 | me.perform_otp(); | |
90 | } | |
24d2ed8c | 91 | } else { |
4c86db12 | 92 | me.success(data); |
24d2ed8c | 93 | } |
f6710aac | 94 | }, |
24d2ed8c | 95 | }); |
4c86db12 TL |
96 | }, |
97 | failure: function(resp) { | |
98 | var me = this; | |
99 | var view = me.getView(); | |
100 | view.el.unmask(); | |
101 | var handler = function() { | |
102 | var uf = me.lookupReference('usernameField'); | |
103 | uf.focus(true, true); | |
24d2ed8c | 104 | }; |
65532495 | 105 | |
f41b67ab OB |
106 | let emsg = gettext("Login failed. Please try again"); |
107 | ||
108 | if (resp.failureType === "connect") { | |
e5d8aebb | 109 | emsg = gettext("Connection failure. Network error or Proxmox VE services not running?"); |
f41b67ab OB |
110 | } |
111 | ||
112 | Ext.MessageBox.alert(gettext('Error'), emsg, handler); | |
4c86db12 TL |
113 | }, |
114 | success: function(data) { | |
115 | var me = this; | |
116 | var view = me.getView(); | |
117 | var handler = view.handler || Ext.emptyFn; | |
118 | handler.call(me, data); | |
119 | view.close(); | |
120 | }, | |
121 | ||
313eb589 WB |
122 | perform_otp: function() { |
123 | var me = this; | |
124 | var win = Ext.create('PVE.window.TFALoginWindow', { | |
125 | onLogin: function(value) { | |
126 | me.finish_tfa(value); | |
127 | }, | |
128 | onCancel: function() { | |
129 | Proxmox.LoggedOut = false; | |
130 | Proxmox.Utils.authClear(); | |
131 | me.getView().show(); | |
f6710aac | 132 | }, |
313eb589 WB |
133 | }); |
134 | win.show(); | |
135 | }, | |
136 | ||
4c86db12 TL |
137 | perform_u2f: function(data) { |
138 | var me = this; | |
4c86db12 TL |
139 | // Show the message: |
140 | var msg = Ext.Msg.show({ | |
141 | title: 'U2F: '+gettext('Verification'), | |
142 | message: gettext('Please press the button on your U2F Device'), | |
f6710aac | 143 | buttons: [], |
4c86db12 TL |
144 | }); |
145 | var chlg = data.U2FChallenge; | |
146 | var key = { | |
147 | version: chlg.version, | |
f6710aac | 148 | keyHandle: chlg.keyHandle, |
24d2ed8c | 149 | }; |
4c86db12 TL |
150 | u2f.sign(chlg.appId, chlg.challenge, [key], function(res) { |
151 | msg.close(); | |
152 | if (res.errorCode) { | |
153 | Proxmox.Utils.authClear(); | |
2d41c7e6 | 154 | Ext.Msg.alert(gettext('Error'), PVE.Utils.render_u2f_error(res.errorCode)); |
4c86db12 TL |
155 | return; |
156 | } | |
157 | delete res.errorCode; | |
313eb589 | 158 | me.finish_tfa(JSON.stringify(res)); |
4c86db12 TL |
159 | }); |
160 | }, | |
313eb589 | 161 | finish_tfa: function(res) { |
4c86db12 TL |
162 | var me = this; |
163 | var view = me.getView(); | |
164 | view.el.mask(gettext('Please wait...'), 'x-mask-loading'); | |
4c86db12 TL |
165 | Proxmox.Utils.API2Request({ |
166 | url: '/api2/extjs/access/tfa', | |
a901e4ad TL |
167 | params: { |
168 | response: res, | |
169 | }, | |
4c86db12 TL |
170 | method: 'POST', |
171 | timeout: 5000, // it'll delay both success & failure | |
172 | success: function(resp, opts) { | |
173 | view.el.unmask(); | |
174 | // Fill in what we copy over from the 1st factor: | |
175 | var data = resp.result.data; | |
176 | data.CSRFPreventionToken = Proxmox.CSRFPreventionToken; | |
177 | data.username = Proxmox.UserName; | |
178 | // Finish logging in: | |
179 | me.success(data); | |
180 | }, | |
181 | failure: function(resp, opts) { | |
182 | Proxmox.Utils.authClear(); | |
183 | me.failure(resp); | |
f6710aac | 184 | }, |
4c86db12 | 185 | }); |
da096950 DM |
186 | }, |
187 | ||
188 | control: { | |
189 | 'field[name=username]': { | |
190 | specialkey: function(f, e) { | |
191 | if (e.getKey() === e.ENTER) { | |
192 | var pf = this.lookupReference('passwordField'); | |
26031ab1 | 193 | if (!pf.getValue()) { |
da096950 DM |
194 | pf.focus(false); |
195 | } | |
196 | } | |
f6710aac | 197 | }, |
da096950 | 198 | }, |
da096950 DM |
199 | 'field[name=lang]': { |
200 | change: function(f, value) { | |
201 | var dt = Ext.Date.add(new Date(), Ext.Date.YEAR, 10); | |
202 | Ext.util.Cookies.set('PVELangCookie', value, dt); | |
203 | this.getView().mask(gettext('Please wait...'), 'x-mask-loading'); | |
204 | window.location.reload(); | |
f6710aac | 205 | }, |
da096950 | 206 | }, |
3dd99bab DM |
207 | 'field[name=realm]': { |
208 | change: function(f, value) { | |
209 | let record = f.store.getById(value); | |
210 | if (record === undefined) return; | |
211 | let data = record.data; | |
212 | this.getViewModel().set("openid", data.type === "openid"); | |
213 | }, | |
214 | }, | |
215 | 'button[reference=loginButton]': { | |
f6710aac | 216 | click: 'onLogon', |
a901e4ad | 217 | }, |
65532495 DC |
218 | '#': { |
219 | show: function() { | |
3dd99bab DM |
220 | var me = this; |
221 | ||
65532495 DC |
222 | var sp = Ext.state.Manager.getProvider(); |
223 | var checkboxField = this.lookupReference('saveunField'); | |
224 | var unField = this.lookupReference('usernameField'); | |
225 | ||
226 | var checked = sp.get(checkboxField.getStateId()); | |
227 | checkboxField.setValue(checked); | |
228 | ||
8058410f | 229 | if (checked === true) { |
65532495 | 230 | var username = sp.get(unField.getStateId()); |
65532495 DC |
231 | unField.setValue(username); |
232 | var pwField = this.lookupReference('passwordField'); | |
233 | pwField.focus(); | |
234 | } | |
3dd99bab DM |
235 | |
236 | let auth = Proxmox.Utils.getOpenIDRedirectionAuthorization(); | |
237 | if (auth !== undefined) { | |
238 | Proxmox.Utils.authClear(); | |
239 | ||
240 | let loginForm = this.lookupReference('loginForm'); | |
241 | loginForm.mask(gettext('OpenID login - please wait...'), 'x-mask-loading'); | |
242 | ||
243 | const redirectURL = location.origin; | |
244 | ||
245 | Proxmox.Utils.API2Request({ | |
246 | url: '/api2/extjs/access/openid/login', | |
247 | params: { | |
248 | state: auth.state, | |
249 | code: auth.code, | |
250 | "redirect-url": redirectURL, | |
251 | }, | |
252 | method: 'POST', | |
253 | failure: function(response) { | |
254 | loginForm.unmask(); | |
255 | let error = response.htmlStatus; | |
256 | Ext.MessageBox.alert( | |
257 | gettext('Error'), | |
258 | gettext('OpenID login failed, please try again') + `<br>${error}`, | |
259 | () => { window.location = redirectURL; }, | |
260 | ); | |
261 | }, | |
262 | success: function(response, options) { | |
263 | loginForm.unmask(); | |
264 | let data = response.result.data; | |
265 | history.replaceState(null, '', redirectURL); | |
266 | me.success(data); | |
267 | }, | |
268 | }); | |
269 | } | |
f6710aac TL |
270 | }, |
271 | }, | |
272 | }, | |
04237985 | 273 | }, |
da096950 | 274 | |
3262415b | 275 | width: 400, |
3262415b | 276 | modal: true, |
3262415b | 277 | border: false, |
3262415b | 278 | draggable: true, |
3262415b | 279 | closable: false, |
3262415b | 280 | resizable: false, |
3262415b DM |
281 | layout: 'auto', |
282 | ||
283 | title: gettext('Proxmox VE Login'), | |
284 | ||
a765aeca | 285 | defaultFocus: 'usernameField', |
551456ff DC |
286 | defaultButton: 'loginButton', |
287 | ||
da096950 DM |
288 | items: [{ |
289 | xtype: 'form', | |
290 | layout: 'form', | |
291 | url: '/api2/extjs/access/ticket', | |
292 | reference: 'loginForm', | |
293 | ||
294 | fieldDefaults: { | |
295 | labelAlign: 'right', | |
f6710aac | 296 | allowBlank: false, |
da096950 DM |
297 | }, |
298 | ||
299 | items: [ | |
300 | { | |
301 | xtype: 'textfield', | |
302 | fieldLabel: gettext('User name'), | |
303 | name: 'username', | |
a765aeca | 304 | itemId: 'usernameField', |
da096950 | 305 | reference: 'usernameField', |
f6710aac | 306 | stateId: 'login-username', |
3dd99bab DM |
307 | bind: { |
308 | visible: "{!openid}", | |
309 | disabled: "{openid}", | |
310 | }, | |
da096950 DM |
311 | }, |
312 | { | |
313 | xtype: 'textfield', | |
314 | inputType: 'password', | |
315 | fieldLabel: gettext('Password'), | |
316 | name: 'password', | |
f6710aac | 317 | reference: 'passwordField', |
3dd99bab DM |
318 | bind: { |
319 | visible: "{!openid}", | |
320 | disabled: "{openid}", | |
321 | }, | |
da096950 | 322 | }, |
da096950 | 323 | { |
2892e744 | 324 | xtype: 'pmxRealmComboBox', |
f6710aac | 325 | name: 'realm', |
da096950 DM |
326 | }, |
327 | { | |
14580653 | 328 | xtype: 'proxmoxLanguageSelector', |
da096950 | 329 | fieldLabel: gettext('Language'), |
f4aa76c5 | 330 | value: Ext.util.Cookies.get('PVELangCookie') || Proxmox.defaultLang || 'en', |
da096950 DM |
331 | name: 'lang', |
332 | reference: 'langField', | |
f6710aac TL |
333 | submitValue: false, |
334 | }, | |
da096950 DM |
335 | ], |
336 | buttons: [ | |
65532495 DC |
337 | { |
338 | xtype: 'checkbox', | |
339 | fieldLabel: gettext('Save User name'), | |
340 | name: 'saveusername', | |
341 | reference: 'saveunField', | |
342 | stateId: 'login-saveusername', | |
ecf4b557 | 343 | labelWidth: 250, |
65532495 | 344 | labelAlign: 'right', |
f6710aac | 345 | submitValue: false, |
3dd99bab DM |
346 | bind: { |
347 | visible: "{!openid}", | |
348 | }, | |
65532495 | 349 | }, |
da096950 | 350 | { |
3dd99bab DM |
351 | bind: { |
352 | text: "{button_text}", | |
353 | }, | |
f6710aac TL |
354 | reference: 'loginButton', |
355 | }, | |
356 | ], | |
357 | }], | |
da096950 | 358 | }); |
313eb589 WB |
359 | Ext.define('PVE.window.TFALoginWindow', { |
360 | extend: 'Ext.window.Window', | |
361 | ||
362 | modal: true, | |
363 | resizable: false, | |
1102bcb1 | 364 | title: 'Two-Factor Authentication', |
313eb589 WB |
365 | layout: 'form', |
366 | defaultButton: 'loginButton', | |
367 | defaultFocus: 'otpField', | |
368 | ||
369 | controller: { | |
370 | xclass: 'Ext.app.ViewController', | |
371 | login: function() { | |
372 | var me = this; | |
373 | var view = me.getView(); | |
6db00a09 | 374 | view.onLogin(me.lookup('otpField').getValue()); |
313eb589 WB |
375 | view.close(); |
376 | }, | |
377 | cancel: function() { | |
378 | var me = this; | |
379 | var view = me.getView(); | |
380 | view.onCancel(); | |
381 | view.close(); | |
f6710aac | 382 | }, |
313eb589 WB |
383 | }, |
384 | ||
385 | items: [ | |
386 | { | |
387 | xtype: 'textfield', | |
1102bcb1 | 388 | fieldLabel: gettext('Please enter your OTP verification code:'), |
313eb589 WB |
389 | name: 'otp', |
390 | itemId: 'otpField', | |
391 | reference: 'otpField', | |
f6710aac TL |
392 | allowBlank: false, |
393 | }, | |
313eb589 WB |
394 | ], |
395 | ||
396 | buttons: [ | |
397 | { | |
398 | text: gettext('Login'), | |
399 | reference: 'loginButton', | |
f6710aac | 400 | handler: 'login', |
313eb589 WB |
401 | }, |
402 | { | |
403 | text: gettext('Cancel'), | |
f6710aac TL |
404 | handler: 'cancel', |
405 | }, | |
406 | ], | |
313eb589 | 407 | }); |