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