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