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