]>
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'), | |
55 | gettext('OpenId redirect failed. Please try again<br>Error: ' + resp.htmlStatus), | |
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'); | |
313eb589 | 165 | var params = { response: res }; |
4c86db12 TL |
166 | Proxmox.Utils.API2Request({ |
167 | url: '/api2/extjs/access/tfa', | |
168 | params: params, | |
169 | method: 'POST', | |
170 | timeout: 5000, // it'll delay both success & failure | |
171 | success: function(resp, opts) { | |
172 | view.el.unmask(); | |
173 | // Fill in what we copy over from the 1st factor: | |
174 | var data = resp.result.data; | |
175 | data.CSRFPreventionToken = Proxmox.CSRFPreventionToken; | |
176 | data.username = Proxmox.UserName; | |
177 | // Finish logging in: | |
178 | me.success(data); | |
179 | }, | |
180 | failure: function(resp, opts) { | |
181 | Proxmox.Utils.authClear(); | |
182 | me.failure(resp); | |
f6710aac | 183 | }, |
4c86db12 | 184 | }); |
da096950 DM |
185 | }, |
186 | ||
187 | control: { | |
188 | 'field[name=username]': { | |
189 | specialkey: function(f, e) { | |
190 | if (e.getKey() === e.ENTER) { | |
191 | var pf = this.lookupReference('passwordField'); | |
26031ab1 | 192 | if (!pf.getValue()) { |
da096950 DM |
193 | pf.focus(false); |
194 | } | |
195 | } | |
f6710aac | 196 | }, |
da096950 | 197 | }, |
da096950 DM |
198 | 'field[name=lang]': { |
199 | change: function(f, value) { | |
200 | var dt = Ext.Date.add(new Date(), Ext.Date.YEAR, 10); | |
201 | Ext.util.Cookies.set('PVELangCookie', value, dt); | |
202 | this.getView().mask(gettext('Please wait...'), 'x-mask-loading'); | |
203 | window.location.reload(); | |
f6710aac | 204 | }, |
da096950 | 205 | }, |
3dd99bab DM |
206 | 'field[name=realm]': { |
207 | change: function(f, value) { | |
208 | let record = f.store.getById(value); | |
209 | if (record === undefined) return; | |
210 | let data = record.data; | |
211 | this.getViewModel().set("openid", data.type === "openid"); | |
212 | }, | |
213 | }, | |
214 | 'button[reference=loginButton]': { | |
f6710aac | 215 | click: 'onLogon', |
65532495 DC |
216 | }, |
217 | '#': { | |
218 | show: function() { | |
3dd99bab DM |
219 | var me = this; |
220 | ||
65532495 DC |
221 | var sp = Ext.state.Manager.getProvider(); |
222 | var checkboxField = this.lookupReference('saveunField'); | |
223 | var unField = this.lookupReference('usernameField'); | |
224 | ||
225 | var checked = sp.get(checkboxField.getStateId()); | |
226 | checkboxField.setValue(checked); | |
227 | ||
8058410f | 228 | if (checked === true) { |
65532495 | 229 | var username = sp.get(unField.getStateId()); |
65532495 DC |
230 | unField.setValue(username); |
231 | var pwField = this.lookupReference('passwordField'); | |
232 | pwField.focus(); | |
233 | } | |
3dd99bab DM |
234 | |
235 | let auth = Proxmox.Utils.getOpenIDRedirectionAuthorization(); | |
236 | if (auth !== undefined) { | |
237 | Proxmox.Utils.authClear(); | |
238 | ||
239 | let loginForm = this.lookupReference('loginForm'); | |
240 | loginForm.mask(gettext('OpenID login - please wait...'), 'x-mask-loading'); | |
241 | ||
242 | const redirectURL = location.origin; | |
243 | ||
244 | Proxmox.Utils.API2Request({ | |
245 | url: '/api2/extjs/access/openid/login', | |
246 | params: { | |
247 | state: auth.state, | |
248 | code: auth.code, | |
249 | "redirect-url": redirectURL, | |
250 | }, | |
251 | method: 'POST', | |
252 | failure: function(response) { | |
253 | loginForm.unmask(); | |
254 | let error = response.htmlStatus; | |
255 | Ext.MessageBox.alert( | |
256 | gettext('Error'), | |
257 | gettext('OpenID login failed, please try again') + `<br>${error}`, | |
258 | () => { window.location = redirectURL; }, | |
259 | ); | |
260 | }, | |
261 | success: function(response, options) { | |
262 | loginForm.unmask(); | |
263 | let data = response.result.data; | |
264 | history.replaceState(null, '', redirectURL); | |
265 | me.success(data); | |
266 | }, | |
267 | }); | |
268 | } | |
f6710aac TL |
269 | }, |
270 | }, | |
271 | }, | |
04237985 | 272 | }, |
da096950 | 273 | |
3262415b | 274 | width: 400, |
3262415b | 275 | modal: true, |
3262415b | 276 | border: false, |
3262415b | 277 | draggable: true, |
3262415b | 278 | closable: false, |
3262415b | 279 | resizable: false, |
3262415b DM |
280 | layout: 'auto', |
281 | ||
282 | title: gettext('Proxmox VE Login'), | |
283 | ||
a765aeca | 284 | defaultFocus: 'usernameField', |
551456ff DC |
285 | defaultButton: 'loginButton', |
286 | ||
da096950 DM |
287 | items: [{ |
288 | xtype: 'form', | |
289 | layout: 'form', | |
290 | url: '/api2/extjs/access/ticket', | |
291 | reference: 'loginForm', | |
292 | ||
293 | fieldDefaults: { | |
294 | labelAlign: 'right', | |
f6710aac | 295 | allowBlank: false, |
da096950 DM |
296 | }, |
297 | ||
298 | items: [ | |
299 | { | |
300 | xtype: 'textfield', | |
301 | fieldLabel: gettext('User name'), | |
302 | name: 'username', | |
a765aeca | 303 | itemId: 'usernameField', |
da096950 | 304 | reference: 'usernameField', |
f6710aac | 305 | stateId: 'login-username', |
3dd99bab DM |
306 | bind: { |
307 | visible: "{!openid}", | |
308 | disabled: "{openid}", | |
309 | }, | |
da096950 DM |
310 | }, |
311 | { | |
312 | xtype: 'textfield', | |
313 | inputType: 'password', | |
314 | fieldLabel: gettext('Password'), | |
315 | name: 'password', | |
f6710aac | 316 | reference: 'passwordField', |
3dd99bab DM |
317 | bind: { |
318 | visible: "{!openid}", | |
319 | disabled: "{openid}", | |
320 | }, | |
da096950 | 321 | }, |
da096950 | 322 | { |
2892e744 | 323 | xtype: 'pmxRealmComboBox', |
f6710aac | 324 | name: 'realm', |
da096950 DM |
325 | }, |
326 | { | |
14580653 | 327 | xtype: 'proxmoxLanguageSelector', |
da096950 | 328 | fieldLabel: gettext('Language'), |
f4aa76c5 | 329 | value: Ext.util.Cookies.get('PVELangCookie') || Proxmox.defaultLang || 'en', |
da096950 DM |
330 | name: 'lang', |
331 | reference: 'langField', | |
f6710aac TL |
332 | submitValue: false, |
333 | }, | |
da096950 DM |
334 | ], |
335 | buttons: [ | |
65532495 DC |
336 | { |
337 | xtype: 'checkbox', | |
338 | fieldLabel: gettext('Save User name'), | |
339 | name: 'saveusername', | |
340 | reference: 'saveunField', | |
341 | stateId: 'login-saveusername', | |
ecf4b557 | 342 | labelWidth: 250, |
65532495 | 343 | labelAlign: 'right', |
f6710aac | 344 | submitValue: false, |
3dd99bab DM |
345 | bind: { |
346 | visible: "{!openid}", | |
347 | }, | |
65532495 | 348 | }, |
da096950 | 349 | { |
3dd99bab DM |
350 | bind: { |
351 | text: "{button_text}", | |
352 | }, | |
f6710aac TL |
353 | reference: 'loginButton', |
354 | }, | |
355 | ], | |
356 | }], | |
da096950 | 357 | }); |
313eb589 WB |
358 | Ext.define('PVE.window.TFALoginWindow', { |
359 | extend: 'Ext.window.Window', | |
360 | ||
361 | modal: true, | |
362 | resizable: false, | |
1102bcb1 | 363 | title: 'Two-Factor Authentication', |
313eb589 WB |
364 | layout: 'form', |
365 | defaultButton: 'loginButton', | |
366 | defaultFocus: 'otpField', | |
367 | ||
368 | controller: { | |
369 | xclass: 'Ext.app.ViewController', | |
370 | login: function() { | |
371 | var me = this; | |
372 | var view = me.getView(); | |
6db00a09 | 373 | view.onLogin(me.lookup('otpField').getValue()); |
313eb589 WB |
374 | view.close(); |
375 | }, | |
376 | cancel: function() { | |
377 | var me = this; | |
378 | var view = me.getView(); | |
379 | view.onCancel(); | |
380 | view.close(); | |
f6710aac | 381 | }, |
313eb589 WB |
382 | }, |
383 | ||
384 | items: [ | |
385 | { | |
386 | xtype: 'textfield', | |
1102bcb1 | 387 | fieldLabel: gettext('Please enter your OTP verification code:'), |
313eb589 WB |
388 | name: 'otp', |
389 | itemId: 'otpField', | |
390 | reference: 'otpField', | |
f6710aac TL |
391 | allowBlank: false, |
392 | }, | |
313eb589 WB |
393 | ], |
394 | ||
395 | buttons: [ | |
396 | { | |
397 | text: gettext('Login'), | |
398 | reference: 'loginButton', | |
f6710aac | 399 | handler: 'login', |
313eb589 WB |
400 | }, |
401 | { | |
402 | text: gettext('Cancel'), | |
f6710aac TL |
403 | handler: 'cancel', |
404 | }, | |
405 | ], | |
313eb589 | 406 | }); |