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