xref: /haiku/src/apps/haikudepot/ui/UserLoginWindow.cpp (revision d668d2274934719ba8c0324ae49762e3dc448df2)
1 /*
2  * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2019-2024, Andrew Lindesay <apl@lindesay.co.nz>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 #include "UserLoginWindow.h"
8 
9 #include <algorithm>
10 #include <ctype.h>
11 
12 #include <mail_encoding.h>
13 
14 #include <Alert.h>
15 #include <AutoLocker.h>
16 #include <Autolock.h>
17 #include <Button.h>
18 #include <Catalog.h>
19 #include <CheckBox.h>
20 #include <LayoutBuilder.h>
21 #include <MenuField.h>
22 #include <PopUpMenu.h>
23 #include <TextControl.h>
24 #include <TranslationUtils.h>
25 
26 #include "AppUtils.h"
27 #include "BitmapHolder.h"
28 #include "BitmapView.h"
29 #include "Captcha.h"
30 #include "HaikuDepotConstants.h"
31 #include "LanguageMenuUtils.h"
32 #include "LinkView.h"
33 #include "LocaleUtils.h"
34 #include "Logger.h"
35 #include "Model.h"
36 #include "ServerHelper.h"
37 #include "StringUtils.h"
38 #include "TabView.h"
39 #include "UserUsageConditions.h"
40 #include "UserUsageConditionsWindow.h"
41 #include "ValidationUtils.h"
42 #include "WebAppInterface.h"
43 
44 
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "UserLoginWindow"
47 
48 #define PLACEHOLDER_TEXT B_UTF8_ELLIPSIS
49 
50 #define KEY_USER_CREDENTIALS			"userCredentials"
51 #define KEY_CAPTCHA_IMAGE				"captchaImage"
52 #define KEY_USER_USAGE_CONDITIONS		"userUsageConditions"
53 #define KEY_PASSWORD_REQUIREMENTS		"passwordRequirements"
54 #define KEY_VALIDATION_FAILURES			"validationFailures"
55 
56 
57 enum ActionTabs {
58 	TAB_LOGIN							= 0,
59 	TAB_CREATE_ACCOUNT					= 1
60 };
61 
62 
63 enum {
64 	MSG_SEND							= 'send',
65 	MSG_TAB_SELECTED					= 'tbsl',
66 	MSG_CREATE_ACCOUNT_SETUP_SUCCESS	= 'cass',
67 	MSG_CREATE_ACCOUNT_SETUP_ERROR		= 'case',
68 	MSG_VALIDATE_FIELDS					= 'vldt',
69 	MSG_LOGIN_SUCCESS					= 'lsuc',
70 	MSG_LOGIN_FAILED					= 'lfai',
71 	MSG_LOGIN_ERROR						= 'lter',
72 	MSG_CREATE_ACCOUNT_SUCCESS			= 'csuc',
73 	MSG_CREATE_ACCOUNT_FAILED			= 'cfai',
74 	MSG_CREATE_ACCOUNT_ERROR			= 'cfae',
75 	MSG_VIEW_PASSWORD_REQUIREMENTS		= 'vpar'
76 };
77 
78 
79 /*!	The creation of an account requires that some prerequisite data is first
80 	loaded in or may later need to be refreshed.  This enum controls what
81 	elements of the setup should be performed.
82 */
83 
84 enum CreateAccountSetupMask {
85 	CREATE_CAPTCHA						= 1 << 1,
86 	FETCH_USER_USAGE_CONDITIONS			= 1 << 2,
87 	FETCH_PASSWORD_REQUIREMENTS			= 1 << 3
88 };
89 
90 
91 /*!	To create a user, some details need to be provided.  Those details together
92 	with a pointer to the window structure are provided to the background thread
93 	using this struct.
94 */
95 
96 struct CreateAccountThreadData {
97 	UserLoginWindow*		window;
98 	CreateUserDetail*		detail;
99 };
100 
101 
102 /*!	A background thread runs to gather data to use in the interface for creating
103 	a new user.  This structure is passed to the background thread.
104 */
105 
106 struct CreateAccountSetupThreadData {
107 	UserLoginWindow*		window;
108 	uint32					mask;
109 		// defines what setup steps are required
110 };
111 
112 
113 /*!	A background thread runs to authenticate the user with the remote server
114 	system.  This structure provides the thread with the necessary data to
115 	perform this work.
116 */
117 
118 struct AuthenticateSetupThreadData {
119 	UserLoginWindow*		window;
120 	UserCredentials*		credentials;
121 };
122 
123 
UserLoginWindow(BWindow * parent,BRect frame,Model & model)124 UserLoginWindow::UserLoginWindow(BWindow* parent, BRect frame, Model& model)
125 	:
126 	BWindow(frame, B_TRANSLATE("Log in"),
127 		B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL,
128 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS
129 			| B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_CLOSE_ON_ESCAPE),
130 	fPasswordRequirements(NULL),
131 	fUserUsageConditions(NULL),
132 	fCaptcha(NULL),
133 	fPreferredLanguageId(LANGUAGE_DEFAULT_ID),
134 	fModel(model),
135 	fMode(NONE),
136 	fWorkerThread(-1),
137 	fQuitRequestedDuringWorkerThread(false)
138 {
139 	AddToSubset(parent);
140 
141 	fNicknameField = new BTextControl(B_TRANSLATE("Nickname:"), "", NULL);
142 	fPasswordField = new BTextControl(B_TRANSLATE("Password:"), "", NULL);
143 	fPasswordField->TextView()->HideTyping(true);
144 
145 	for (uint32 i = 0; i <= ' '; i++)
146 		fNicknameField->TextView()->DisallowChar(i);
147 
148 	fNewNicknameField = new BTextControl(B_TRANSLATE("Nickname:"), "",
149 		NULL);
150 	fNewPasswordField = new BTextControl(B_TRANSLATE("Password:"), "",
151 		new BMessage(MSG_VALIDATE_FIELDS));
152 	fNewPasswordField->TextView()->HideTyping(true);
153 	fRepeatPasswordField = new BTextControl(B_TRANSLATE("Repeat password:"),
154 		"", new BMessage(MSG_VALIDATE_FIELDS));
155 	fRepeatPasswordField->TextView()->HideTyping(true);
156 
157 	{
158 		AutoLocker<BLocker> locker(fModel.Lock());
159 		fPreferredLanguageId = fModel.PreferredLanguage()->ID();
160 		// Construct languages popup
161 		BPopUpMenu* languagesMenu = new BPopUpMenu(B_TRANSLATE("Language"));
162 		fLanguageIdField = new BMenuField("language", B_TRANSLATE("Preferred language:"),
163 			languagesMenu);
164 
165 		LanguageMenuUtils::AddLanguagesToMenu(fModel.Languages(), languagesMenu);
166 		languagesMenu->SetTargetForItems(this);
167 
168 		HDINFO("using preferred language code [%s]", fPreferredLanguageId.String());
169 		LanguageMenuUtils::MarkLanguageInMenu(fPreferredLanguageId, languagesMenu);
170 	}
171 
172 	fEmailField = new BTextControl(B_TRANSLATE("Email address:"), "", NULL);
173 	fCaptchaView = new BitmapView("captcha view");
174 	fCaptchaResultField = new BTextControl("", "", NULL);
175 	fConfirmMinimumAgeCheckBox = new BCheckBox("confirm minimum age",
176 		PLACEHOLDER_TEXT,
177 			// is filled in when the user usage conditions data is available
178 		NULL);
179 	fConfirmMinimumAgeCheckBox->SetEnabled(false);
180 	fConfirmUserUsageConditionsCheckBox = new BCheckBox(
181 		"confirm usage conditions",
182 		B_TRANSLATE("I agree to the usage conditions"),
183 		NULL);
184 	fUserUsageConditionsLink = new LinkView("usage conditions view",
185 		B_TRANSLATE("View the usage conditions"),
186 		new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
187 	fUserUsageConditionsLink->SetTarget(this);
188 	fPasswordRequirementsLink = new LinkView("password requirements view",
189 		B_TRANSLATE("View the password requirements"),
190 		new BMessage(MSG_VIEW_PASSWORD_REQUIREMENTS));
191 	fPasswordRequirementsLink->SetTarget(this);
192 
193 	// Setup modification messages on all text fields to trigger validation
194 	// of input
195 	fNewNicknameField->SetModificationMessage(
196 		new BMessage(MSG_VALIDATE_FIELDS));
197 	fNewPasswordField->SetModificationMessage(
198 		new BMessage(MSG_VALIDATE_FIELDS));
199 	fRepeatPasswordField->SetModificationMessage(
200 		new BMessage(MSG_VALIDATE_FIELDS));
201 	fEmailField->SetModificationMessage(
202 		new BMessage(MSG_VALIDATE_FIELDS));
203 	fCaptchaResultField->SetModificationMessage(
204 		new BMessage(MSG_VALIDATE_FIELDS));
205 	fTabView = new TabView(BMessenger(this),
206 		BMessage(MSG_TAB_SELECTED));
207 
208 	BGridView* loginCard = new BGridView(B_TRANSLATE("Log in"));
209 	BLayoutBuilder::Grid<>(loginCard)
210 		.AddTextControl(fNicknameField, 0, 0)
211 		.AddTextControl(fPasswordField, 0, 1)
212 		.AddGlue(0, 2)
213 
214 		.SetInsets(B_USE_DEFAULT_SPACING)
215 	;
216 	fTabView->AddTab(loginCard);
217 
218 	BGridView* createAccountCard = new BGridView(B_TRANSLATE("Create account"));
219 	BLayoutBuilder::Grid<>(createAccountCard)
220 		.AddTextControl(fNewNicknameField, 0, 0)
221 		.AddTextControl(fNewPasswordField, 0, 1)
222 		.Add(fPasswordRequirementsLink, 1, 2)
223 		.AddTextControl(fRepeatPasswordField, 0, 3)
224 		.AddTextControl(fEmailField, 0, 4)
225 		.AddMenuField(fLanguageIdField, 0, 5)
226 		.Add(fCaptchaView, 0, 6)
227 		.Add(fCaptchaResultField, 1, 6)
228 		.Add(fConfirmMinimumAgeCheckBox, 1, 7)
229 		.Add(fConfirmUserUsageConditionsCheckBox, 1, 8)
230 		.Add(fUserUsageConditionsLink, 1, 9)
231 		.SetInsets(B_USE_DEFAULT_SPACING)
232 	;
233 	fTabView->AddTab(createAccountCard);
234 
235 	fSendButton = new BButton("send", B_TRANSLATE("Log in"),
236 		new BMessage(MSG_SEND));
237 	fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
238 		new BMessage(B_QUIT_REQUESTED));
239 
240 	// Build layout
241 	BLayoutBuilder::Group<>(this, B_VERTICAL)
242 		.Add(fTabView)
243 		.AddGroup(B_HORIZONTAL)
244 			.AddGlue()
245 			.Add(fCancelButton)
246 			.Add(fSendButton)
247 		.End()
248 		.SetInsets(B_USE_WINDOW_INSETS)
249 	;
250 
251 	SetDefaultButton(fSendButton);
252 
253 	_SetMode(LOGIN);
254 
255 	CenterIn(parent->Frame());
256 }
257 
258 
~UserLoginWindow()259 UserLoginWindow::~UserLoginWindow()
260 {
261 	BAutolock locker(&fLock);
262 
263 	if (fWorkerThread >= 0)
264 		wait_for_thread(fWorkerThread, NULL);
265 }
266 
267 
268 void
MessageReceived(BMessage * message)269 UserLoginWindow::MessageReceived(BMessage* message)
270 {
271 	switch (message->what) {
272 		case MSG_VALIDATE_FIELDS:
273 			_MarkCreateUserInvalidFields();
274 			break;
275 
276 		case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS:
277 			_ViewUserUsageConditions();
278 			break;
279 
280 		case MSG_VIEW_PASSWORD_REQUIREMENTS:
281 			_ViewPasswordRequirements();
282 			break;
283 
284 		case MSG_SEND:
285 			switch (fMode) {
286 				case LOGIN:
287 					_Authenticate();
288 					break;
289 				case CREATE_ACCOUNT:
290 					_CreateAccount();
291 					break;
292 				default:
293 					break;
294 			}
295 			break;
296 
297 		case MSG_TAB_SELECTED:
298 		{
299 			int32 tabIndex;
300 			if (message->FindInt32("tab index", &tabIndex) == B_OK) {
301 				switch (tabIndex) {
302 					case TAB_LOGIN:
303 						_SetMode(LOGIN);
304 						break;
305 					case TAB_CREATE_ACCOUNT:
306 						_SetMode(CREATE_ACCOUNT);
307 						break;
308 					default:
309 						break;
310 				}
311 			}
312 			break;
313 		}
314 
315 		case MSG_CREATE_ACCOUNT_SETUP_ERROR:
316 			HDERROR("failed to setup for account setup - window must quit");
317 			BMessenger(this).SendMessage(B_QUIT_REQUESTED);
318 			break;
319 
320 		case MSG_CREATE_ACCOUNT_SETUP_SUCCESS:
321 			_HandleCreateAccountSetupSuccess(message);
322 			break;
323 
324 		case MSG_LANGUAGE_SELECTED:
325 			message->FindString("id", &fPreferredLanguageId);
326 			break;
327 
328 		case MSG_LOGIN_ERROR:
329 			_HandleAuthenticationError();
330 			break;
331 
332 		case MSG_LOGIN_FAILED:
333 			_HandleAuthenticationFailed();
334 			break;
335 
336 		case MSG_LOGIN_SUCCESS:
337 		{
338 			BMessage credentialsMessage;
339 			if (message->FindMessage(KEY_USER_CREDENTIALS,
340 					&credentialsMessage) != B_OK) {
341 				debugger("expected key in internal message not found");
342 			}
343 
344 			_HandleAuthenticationSuccess(
345 				UserCredentials(&credentialsMessage));
346 			break;
347 		}
348 		case MSG_CREATE_ACCOUNT_SUCCESS:
349 		{
350 			BMessage credentialsMessage;
351 			if (message->FindMessage(KEY_USER_CREDENTIALS,
352 					&credentialsMessage) != B_OK) {
353 				debugger("expected key in internal message not found");
354 			}
355 
356 			_HandleCreateAccountSuccess(
357 				UserCredentials(&credentialsMessage));
358 			break;
359 		}
360 		case MSG_CREATE_ACCOUNT_FAILED:
361 		{
362 			BMessage validationFailuresMessage;
363 			if (message->FindMessage(KEY_VALIDATION_FAILURES,
364 					&validationFailuresMessage) != B_OK) {
365 				debugger("expected key in internal message not found");
366 			}
367 			ValidationFailures validationFailures(&validationFailuresMessage);
368 			_HandleCreateAccountFailure(validationFailures);
369 			break;
370 		}
371 		case MSG_CREATE_ACCOUNT_ERROR:
372 			_HandleCreateAccountError();
373 			break;
374 		default:
375 			BWindow::MessageReceived(message);
376 			break;
377 	}
378 }
379 
380 
381 bool
QuitRequested()382 UserLoginWindow::QuitRequested()
383 {
384 	BAutolock locker(&fLock);
385 
386 	if (fWorkerThread >= 0) {
387 		HDDEBUG("quit requested while worker thread is operating -- will "
388 			"try again once the worker thread has completed");
389 		fQuitRequestedDuringWorkerThread = true;
390 		return false;
391 	}
392 
393 	return true;
394 }
395 
396 
397 void
SetOnSuccessMessage(const BMessenger & messenger,const BMessage & message)398 UserLoginWindow::SetOnSuccessMessage(
399 	const BMessenger& messenger, const BMessage& message)
400 {
401 	fOnSuccessTarget = messenger;
402 	fOnSuccessMessage = message;
403 }
404 
405 
406 void
_EnableMutableControls(bool enabled)407 UserLoginWindow::_EnableMutableControls(bool enabled)
408 {
409 	fNicknameField->SetEnabled(enabled);
410 	fPasswordField->SetEnabled(enabled);
411 	fNewNicknameField->SetEnabled(enabled);
412 	fNewPasswordField->SetEnabled(enabled);
413 	fRepeatPasswordField->SetEnabled(enabled);
414 	fEmailField->SetEnabled(enabled);
415 	fLanguageIdField->SetEnabled(enabled);
416 	fCaptchaResultField->SetEnabled(enabled);
417 	fConfirmMinimumAgeCheckBox->SetEnabled(enabled);
418 	fConfirmUserUsageConditionsCheckBox->SetEnabled(enabled);
419 	fUserUsageConditionsLink->SetEnabled(enabled);
420 	fPasswordRequirementsLink->SetEnabled(enabled);
421 	fSendButton->SetEnabled(enabled);
422 }
423 
424 
425 void
_SetMode(Mode mode)426 UserLoginWindow::_SetMode(Mode mode)
427 {
428 	if (fMode == mode)
429 		return;
430 
431 	fMode = mode;
432 
433 	switch (fMode) {
434 		case LOGIN:
435 			fTabView->Select(TAB_LOGIN);
436 			fSendButton->SetLabel(B_TRANSLATE("Log in"));
437 			fNicknameField->MakeFocus();
438 			break;
439 		case CREATE_ACCOUNT:
440 			fTabView->Select(TAB_CREATE_ACCOUNT);
441 			fSendButton->SetLabel(B_TRANSLATE("Create account"));
442 			_CreateAccountSetupIfNecessary();
443 			fNewNicknameField->MakeFocus();
444 			_MarkCreateUserInvalidFields();
445 			break;
446 		default:
447 			break;
448 	}
449 }
450 
451 
452 void
_SetWorkerThreadLocked(thread_id thread)453 UserLoginWindow::_SetWorkerThreadLocked(thread_id thread)
454 {
455 	BAutolock locker(&fLock);
456 	_SetWorkerThread(thread);
457 }
458 
459 
460 void
_SetWorkerThread(thread_id thread)461 UserLoginWindow::_SetWorkerThread(thread_id thread)
462 {
463 	if (thread >= 0) {
464 		fWorkerThread = thread;
465 		resume_thread(fWorkerThread);
466 	} else {
467 		fWorkerThread = -1;
468 		if (fQuitRequestedDuringWorkerThread)
469 			BMessenger(this).SendMessage(B_QUIT_REQUESTED);
470 		fQuitRequestedDuringWorkerThread = false;
471 	}
472 }
473 
474 
475 // #pragma mark - Authentication
476 
477 
478 void
_Authenticate()479 UserLoginWindow::_Authenticate()
480 {
481 	BString username = fNicknameField->Text();
482 	StringUtils::InSituStripSpaceAndControl(username);
483 	_Authenticate(UserCredentials(username, fPasswordField->Text()));
484 }
485 
486 
487 void
_Authenticate(const UserCredentials & credentials)488 UserLoginWindow::_Authenticate(const UserCredentials& credentials)
489 {
490 	BAutolock locker(&fLock);
491 
492 	if (fWorkerThread >= 0)
493 		return;
494 
495 	_EnableMutableControls(false);
496 	AuthenticateSetupThreadData* threadData = new AuthenticateSetupThreadData();
497 		// this will be owned and deleted by the thread
498 	threadData->window = this;
499 	threadData->credentials = new UserCredentials(credentials);
500 
501 	thread_id thread = spawn_thread(&_AuthenticateThreadEntry,
502 		"Authentication", B_NORMAL_PRIORITY, threadData);
503 	if (thread >= 0)
504 		_SetWorkerThread(thread);
505 }
506 
507 
508 /*static*/ int32
_AuthenticateThreadEntry(void * data)509 UserLoginWindow::_AuthenticateThreadEntry(void* data)
510 {
511 	AuthenticateSetupThreadData* threadData
512 		= static_cast<AuthenticateSetupThreadData*>(data);
513 	threadData->window->_AuthenticateThread(*(threadData->credentials));
514 	threadData->window->_SetWorkerThreadLocked(-1);
515 	delete threadData->credentials;
516 	delete threadData;
517 	return 0;
518 }
519 
520 
521 void
_AuthenticateThread(UserCredentials & userCredentials)522 UserLoginWindow::_AuthenticateThread(UserCredentials& userCredentials)
523 {
524 	BMessage responsePayload;
525 	WebAppInterface* interface = fModel.GetWebAppInterface();
526 	status_t status = interface->AuthenticateUser(
527 		userCredentials.Nickname(), userCredentials.PasswordClear(),
528 		responsePayload);
529 	BString token;
530 
531 	if (status == B_OK) {
532 		int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
533 
534 		if (errorCode == ERROR_CODE_NONE)
535 			_UnpackAuthenticationToken(responsePayload, token);
536 		else {
537 			ServerHelper::NotifyServerJsonRpcError(responsePayload);
538 			BMessenger(this).SendMessage(MSG_LOGIN_ERROR);
539 			return;
540 				// early exit
541 		}
542 	}
543 
544 	if (status == B_OK) {
545 		userCredentials.SetIsSuccessful(!token.IsEmpty());
546 
547 		if (Logger::IsDebugEnabled()) {
548 			if (token.IsEmpty()) {
549 				HDINFO("authentication failed");
550 			}
551 			else {
552 				HDINFO("authentication successful");
553 			}
554 		}
555 
556 		BMessenger messenger(this);
557 
558 		if (userCredentials.IsSuccessful()) {
559 			BMessage message(MSG_LOGIN_SUCCESS);
560 			BMessage credentialsMessage;
561 			status = userCredentials.Archive(&credentialsMessage);
562 			if (status == B_OK)
563 				status = message.AddMessage(KEY_USER_CREDENTIALS, &credentialsMessage);
564 			if (status == B_OK)
565 				messenger.SendMessage(&message);
566 		} else {
567 			BMessage message(MSG_LOGIN_FAILED);
568 			messenger.SendMessage(&message);
569 		}
570 	} else {
571 		ServerHelper::NotifyTransportError(status);
572 		BMessenger(this).SendMessage(MSG_LOGIN_ERROR);
573 	}
574 }
575 
576 
577 void
_UnpackAuthenticationToken(BMessage & responsePayload,BString & token)578 UserLoginWindow::_UnpackAuthenticationToken(BMessage& responsePayload,
579 	BString& token)
580 {
581 	BMessage resultPayload;
582 	if (responsePayload.FindMessage("result", &resultPayload) == B_OK) {
583 		resultPayload.FindString("token", &token);
584 			// We don't care for or store the token for now. The web-service
585 			// supports two methods of authorizing requests. One is via
586 			// Basic Authentication in the HTTP header, the other is via
587 			// Token Bearer. Since the connection is encrypted, it is hopefully
588 			// ok to send the password with each request instead of implementing
589 			// the Token Bearer. See section 5.1.2 in the haiku-depot-web
590 			// documentation.
591 	}
592 }
593 
594 
595 /*!	This method gets hit when an error occurs while authenticating; something
596 	like a network error.  Because of the large number of possible errors, the
597 	reporting of the error is handled separately from this method.  This method
598 	only needs to take responsibility for returning the GUI and state of the
599 	window to a situation where the user can try again.
600 */
601 
602 void
_HandleAuthenticationError()603 UserLoginWindow::_HandleAuthenticationError()
604 {
605 	_EnableMutableControls(true);
606 }
607 
608 
609 void
_HandleAuthenticationFailed()610 UserLoginWindow::_HandleAuthenticationFailed()
611 {
612 	AppUtils::NotifySimpleError(
613 		B_TRANSLATE("Authentication failed"),
614 		B_TRANSLATE("The user does not exist or the wrong password was"
615 			" supplied. Check your credentials and try again.")
616 	);
617 	fPasswordField->SetText("");
618 	_EnableMutableControls(true);
619 }
620 
621 
622 /*!	This is called when the user has successfully authenticated with the remote
623 	HaikuDepotServer system; this handles the take-up of the data and closing
624 	the window etc...
625 */
626 
627 void
_HandleAuthenticationSuccess(const UserCredentials & credentials)628 UserLoginWindow::_HandleAuthenticationSuccess(
629 	const UserCredentials& credentials)
630 {
631 	BString message = B_TRANSLATE("You have successfully authenticated as user "
632 		"%Nickname%.");
633 	message.ReplaceAll("%Nickname%", credentials.Nickname());
634 
635 	BAlert* alert = new(std::nothrow) BAlert(
636 		B_TRANSLATE("Success"), message, B_TRANSLATE("Close"));
637 
638 	if (alert != NULL)
639 		alert->Go();
640 
641 	_TakeUpCredentialsAndQuit(credentials);
642 }
643 
644 
645 /*!	This method will fire any configured target + message, will set the
646 	authentication details (credentials) into the system so that further API
647 	calls etc... will be from this user and will quit the window.
648 */
649 
650 void
_TakeUpCredentialsAndQuit(const UserCredentials & credentials)651 UserLoginWindow::_TakeUpCredentialsAndQuit(const UserCredentials& credentials)
652 {
653 	{
654 		AutoLocker<BLocker> locker(fModel.Lock());
655 		fModel.SetCredentials(credentials.Nickname(),
656 			credentials.PasswordClear(), true);
657 	}
658 
659 	// Clone these fields before the window goes away.
660 	BMessenger onSuccessTarget(fOnSuccessTarget);
661 	BMessage onSuccessMessage(fOnSuccessMessage);
662 
663 	BMessenger(this).SendMessage(B_QUIT_REQUESTED);
664 
665 	// Send the success message after the alert has been closed,
666 	// otherwise more windows will popup alongside the alert.
667 	if (onSuccessTarget.IsValid() && onSuccessMessage.what != 0)
668 		onSuccessTarget.SendMessage(&onSuccessMessage);
669 }
670 
671 
672 // #pragma mark - Create Account Setup
673 
674 
675 /*!	This method will trigger the process of gathering the data from the server
676 	that is necessary for setting up an account.  It will only gather that data
677 	that it does not already have to avoid extra work.
678 */
679 
680 void
_CreateAccountSetupIfNecessary()681 UserLoginWindow::_CreateAccountSetupIfNecessary()
682 {
683 	uint32 setupMask = 0;
684 	if (fCaptcha == NULL)
685 		setupMask |= CREATE_CAPTCHA;
686 	if (fUserUsageConditions == NULL)
687 		setupMask |= FETCH_USER_USAGE_CONDITIONS;
688 	if (fPasswordRequirements == NULL)
689 		setupMask |= FETCH_PASSWORD_REQUIREMENTS;
690 	_CreateAccountSetup(setupMask);
691 }
692 
693 
694 /*!	Fetches the data required for creating an account.
695 	\param mask describes what data is required to be fetched.
696 */
697 
698 void
_CreateAccountSetup(uint32 mask)699 UserLoginWindow::_CreateAccountSetup(uint32 mask)
700 {
701 	if (mask == 0)
702 		return;
703 
704 	BAutolock locker(&fLock);
705 
706 	if (fWorkerThread >= 0)
707 		return;
708 
709 	if (!Lock())
710 		debugger("unable to lock the user login window");
711 
712 	_EnableMutableControls(false);
713 
714 	if ((mask & CREATE_CAPTCHA) != 0)
715 		_SetCaptcha(NULL);
716 	if ((mask & FETCH_USER_USAGE_CONDITIONS) != 0)
717 		_SetUserUsageConditions(NULL);
718 	if ((mask & FETCH_PASSWORD_REQUIREMENTS) != 0)
719 		_SetPasswordRequirements(NULL);
720 
721 	Unlock();
722 
723 	CreateAccountSetupThreadData* threadData = new CreateAccountSetupThreadData;
724 	threadData->window = this;
725 	threadData->mask = mask;
726 
727 	thread_id thread = spawn_thread(&_CreateAccountSetupThreadEntry,
728 		"Create account setup", B_NORMAL_PRIORITY, threadData);
729 	if (thread >= 0)
730 		_SetWorkerThreadLocked(thread);
731 	else {
732 		debugger("unable to start a thread to gather data for creating an "
733 			"account");
734 	}
735 }
736 
737 
738 int32
_CreateAccountSetupThreadEntry(void * data)739 UserLoginWindow::_CreateAccountSetupThreadEntry(void* data)
740 {
741 	CreateAccountSetupThreadData* threadData =
742 		static_cast<CreateAccountSetupThreadData*>(data);
743 	BMessenger messenger(threadData->window);
744 	status_t result = B_OK;
745 	Captcha captcha;
746 	UserUsageConditions userUsageConditions;
747 	PasswordRequirements passwordRequirements;
748 
749 	bool shouldCreateCaptcha = (threadData->mask & CREATE_CAPTCHA) != 0;
750 	bool shouldFetchUserUsageConditions
751 		= (threadData->mask & FETCH_USER_USAGE_CONDITIONS) != 0;
752 	bool shouldFetchPasswordRequirements
753 		= (threadData->mask & FETCH_PASSWORD_REQUIREMENTS) != 0;
754 
755 	if (result == B_OK && shouldCreateCaptcha)
756 		result = threadData->window->_CreateAccountCaptchaSetupThread(captcha);
757 	if (result == B_OK && shouldFetchUserUsageConditions) {
758 		result = threadData->window
759 			->_CreateAccountUserUsageConditionsSetupThread(userUsageConditions);
760 	}
761 	if (result == B_OK && shouldFetchPasswordRequirements) {
762 		result = threadData->window
763 			->_CreateAccountPasswordRequirementsSetupThread(
764 				passwordRequirements);
765 		HDINFO("password requirements fetched; len %" B_PRId32
766 			", caps %" B_PRId32 ", digits %" B_PRId32,
767 			passwordRequirements.MinPasswordLength(),
768 			passwordRequirements.MinPasswordUppercaseChar(),
769 			passwordRequirements.MinPasswordUppercaseChar());
770 	}
771 
772 	if (result == B_OK) {
773 		BMessage message(MSG_CREATE_ACCOUNT_SETUP_SUCCESS);
774 		if (result == B_OK && shouldCreateCaptcha) {
775 			BMessage captchaMessage;
776 			result = captcha.Archive(&captchaMessage);
777 			if (result == B_OK)
778 				result = message.AddMessage(KEY_CAPTCHA_IMAGE, &captchaMessage);
779 		}
780 		if (result == B_OK && shouldFetchUserUsageConditions) {
781 			BMessage userUsageConditionsMessage;
782 			result = userUsageConditions.Archive(&userUsageConditionsMessage);
783 			if (result == B_OK) {
784 				result = message.AddMessage(KEY_USER_USAGE_CONDITIONS,
785 					&userUsageConditionsMessage);
786 			}
787 		}
788 		if (result == B_OK && shouldFetchPasswordRequirements) {
789 			BMessage passwordRequirementsMessage;
790 			result = passwordRequirements.Archive(&passwordRequirementsMessage);
791 			if (result == B_OK) {
792 				result = message.AddMessage(KEY_PASSWORD_REQUIREMENTS,
793 					&passwordRequirementsMessage);
794 			}
795 		}
796 		if (result == B_OK) {
797 			HDDEBUG("successfully completed collection of create account "
798 				"data from the server in background thread");
799 			messenger.SendMessage(&message);
800 		} else {
801 			debugger("unable to configure the "
802 				"'MSG_CREATE_ACCOUNT_SETUP_SUCCESS' message.");
803 		}
804 	}
805 
806 	if (result != B_OK) {
807 		// any error messages / alerts should have already been handled by this
808 		// point.
809 		messenger.SendMessage(MSG_CREATE_ACCOUNT_SETUP_ERROR);
810 	}
811 
812 	threadData->window->_SetWorkerThreadLocked(-1);
813 	delete threadData;
814 	return 0;
815 }
816 
817 
818 status_t
_CreateAccountUserUsageConditionsSetupThread(UserUsageConditions & userUsageConditions)819 UserLoginWindow::_CreateAccountUserUsageConditionsSetupThread(
820 	UserUsageConditions& userUsageConditions)
821 {
822 	WebAppInterface* interface = fModel.GetWebAppInterface();
823 	status_t result = interface->RetrieveUserUsageConditions(NULL, userUsageConditions);
824 
825 	if (result != B_OK) {
826 		AppUtils::NotifySimpleError(
827 			B_TRANSLATE("Usage conditions download problem"),
828 			B_TRANSLATE("An error has arisen downloading the usage "
829 				"conditions required to create a new user. Check the log for "
830 				"details and try again. "
831 				ALERT_MSG_LOGS_USER_GUIDE));
832 	}
833 
834 	return result;
835 }
836 
837 
838 status_t
_CreateAccountPasswordRequirementsSetupThread(PasswordRequirements & passwordRequirements)839 UserLoginWindow::_CreateAccountPasswordRequirementsSetupThread(
840 	PasswordRequirements& passwordRequirements)
841 {
842 	WebAppInterface* interface = fModel.GetWebAppInterface();
843 	status_t result = interface->RetrievePasswordRequirements(passwordRequirements);
844 
845 	if (result != B_OK) {
846 		AppUtils::NotifySimpleError(
847 			B_TRANSLATE("Password requirements download problem"),
848 			B_TRANSLATE("An error has arisen downloading the password "
849 				"requirements required to create a new user. Check the log for "
850 				"details and try again. "
851 				ALERT_MSG_LOGS_USER_GUIDE));
852 	}
853 
854 	return result;
855 }
856 
857 
858 status_t
_CreateAccountCaptchaSetupThread(Captcha & captcha)859 UserLoginWindow::_CreateAccountCaptchaSetupThread(Captcha& captcha)
860 {
861 	WebAppInterface* interface = fModel.GetWebAppInterface();
862 	BMessage responsePayload;
863 
864 	status_t status = interface->RequestCaptcha(responsePayload);
865 
866 // check for transport related errors.
867 
868 	if (status != B_OK) {
869 		AppUtils::NotifySimpleError(
870 			B_TRANSLATE("Captcha error"),
871 			B_TRANSLATE("It was not possible to communicate with the server to "
872 				"obtain a captcha image required to create a new user."));
873 	}
874 
875 // check for server-generated errors.
876 
877 	if (status == B_OK) {
878 		if (WebAppInterface::ErrorCodeFromResponse(responsePayload)
879 				!= ERROR_CODE_NONE) {
880 			ServerHelper::AlertTransportError(&responsePayload);
881 			status = B_ERROR;
882 		}
883 	}
884 
885 // now parse the response from the server and extract the captcha data.
886 
887 	if (status == B_OK) {
888 		status = _UnpackCaptcha(responsePayload, captcha);
889 		if (status != B_OK) {
890 			AppUtils::NotifySimpleError(
891 				B_TRANSLATE("Captcha error"),
892 				B_TRANSLATE("It was not possible to extract necessary captcha "
893 					"information from the data sent back from the server."));
894 		}
895 	}
896 
897 	return status;
898 }
899 
900 
901 /*!	Takes the data returned to the client after it was requested from the
902 	server and extracts from it the captcha image.
903 */
904 
905 status_t
_UnpackCaptcha(BMessage & responsePayload,Captcha & captcha)906 UserLoginWindow::_UnpackCaptcha(BMessage& responsePayload, Captcha& captcha)
907 {
908 	status_t result = B_OK;
909 
910 	BMessage resultMessage;
911 	if (result == B_OK)
912 		result = responsePayload.FindMessage("result", &resultMessage);
913 	BString token;
914 	if (result == B_OK)
915 		result = resultMessage.FindString("token", &token);
916 	BString pngImageDataBase64;
917 	if (result == B_OK)
918 		result = resultMessage.FindString("pngImageDataBase64", &pngImageDataBase64);
919 
920 	ssize_t encodedSize = 0;
921 	ssize_t decodedSize = 0;
922 	if (result == B_OK) {
923 		encodedSize = pngImageDataBase64.Length();
924 		decodedSize = (encodedSize * 3 + 3) / 4;
925 		if (decodedSize <= 0)
926 			result = B_ERROR;
927 	}
928 	else
929 		HDERROR("obtained a captcha with no image data");
930 
931 	char* buffer = NULL;
932 	if (result == B_OK) {
933 		buffer = new char[decodedSize];
934 		decodedSize = decode_base64(buffer, pngImageDataBase64.String(),
935 			encodedSize);
936 		if (decodedSize <= 0)
937 			result = B_ERROR;
938 
939 		if (result == B_OK) {
940 			captcha.SetToken(token);
941 			captcha.SetPngImageData(buffer, decodedSize);
942 		}
943 		delete[] buffer;
944 
945 		HDDEBUG("did obtain a captcha image of size %" B_PRIuSIZE " bytes",
946 			decodedSize);
947 	}
948 
949 	return result;
950 }
951 
952 
953 void
_HandleCreateAccountSetupSuccess(BMessage * message)954 UserLoginWindow::_HandleCreateAccountSetupSuccess(BMessage* message)
955 {
956 	HDDEBUG("handling account setup success");
957 
958 	BMessage captchaMessage;
959 	BMessage userUsageConditionsMessage;
960 	BMessage passwordRequirementsMessage;
961 
962 	if (message->FindMessage(KEY_CAPTCHA_IMAGE, &captchaMessage) == B_OK)
963 		_SetCaptcha(new Captcha(&captchaMessage));
964 
965 	if (message->FindMessage(KEY_USER_USAGE_CONDITIONS,
966 			&userUsageConditionsMessage) == B_OK) {
967 		_SetUserUsageConditions(
968 			new UserUsageConditions(&userUsageConditionsMessage));
969 	}
970 
971 	if (message->FindMessage(KEY_PASSWORD_REQUIREMENTS,
972 			&passwordRequirementsMessage) == B_OK) {
973 		_SetPasswordRequirements(
974 			new PasswordRequirements(&passwordRequirementsMessage));
975 	}
976 
977 	_EnableMutableControls(true);
978 }
979 
980 
981 void
_SetCaptcha(Captcha * captcha)982 UserLoginWindow::_SetCaptcha(Captcha* captcha)
983 {
984 	HDDEBUG("setting captcha");
985 	if (fCaptcha != NULL)
986 		delete fCaptcha;
987 	fCaptcha = captcha;
988 
989 	if (fCaptcha == NULL)
990 		fCaptchaView->UnsetBitmap();
991 	else {
992 		BBitmap* bitmap = BTranslationUtils::GetBitmap(fCaptcha->PngImageData());
993 
994 		if (bitmap == NULL) {
995 			HDERROR("unable to read the captcha bitmap as an image");
996 			fCaptchaView->UnsetBitmap();
997 		} else {
998 			BitmapHolderRef bitmapHolderRef
999 				= BitmapHolderRef(new(std::nothrow) BitmapHolder(bitmap), true);
1000 			fCaptchaView->SetBitmap(bitmapHolderRef);
1001 		}
1002 	}
1003 	fCaptchaResultField->SetText("");
1004 }
1005 
1006 
1007 /*!	This method is hit when the user usage conditions data arrives back from the
1008 	server.  At this point some of the UI elements may need to be updated.
1009 */
1010 
1011 void
_SetUserUsageConditions(UserUsageConditions * userUsageConditions)1012 UserLoginWindow::_SetUserUsageConditions(
1013 	UserUsageConditions* userUsageConditions)
1014 {
1015 	HDDEBUG("setting user usage conditions");
1016 	if (fUserUsageConditions != NULL)
1017 		delete fUserUsageConditions;
1018 	fUserUsageConditions = userUsageConditions;
1019 
1020 	if (fUserUsageConditions != NULL) {
1021 		fConfirmMinimumAgeCheckBox->SetLabel(
1022     		LocaleUtils::CreateTranslatedIAmMinimumAgeSlug(
1023     			fUserUsageConditions->MinimumAge()));
1024 	} else {
1025 		fConfirmMinimumAgeCheckBox->SetLabel(PLACEHOLDER_TEXT);
1026 		fConfirmMinimumAgeCheckBox->SetValue(0);
1027 		fConfirmUserUsageConditionsCheckBox->SetValue(0);
1028 	}
1029 }
1030 
1031 
1032 void
_SetPasswordRequirements(PasswordRequirements * passwordRequirements)1033 UserLoginWindow::_SetPasswordRequirements(
1034 	PasswordRequirements* passwordRequirements)
1035 {
1036 	HDDEBUG("setting password requirements");
1037 	if (fPasswordRequirements != NULL)
1038 		delete fPasswordRequirements;
1039 	fPasswordRequirements = passwordRequirements;
1040 	if (fPasswordRequirements != NULL) {
1041 		HDDEBUG("password requirements set to; len %" B_PRId32
1042 			", caps %" B_PRId32 ", digits %" B_PRId32,
1043 			fPasswordRequirements->MinPasswordLength(),
1044 			fPasswordRequirements->MinPasswordUppercaseChar(),
1045 			fPasswordRequirements->MinPasswordUppercaseChar());
1046 	}
1047 }
1048 
1049 
1050 // #pragma mark - Create Account
1051 
1052 
1053 void
_CreateAccount()1054 UserLoginWindow::_CreateAccount()
1055 {
1056 	BAutolock locker(&fLock);
1057 
1058 	if (fCaptcha == NULL)
1059 		debugger("missing captcha when assembling create user details");
1060 	if (fUserUsageConditions == NULL)
1061 		debugger("missing user usage conditions when assembling create user "
1062 			"details");
1063 
1064 	if (fWorkerThread >= 0)
1065 		return;
1066 
1067 	CreateUserDetail* detail = new CreateUserDetail();
1068 	ValidationFailures validationFailures;
1069 
1070 	_AssembleCreateUserDetail(*detail);
1071 	_ValidateCreateUserDetail(*detail, validationFailures);
1072 	_MarkCreateUserInvalidFields(validationFailures);
1073 	_AlertCreateUserValidationFailure(validationFailures);
1074 
1075 	if (validationFailures.IsEmpty()) {
1076 		CreateAccountThreadData* data = new CreateAccountThreadData();
1077 		data->window = this;
1078 		data->detail = detail;
1079 
1080 		thread_id thread = spawn_thread(&_CreateAccountThreadEntry,
1081 			"Account creator", B_NORMAL_PRIORITY, data);
1082 		if (thread >= 0)
1083 			_SetWorkerThread(thread);
1084 	}
1085 }
1086 
1087 
1088 /*!	Take the data from the user interface and put it into a model object to be
1089 	used as the input for the validation and communication with the backend
1090 	application server (HDS).
1091 */
1092 
1093 void
_AssembleCreateUserDetail(CreateUserDetail & detail)1094 UserLoginWindow::_AssembleCreateUserDetail(CreateUserDetail& detail)
1095 {
1096 	detail.SetNickname(fNewNicknameField->Text());
1097 	detail.SetPasswordClear(fNewPasswordField->Text());
1098 	detail.SetIsPasswordRepeated(strlen(fRepeatPasswordField->Text()) > 0
1099 		&& strcmp(fNewPasswordField->Text(),
1100 			fRepeatPasswordField->Text()) == 0);
1101 	detail.SetEmail(fEmailField->Text());
1102 
1103 	if (fCaptcha != NULL)
1104 		detail.SetCaptchaToken(fCaptcha->Token());
1105 
1106 	detail.SetCaptchaResponse(fCaptchaResultField->Text());
1107 	detail.SetLanguageId(fPreferredLanguageId);
1108 
1109 	if ( fUserUsageConditions != NULL
1110 		&& fConfirmMinimumAgeCheckBox->Value() == 1
1111 		&& fConfirmUserUsageConditionsCheckBox->Value() == 1) {
1112 		detail.SetAgreedToUserUsageConditionsCode(fUserUsageConditions->Code());
1113 	}
1114 }
1115 
1116 
1117 /*!	This method will check the data supplied in the detail and will relay any
1118 	validation or data problems into the supplied ValidationFailures object.
1119 */
1120 
1121 void
_ValidateCreateUserDetail(CreateUserDetail & detail,ValidationFailures & failures)1122 UserLoginWindow::_ValidateCreateUserDetail(
1123 	CreateUserDetail& detail, ValidationFailures& failures)
1124 {
1125 	if (!ValidationUtils::IsValidEmail(detail.Email()))
1126 		failures.AddFailure("email", "malformed");
1127 
1128 	if (detail.Nickname().IsEmpty())
1129 		failures.AddFailure("nickname", "required");
1130 	else {
1131 		if (!ValidationUtils::IsValidNickname(detail.Nickname()))
1132 			failures.AddFailure("nickname", "malformed");
1133 	}
1134 
1135 	if (detail.PasswordClear().IsEmpty())
1136 		failures.AddFailure("passwordClear", "required");
1137 	else {
1138 		if (!ValidationUtils::IsValidPasswordClear(detail.PasswordClear()))
1139 			failures.AddFailure("passwordClear", "invalid");
1140 	}
1141 
1142 	if (!detail.IsPasswordRepeated())
1143 		failures.AddFailure("repeatPasswordClear", "repeat");
1144 
1145 	if (detail.AgreedToUserUsageConditionsCode().IsEmpty())
1146 		failures.AddFailure("agreedToUserUsageConditionsCode", "required");
1147 
1148 	if (detail.CaptchaResponse().IsEmpty())
1149 		failures.AddFailure("captchaResponse", "required");
1150 }
1151 
1152 
1153 void
_MarkCreateUserInvalidFields()1154 UserLoginWindow::_MarkCreateUserInvalidFields()
1155 {
1156 	CreateUserDetail detail;
1157 	ValidationFailures failures;
1158 	_AssembleCreateUserDetail(detail);
1159 	_ValidateCreateUserDetail(detail, failures);
1160 	_MarkCreateUserInvalidFields(failures);
1161 }
1162 
1163 
1164 void
_MarkCreateUserInvalidFields(const ValidationFailures & failures)1165 UserLoginWindow::_MarkCreateUserInvalidFields(
1166 	const ValidationFailures& failures)
1167 {
1168 	fNewNicknameField->MarkAsInvalid(failures.Contains("nickname"));
1169 	fNewPasswordField->MarkAsInvalid(failures.Contains("passwordClear"));
1170 	fRepeatPasswordField->MarkAsInvalid(failures.Contains("repeatPasswordClear"));
1171 	fEmailField->MarkAsInvalid(failures.Contains("email"));
1172 	fCaptchaResultField->MarkAsInvalid(failures.Contains("captchaResponse"));
1173 }
1174 
1175 
1176 void
_AlertCreateUserValidationFailure(const ValidationFailures & failures)1177 UserLoginWindow::_AlertCreateUserValidationFailure(
1178 	const ValidationFailures& failures)
1179 {
1180 	if (!failures.IsEmpty()) {
1181 		BString alertMessage = B_TRANSLATE("There are problems in the supplied "
1182 			"data:");
1183 		alertMessage << "\n\n";
1184 
1185 		for (int32 i = 0; i < failures.CountFailures(); i++) {
1186 			ValidationFailure* failure = failures.FailureAtIndex(i);
1187 			BStringList messages = failure->Messages();
1188 
1189 			for (int32 j = 0; j < messages.CountStrings(); j++) {
1190 				alertMessage << _CreateAlertTextFromValidationFailure(
1191 					failure->Property(), messages.StringAt(j));
1192 				alertMessage << '\n';
1193 			}
1194 		}
1195 
1196 		BAlert* alert = new(std::nothrow) BAlert(
1197 			B_TRANSLATE("Input validation"),
1198 			alertMessage,
1199 			B_TRANSLATE("OK"), NULL, NULL,
1200 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1201 
1202 		if (alert != NULL)
1203 			alert->Go();
1204 	}
1205 }
1206 
1207 
1208 /*!	This method produces a debug string for a set of validation failures.
1209  */
1210 
1211 /*static*/ void
_ValidationFailuresToString(const ValidationFailures & failures,BString & output)1212 UserLoginWindow::_ValidationFailuresToString(const ValidationFailures& failures,
1213 	BString& output)
1214 {
1215 	for (int32 i = 0; i < failures.CountFailures(); i++) {
1216 		ValidationFailure* failure = failures.FailureAtIndex(i);
1217 		BStringList messages = failure->Messages();
1218 		for (int32 j = 0; j < messages.CountStrings(); j++)
1219 		{
1220 			if (0 != j || 0 != i)
1221 				output << ", ";
1222 			output << failure->Property();
1223 			output << ":";
1224 			output << messages.StringAt(j);
1225 		}
1226 	}
1227 }
1228 
1229 
1230 /*static*/ BString
_CreateAlertTextFromValidationFailure(const BString & property,const BString & message)1231 UserLoginWindow::_CreateAlertTextFromValidationFailure(
1232 	const BString& property, const BString& message)
1233 {
1234 	if (property == "email" && message == "malformed")
1235 		return B_TRANSLATE("The email is malformed.");
1236 
1237 	if (property == "nickname" && message == "notunique") {
1238 		return B_TRANSLATE("The nickname must be unique, but the supplied "
1239 			"nickname is already taken. Choose a different nickname.");
1240 	}
1241 
1242 	if (property == "nickname" && message == "required")
1243 		return B_TRANSLATE("The nickname is required.");
1244 
1245 	if (property == "nickname" && message == "malformed") {
1246 		return B_TRANSLATE("The nickname is malformed. The nickname may only "
1247 			"contain digits and lower case latin characters. The nickname "
1248 			"must be between four and sixteen characters in length.");
1249 	}
1250 
1251 	if (property == "passwordClear" && message == "required")
1252 		return B_TRANSLATE("A password is required.");
1253 
1254 	if (property == "passwordClear" && message == "invalid") {
1255 		return B_TRANSLATE("The password must be at least eight characters "
1256 			"long, consist of at least two digits and one upper case "
1257 			"character.");
1258 	}
1259 
1260 	if (property == "passwordClearRepeated" && message == "required") {
1261 		return B_TRANSLATE("The password must be repeated in order to reduce "
1262 			"the chance of entering the password incorrectly.");
1263 	}
1264 
1265 	if (property == "passwordClearRepeated" && message == "repeat")
1266 		return B_TRANSLATE("The password has been incorrectly repeated.");
1267 
1268 	if (property == "agreedToUserUsageConditionsCode"
1269 			&& message == "required") {
1270 		return B_TRANSLATE("The usage agreement must be agreed to and a "
1271 			"confirmation should be made that the person creating the user "
1272 			"meets the minimum age requirement.");
1273 	}
1274 
1275 	if (property == "captchaResponse" && message == "required") {
1276 		return B_TRANSLATE("A response to the captcha question must be "
1277 			"provided.");
1278 	}
1279 
1280 	if (property == "captchaResponse" && message == "captchabadresponse") {
1281 		return B_TRANSLATE("The supplied response to the captcha is "
1282 			"incorrect. A new captcha will be generated; try again.");
1283 	}
1284 
1285 	BString result = B_TRANSLATE("An unexpected error '%Message%' has arisen "
1286 		"with property '%Property%'");
1287 	result.ReplaceAll("%Message%", message);
1288 	result.ReplaceAll("%Property%", property);
1289 	return result;
1290 }
1291 
1292 
1293 /*!	This is the entry-point for the thread that will process the data to create
1294 	the new account.
1295 */
1296 
1297 int32
_CreateAccountThreadEntry(void * data)1298 UserLoginWindow::_CreateAccountThreadEntry(void* data)
1299 {
1300 	CreateAccountThreadData* threadData =
1301 		static_cast<CreateAccountThreadData*>(data);
1302 	threadData->window->_CreateAccountThread(threadData->detail);
1303 	threadData->window->_SetWorkerThreadLocked(-1);
1304 	if (NULL != threadData->detail)
1305 		delete threadData->detail;
1306 	return 0;
1307 }
1308 
1309 
1310 /*!	This method runs in a background thread run and makes the necessary calls
1311 	to the application server to actually create the user.
1312 */
1313 
1314 void
_CreateAccountThread(CreateUserDetail * detail)1315 UserLoginWindow::_CreateAccountThread(CreateUserDetail* detail)
1316 {
1317 	WebAppInterface* interface = fModel.GetWebAppInterface();
1318 	BMessage responsePayload;
1319 	BMessenger messenger(this);
1320 
1321 	status_t status = interface->CreateUser(
1322 		detail->Nickname(),
1323 		detail->PasswordClear(),
1324 		detail->Email(),
1325 		detail->CaptchaToken(),
1326 		detail->CaptchaResponse(),
1327 		detail->LanguageId(),
1328 		detail->AgreedToUserUsageConditionsCode(),
1329 		responsePayload);
1330 
1331 	BString error = B_TRANSLATE(
1332 		"There was a puzzling response from the web service.");
1333 
1334 	if (status == B_OK) {
1335 		int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
1336 
1337 		switch (errorCode) {
1338 			case ERROR_CODE_NONE:
1339 			{
1340 				BMessage userCredentialsMessage;
1341 				UserCredentials userCredentials(detail->Nickname(),
1342 					detail->PasswordClear());
1343 				userCredentials.Archive(&userCredentialsMessage);
1344 				BMessage message(MSG_CREATE_ACCOUNT_SUCCESS);
1345 				message.AddMessage(KEY_USER_CREDENTIALS,
1346 					&userCredentialsMessage);
1347 				messenger.SendMessage(&message);
1348 				break;
1349 			}
1350 			case ERROR_CODE_CAPTCHABADRESPONSE:
1351 			{
1352 				ValidationFailures validationFailures;
1353 				validationFailures.AddFailure("captchaResponse", "captchabadresponse");
1354 				BMessage validationFailuresMessage;
1355 				validationFailures.Archive(&validationFailuresMessage);
1356 				BMessage message(MSG_CREATE_ACCOUNT_FAILED);
1357 				message.AddMessage(KEY_VALIDATION_FAILURES,
1358 					&validationFailuresMessage);
1359 				messenger.SendMessage(&message);
1360 				break;
1361 			}
1362 			case ERROR_CODE_VALIDATION:
1363 			{
1364 				ValidationFailures validationFailures;
1365 				ServerHelper::GetFailuresFromJsonRpcError(validationFailures,
1366 					responsePayload);
1367 				if (Logger::IsDebugEnabled()) {
1368 					BString debugString;
1369 					_ValidationFailuresToString(validationFailures,
1370 						debugString);
1371 					HDDEBUG("create account validation issues; %s",
1372 						debugString.String());
1373 				}
1374 				BMessage validationFailuresMessage;
1375 				validationFailures.Archive(&validationFailuresMessage);
1376 				BMessage message(MSG_CREATE_ACCOUNT_FAILED);
1377 				message.AddMessage(KEY_VALIDATION_FAILURES,
1378 					&validationFailuresMessage);
1379 				messenger.SendMessage(&message);
1380 				break;
1381 			}
1382 			default:
1383 				ServerHelper::NotifyServerJsonRpcError(responsePayload);
1384 				messenger.SendMessage(MSG_CREATE_ACCOUNT_ERROR);
1385 				break;
1386 		}
1387 	} else {
1388 		AppUtils::NotifySimpleError(
1389 			B_TRANSLATE("User creation error"),
1390 			B_TRANSLATE("It was not possible to create the new user."));
1391 		messenger.SendMessage(MSG_CREATE_ACCOUNT_ERROR);
1392 	}
1393 }
1394 
1395 
1396 void
_HandleCreateAccountSuccess(const UserCredentials & credentials)1397 UserLoginWindow::_HandleCreateAccountSuccess(
1398 	const UserCredentials& credentials)
1399 {
1400 	BString message = B_TRANSLATE("The user %Nickname% has been successfully "
1401 		"created in the HaikuDepotServer system. You can administer your user "
1402 		"details by using the web interface. You are now logged-in as this "
1403 		"new user.");
1404 	message.ReplaceAll("%Nickname%", credentials.Nickname());
1405 
1406 	BAlert* alert = new(std::nothrow) BAlert(
1407 		B_TRANSLATE("User Created"), message, B_TRANSLATE("Close"));
1408 
1409 	if (alert != NULL)
1410 		alert->Go();
1411 
1412 	_TakeUpCredentialsAndQuit(credentials);
1413 }
1414 
1415 
1416 void
_HandleCreateAccountFailure(const ValidationFailures & failures)1417 UserLoginWindow::_HandleCreateAccountFailure(const ValidationFailures& failures)
1418 {
1419 	_MarkCreateUserInvalidFields(failures);
1420 	_AlertCreateUserValidationFailure(failures);
1421 	_EnableMutableControls(true);
1422 
1423 	// if an attempt was made to the server then the captcha would have been
1424 	// used up and a new captcha is required.
1425 	_CreateAccountSetup(CREATE_CAPTCHA);
1426 }
1427 
1428 
1429 /*!	Handles the main UI-thread processing for the situation where there was an
1430 	unexpected error when creating the account.  Note that any error messages
1431 	presented to the user are expected to be prepared and initiated from the
1432 	background thread creating the account.
1433 */
1434 
1435 void
_HandleCreateAccountError()1436 UserLoginWindow::_HandleCreateAccountError()
1437 {
1438 	_EnableMutableControls(true);
1439 }
1440 
1441 
1442 /*!	Opens a new window that shows the already downloaded user usage conditions.
1443 */
1444 
1445 void
_ViewUserUsageConditions()1446 UserLoginWindow::_ViewUserUsageConditions()
1447 {
1448 	if (fUserUsageConditions == NULL)
1449 		debugger("the usage conditions should be set");
1450 	UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
1451 		fModel, *fUserUsageConditions);
1452 	window->Show();
1453 }
1454 
1455 
1456 void
_ViewPasswordRequirements()1457 UserLoginWindow::_ViewPasswordRequirements()
1458 {
1459 	if (fPasswordRequirements == NULL)
1460 		HDFATAL("the password requirements must have been setup");
1461 	BString msg = B_TRANSLATE("The password must be a minimum of "
1462 		"%MinPasswordLength% characters. "
1463 		"%MinPasswordUppercaseChar% characters must be upper-case and "
1464 		"%MinPasswordDigitsChar% characters must be digits.");
1465 	msg.ReplaceAll("%MinPasswordLength%",
1466 		BString() << fPasswordRequirements->MinPasswordLength());
1467 	msg.ReplaceAll("%MinPasswordUppercaseChar%",
1468 		BString() << fPasswordRequirements->MinPasswordUppercaseChar());
1469 	msg.ReplaceAll("%MinPasswordDigitsChar%",
1470 		BString() << fPasswordRequirements->MinPasswordDigitsChar());
1471 
1472 	BAlert* alert = new(std::nothrow) BAlert(
1473 		B_TRANSLATE("Password requirements"), msg, B_TRANSLATE("OK"));
1474 
1475 	if (alert != NULL)
1476 		alert->Go();
1477 }
1478