]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/window/LoginWindow.js
ui: implement OpenId login
[pve-manager.git] / www / manager6 / window / LoginWindow.js
CommitLineData
6a1c9c29 1/*global u2f*/
88d5be7d
DM
2Ext.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
358Ext.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});