diff --git a/android/java/org/chromium/chrome/browser/settings/BraveAccountSectionController.java b/android/java/org/chromium/chrome/browser/settings/BraveAccountSectionController.java index 96c2f551b2e..8c34ebb46e8 100644 --- a/android/java/org/chromium/chrome/browser/settings/BraveAccountSectionController.java +++ b/android/java/org/chromium/chrome/browser/settings/BraveAccountSectionController.java @@ -192,7 +192,7 @@ public class BraveAccountSectionController setVisibility(resendConfirmationEmailPref, false); setVisibility(cancelRegistrationPref, false); setVisibility(getStartedPref, false); - } else if (state.which() == AccountState.Tag.Verification) { + } else if (state.getLoggedOut().verification != null) { setVisibility(userInfoPref, false); setVisibility(signOutPref, false); setVisibility(almostTherePref, true); diff --git a/browser/resources/settings/getting_started_page/brave_account_row.ts b/browser/resources/settings/getting_started_page/brave_account_row.ts index f70e2c20fbe..0c5d68abb4b 100644 --- a/browser/resources/settings/getting_started_page/brave_account_row.ts +++ b/browser/resources/settings/getting_started_page/brave_account_row.ts @@ -182,60 +182,62 @@ export class SettingsBraveAccountRowElement extends I18nMixinLit(CrLitElement) { ` ), - [AccountStateFieldTags.VERIFICATION]: () => html` - ${this.createFirstRow( - this.i18n( - BraveAccountSettingsStrings - .SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_TITLE), - [ - html` - ` - ] - )} -
- - ${this.i18n( + [AccountStateFieldTags.LOGGED_OUT]: () => + this.state!.loggedOut!.verification + ? html` + ${this.createFirstRow( + this.i18n( + BraveAccountSettingsStrings + .SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_TITLE), + [ + html` + ` + ] + )} +
+ + ${this.i18n( + BraveAccountSettingsStrings + .SETTINGS_BRAVE_ACCOUNT_ENTER_REGISTRATION_CODE_BUTTON_LABEL)} + + + ${this.i18n( + BraveAccountSettingsStrings + .SETTINGS_BRAVE_ACCOUNT_CANCEL_REGISTRATION_BUTTON_LABEL)} + +
+ ` + : this.createFirstRow( + this.i18n( BraveAccountSettingsStrings - .SETTINGS_BRAVE_ACCOUNT_ENTER_REGISTRATION_CODE_BUTTON_LABEL)} -
- - ${this.i18n( + .SETTINGS_BRAVE_ACCOUNT_LOGGED_OUT_ROW_TITLE), + [this.i18n( BraveAccountSettingsStrings - .SETTINGS_BRAVE_ACCOUNT_CANCEL_REGISTRATION_BUTTON_LABEL)} - -
- `, - [AccountStateFieldTags.LOGGED_OUT]: () => this.createFirstRow( - this.i18n( - BraveAccountSettingsStrings - .SETTINGS_BRAVE_ACCOUNT_LOGGED_OUT_ROW_TITLE), - [this.i18n( - BraveAccountSettingsStrings - .BRAVE_ACCOUNT_DESCRIPTION)], - html` - - ${this.i18n( - BraveAccountSettingsStrings - .SETTINGS_BRAVE_ACCOUNT_GET_STARTED_BUTTON_LABEL)} - - ` - ), + .BRAVE_ACCOUNT_DESCRIPTION)], + html` + + ${this.i18n( + BraveAccountSettingsStrings + .SETTINGS_BRAVE_ACCOUNT_GET_STARTED_BUTTON_LABEL)} + + ` + ), } return this.state === undefined diff --git a/components/brave_account/BUILD.gn b/components/brave_account/BUILD.gn index 0e21e653113..c8a756231f2 100644 --- a/components/brave_account/BUILD.gn +++ b/components/brave_account/BUILD.gn @@ -11,10 +11,8 @@ source_set("brave_account") { "brave_account_constants.h", "brave_account_service.cc", "brave_account_service.h", - "brave_account_service_constants.h", "brave_account_ui_base.h", "brave_account_utils.h", - "pref_names.h", "prefs.cc", "prefs.h", ] @@ -22,6 +20,7 @@ source_set("brave_account") { deps = [ "//base", "//brave/components/brave_account:features", + "//brave/components/brave_account:internal", "//brave/components/brave_account/endpoint_client", "//brave/components/brave_account/endpoints", "//brave/components/brave_account/mojom", @@ -49,6 +48,26 @@ source_set("brave_account") { } } +source_set("internal") { + visibility = [ + ":*", + "//brave/components/email_aliases:service", + ] + + sources = [ + "brave_account_service_constants.h", + "brave_account_state_prefs.cc", + "brave_account_state_prefs.h", + "pref_names.h", + ] + + public_deps = [ + "//base", + "//brave/components/brave_account/mojom", + "//components/prefs", + ] +} + source_set("features") { sources = [ "features.cc", @@ -95,6 +114,7 @@ source_set("unit_tests") { "//base/test:test_support", "//brave/components/brave_account", "//brave/components/brave_account:features", + "//brave/components/brave_account:internal", "//brave/components/brave_account:test_support", "//brave/components/brave_account/endpoint_client:test_support", "//brave/components/brave_account/endpoints", diff --git a/components/brave_account/brave_account_service.cc b/components/brave_account/brave_account_service.cc index 41e31c6019d..6f4548f9981 100644 --- a/components/brave_account/brave_account_service.cc +++ b/components/brave_account/brave_account_service.cc @@ -15,20 +15,17 @@ #include "base/check_is_test.h" #include "base/functional/bind.h" #include "base/functional/callback.h" -#include "base/json/values_util.h" #include "base/no_destructor.h" #include "base/notimplemented.h" -#include "base/strings/strcat.h" #include "base/types/expected.h" -#include "base/values.h" #include "brave/components/brave_account/brave_account_service_constants.h" +#include "brave/components/brave_account/brave_account_state_prefs.h" #include "brave/components/brave_account/brave_account_utils.h" #include "brave/components/brave_account/endpoint_client/client.h" #include "brave/components/brave_account/endpoint_client/with_headers.h" #include "brave/components/brave_account/endpoints/auth_logout.h" #include "brave/components/brave_account/endpoints/error_body.h" #include "brave/components/brave_account/endpoints/verify_delete.h" -#include "brave/components/brave_account/pref_names.h" #include "components/os_crypt/async/browser/os_crypt_async.h" #include "components/prefs/pref_service.h" #include "net/http/http_request_headers.h" @@ -159,9 +156,8 @@ BraveAccountService::BraveAccountService( PrefService* pref_service, scoped_refptr url_loader_factory, os_crypt_async::OSCryptAsync* os_crypt_async) - : pref_service_(pref_service), + : account_state_prefs_(CHECK_DEREF(pref_service)), url_loader_factory_(std::move(url_loader_factory)) { - CHECK(pref_service_); CHECK(url_loader_factory_); CHECK_DEREF(os_crypt_async) @@ -191,21 +187,9 @@ void BraveAccountService::FinishInitialization( os_crypt_async::Encryptor encryptor) { encryptor_ = std::move(encryptor); - pref_verification_token_.Init( - prefs::kBraveAccountVerificationToken, pref_service_, - base::BindRepeating(&BraveAccountService::OnVerificationTokenChanged, - base::Unretained(this))); - - pref_authentication_token_.Init( - prefs::kBraveAccountAuthenticationToken, pref_service_, - base::BindRepeating(&BraveAccountService::OnAuthenticationTokenChanged, - base::Unretained(this))); - OnAuthenticationTokenChanged(); - - pref_email_address_.Init( - prefs::kBraveAccountEmailAddress, pref_service_, - base::BindRepeating(&BraveAccountService::OnEmailAddressChanged, - base::Unretained(this))); + account_state_prefs_.StartObserving(base::BindRepeating( + &BraveAccountService::OnAccountStateChanged, base::Unretained(this))); + OnAccountStateChanged(); for (auto& pending_receiver : pending_receivers_) { authentication_receivers_.Add(this, std::move(pending_receiver)); @@ -217,7 +201,7 @@ void BraveAccountService::AddObserver( mojo::PendingRemote observer) { const auto observer_id = observers_.Add(std::move(observer)); CHECK_DEREF(observers_.Get(observer_id)) - .OnAccountStateChanged(GetAccountState()); + .OnAccountStateChanged(account_state_prefs_.GetAccountState()); } void BraveAccountService::RegisterInitialize( @@ -269,11 +253,12 @@ void BraveAccountService::RegisterVerify(const std::string& code, CHECK(!code.empty()); const auto encrypted_verification_token = - pref_service_->GetString(prefs::kBraveAccountVerificationToken); + account_state_prefs_.GetVerificationToken( + mojom::LoggedOutVerificationIntent::kRegistration); if (encrypted_verification_token.empty()) { return std::move(callback).Run( base::unexpected(MakeClientError( - mojom::RegisterClientErrorCode::kUserNotInTheVerificationState))); + mojom::RegisterClientErrorCode::kNoRegistrationInProgress))); } const auto verification_token = Decrypt(encrypted_verification_token); @@ -295,12 +280,13 @@ void BraveAccountService::RegisterVerify(const std::string& code, void BraveAccountService::ResendConfirmationEmail( ResendConfirmationEmailCallback callback) { const auto encrypted_verification_token = - pref_service_->GetString(prefs::kBraveAccountVerificationToken); + account_state_prefs_.GetVerificationToken( + mojom::LoggedOutVerificationIntent::kRegistration); if (encrypted_verification_token.empty()) { return std::move(callback).Run( base::unexpected(MakeClientError( mojom::ResendConfirmationEmailClientErrorCode:: - kUserNotInTheVerificationState))); + kNoRegistrationInProgress))); } const auto verification_token = Decrypt(encrypted_verification_token); @@ -325,9 +311,10 @@ void BraveAccountService::ResendConfirmationEmail( void BraveAccountService::CancelRegistration() { const auto encrypted_verification_token = - pref_service_->GetString(prefs::kBraveAccountVerificationToken); + account_state_prefs_.GetVerificationToken( + mojom::LoggedOutVerificationIntent::kRegistration); - pref_service_->ClearPref(prefs::kBraveAccountVerificationToken); + account_state_prefs_.SetLoggedOut(); const auto verification_token = Decrypt(encrypted_verification_token); if (verification_token.empty()) { @@ -385,9 +372,9 @@ void BraveAccountService::LoginFinalize( void BraveAccountService::LogOut() { const auto encrypted_authentication_token = - pref_service_->GetString(prefs::kBraveAccountAuthenticationToken); + account_state_prefs_.GetAuthenticationToken(); - pref_service_->ClearPref(prefs::kBraveAccountAuthenticationToken); + account_state_prefs_.SetLoggedOut(); const auto authentication_token = Decrypt(encrypted_authentication_token); if (authentication_token.empty()) { @@ -406,14 +393,15 @@ void BraveAccountService::GetServiceToken(mojom::Service service, GetServiceTokenCallback callback) { CHECK(service != mojom::Service::kAccounts); std::string service_name(kServiceToString.at(service)); - if (auto service_token = GetCachedServiceToken(service_name); + if (auto service_token = + Decrypt(account_state_prefs_.GetCachedServiceToken(service_name)); !service_token.empty()) { return std::move(callback).Run( mojom::GetServiceTokenResult::New(std::move(service_token))); } auto encrypted_authentication_token = - pref_service_->GetString(prefs::kBraveAccountAuthenticationToken); + account_state_prefs_.GetAuthenticationToken(); if (encrypted_authentication_token.empty()) { return std::move(callback).Run( base::unexpected(MakeClientError( @@ -513,8 +501,9 @@ void BraveAccountService::OnRegisterFinalize( .and_then([&](auto success_body) -> base::expected { - pref_service_->SetString(prefs::kBraveAccountVerificationToken, - encrypted_verification_token); + account_state_prefs_.SetLoggedOutWithVerification( + encrypted_verification_token, + mojom::LoggedOutVerificationIntent::kRegistration); return mojom::RegisterFinalizeResult::New(); }); @@ -560,11 +549,8 @@ void BraveAccountService::OnRegisterVerify(RegisterVerifyCallback callback, kAuthenticationTokenEncryptionFailed)); } - pref_service_->SetString(prefs::kBraveAccountEmailAddress, - success_body.email); - pref_service_->SetString(prefs::kBraveAccountAuthenticationToken, - encrypted_authentication_token); - pref_service_->ClearPref(prefs::kBraveAccountVerificationToken); + account_state_prefs_.SetLoggedIn(success_body.email, + encrypted_authentication_token); return mojom::RegisterVerifyResult::New(); }); @@ -592,10 +578,6 @@ void BraveAccountService::OnResendConfirmationEmail( std::move(response.body->error())))); } -void BraveAccountService::OnVerificationTokenChanged() { - NotifyObservers(); -} - void BraveAccountService::OnLoginInitialize(LoginInitializeCallback callback, LoginInit::Response response) { if (!response.body) { @@ -678,10 +660,8 @@ void BraveAccountService::OnLoginFinalize(LoginFinalizeCallback callback, kAuthenticationTokenEncryptionFailed)); } - pref_service_->SetString(prefs::kBraveAccountEmailAddress, - success_body.email); - pref_service_->SetString(prefs::kBraveAccountAuthenticationToken, - encrypted_authentication_token); + account_state_prefs_.SetLoggedIn(success_body.email, + encrypted_authentication_token); return mojom::LoginFinalizeResult::New(); }); @@ -689,12 +669,13 @@ void BraveAccountService::OnLoginFinalize(LoginFinalizeCallback callback, std::move(callback).Run(std::move(result)); } -void BraveAccountService::OnAuthenticationTokenChanged() { - NotifyObservers(); +void BraveAccountService::OnAccountStateChanged() { + const auto account_state = account_state_prefs_.GetAccountState(); + for (auto& observer : observers_) { + observer->OnAccountStateChanged(account_state.Clone()); + } - if (pref_authentication_token_.GetValue().empty()) { - pref_service_->ClearPref(prefs::kBraveAccountEmailAddress); - pref_service_->ClearPref(prefs::kBraveAccountServiceTokens); + if (!account_state->is_logged_in()) { return auth_validate_timer_.Stop(); } @@ -715,7 +696,7 @@ void BraveAccountService::AuthValidate( current_auth_validate_request.reset(); const auto encrypted_authentication_token = - pref_service_->GetString(prefs::kBraveAccountAuthenticationToken); + account_state_prefs_.GetAuthenticationToken(); if (encrypted_authentication_token.empty()) { return; } @@ -747,51 +728,17 @@ void BraveAccountService::OnAuthValidate(AuthValidate::Response response) { : ""; if (!email.empty()) { - pref_service_->SetString(prefs::kBraveAccountEmailAddress, email); + account_state_prefs_.UpdateEmail(email); } else if (response.status_code >= 400 && response.status_code < 500) { - // Clear the auth token (and stop polling) to prevent - // presenting invalid state to the user and issuing invalid requests. - return pref_service_->ClearPref(prefs::kBraveAccountAuthenticationToken); + // Force logged-out (and stop polling) to prevent presenting invalid state + // to the user and issuing invalid requests. + return account_state_prefs_.SetLoggedOut(); } // Replace watchdog timer with the normal cadence. ScheduleAuthValidate(kAuthValidatePollInterval); } -void BraveAccountService::OnEmailAddressChanged() { - // Only notify observers if logged in, since the email is only relevant in - // the LoggedIn state. - if (!pref_authentication_token_.GetValue().empty()) { - NotifyObservers(); - } -} - -void BraveAccountService::NotifyObservers() { - const auto state = GetAccountState(); - for (auto& observer : observers_) { - observer->OnAccountStateChanged(state.Clone()); - } -} - -mojom::AccountStatePtr BraveAccountService::GetAccountState() const { - if (!pref_service_->GetString(prefs::kBraveAccountAuthenticationToken) - .empty()) { - std::string email = - pref_service_->GetString(prefs::kBraveAccountEmailAddress); - CHECK(!email.empty()); - return mojom::AccountState::NewLoggedIn( - mojom::LoggedInState::New(std::move(email))); - } - - if (!pref_service_->GetString(prefs::kBraveAccountVerificationToken) - .empty()) { - return mojom::AccountState::NewVerification( - mojom::VerificationState::New()); - } - - return mojom::AccountState::NewLoggedOut(mojom::LoggedOutState::New()); -} - void BraveAccountService::OnGetServiceToken( const std::string& expected_encrypted_authentication_token, const std::string& service_name, @@ -803,7 +750,7 @@ void BraveAccountService::OnGetServiceToken( // token as it belongs to a different (or no longer valid) authentication // session. if (const auto current_encrypted_authentication_token = - pref_service_->GetString(prefs::kBraveAccountAuthenticationToken); + account_state_prefs_.GetAuthenticationToken(); current_encrypted_authentication_token != expected_encrypted_authentication_token) { return std::move(callback).Run( @@ -849,18 +796,8 @@ void BraveAccountService::OnGetServiceToken( kServiceTokenEncryptionFailed)); } - auto service_tokens = - pref_service_->GetDict(prefs::kBraveAccountServiceTokens) - .Clone(); - service_tokens.Set(service_name, - base::DictValue() - .Set(prefs::keys::kServiceToken, - std::move(encrypted_service_token)) - .Set(prefs::keys::kLastFetched, - base::TimeToValue(base::Time::Now()))); - - pref_service_->SetDict(prefs::kBraveAccountServiceTokens, - std::move(service_tokens)); + account_state_prefs_.CacheServiceToken( + service_name, std::move(encrypted_service_token)); return mojom::GetServiceTokenResult::New( std::move(success_body.auth_token)); @@ -869,35 +806,6 @@ void BraveAccountService::OnGetServiceToken( std::move(callback).Run(std::move(result)); } -std::string BraveAccountService::GetCachedServiceToken( - const std::string& service_name) const { - const auto* service = - pref_service_->GetDict(prefs::kBraveAccountServiceTokens) - .FindDict(service_name); - if (!service) { - return ""; - } - - const auto* encrypted_service_token = - service->FindString(prefs::keys::kServiceToken); - const auto* last_fetched_value = service->Find(prefs::keys::kLastFetched); - - if (!encrypted_service_token || !last_fetched_value) { - return ""; - } - - const auto last_fetched_time = base::ValueToTime(*last_fetched_value); - if (!last_fetched_time) { - return ""; - } - - if (base::Time::Now() - *last_fetched_time >= kServiceTokenMaxAge) { - return ""; - } - - return Decrypt(*encrypted_service_token); -} - std::string BraveAccountService::Encrypt(const std::string& plain_text) const { if (plain_text.empty()) { return std::string(); diff --git a/components/brave_account/brave_account_service.h b/components/brave_account/brave_account_service.h index 7f67c4115c9..aa88b8fe629 100644 --- a/components/brave_account/brave_account_service.h +++ b/components/brave_account/brave_account_service.h @@ -11,11 +11,11 @@ #include #include "base/functional/callback.h" -#include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "base/timer/timer.h" +#include "brave/components/brave_account/brave_account_state_prefs.h" #include "brave/components/brave_account/endpoint_client/request_handle.h" #include "brave/components/brave_account/endpoints/auth_validate.h" #include "brave/components/brave_account/endpoints/login_finalize.h" @@ -28,7 +28,6 @@ #include "brave/components/brave_account/mojom/brave_account.mojom.h" #include "components/keyed_service/core/keyed_service.h" #include "components/os_crypt/async/common/encryptor.h" -#include "components/prefs/pref_member.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver_set.h" @@ -125,15 +124,13 @@ class BraveAccountService : public KeyedService, public mojom::Authentication { void OnResendConfirmationEmail(ResendConfirmationEmailCallback callback, endpoints::VerifyResend::Response response); - void OnVerificationTokenChanged(); - void OnLoginInitialize(LoginInitializeCallback callback, endpoints::LoginInit::Response response); void OnLoginFinalize(LoginFinalizeCallback callback, endpoints::LoginFinalize::Response response); - void OnAuthenticationTokenChanged(); + void OnAccountStateChanged(); void ScheduleAuthValidate( base::TimeDelta delay = base::Seconds(0), @@ -144,33 +141,22 @@ class BraveAccountService : public KeyedService, public mojom::Authentication { void OnAuthValidate(endpoints::AuthValidate::Response response); - void OnEmailAddressChanged(); - - void NotifyObservers(); - - mojom::AccountStatePtr GetAccountState() const; - void OnGetServiceToken( const std::string& expected_encrypted_authentication_token, const std::string& service_name, GetServiceTokenCallback callback, endpoints::ServiceToken::Response response); - std::string GetCachedServiceToken(const std::string& service_name) const; - std::string Encrypt(const std::string& plain_text) const; std::string Decrypt(const std::string& base64) const; - const raw_ptr pref_service_; + AccountStatePrefs account_state_prefs_; scoped_refptr url_loader_factory_; std::optional encryptor_; std::vector> pending_receivers_; mojo::ReceiverSet authentication_receivers_; mojo::RemoteSet observers_; - StringPrefMember pref_verification_token_; - StringPrefMember pref_authentication_token_; - StringPrefMember pref_email_address_; base::OneShotTimer auth_validate_timer_; base::WeakPtrFactory weak_factory_{this}; }; diff --git a/components/brave_account/brave_account_service_constants.h b/components/brave_account/brave_account_service_constants.h index a8c8493a67d..b43be905c16 100644 --- a/components/brave_account/brave_account_service_constants.h +++ b/components/brave_account/brave_account_service_constants.h @@ -10,11 +10,10 @@ namespace brave_account { -inline constexpr base::TimeDelta kVerifyResultPollInterval = base::Seconds(5); inline constexpr base::TimeDelta kAuthValidatePollInterval = base::Minutes(2); inline constexpr base::TimeDelta kVerifyResendTimeout = base::Seconds(10); -// If a polling request (VerifyResult or AuthValidate) doesn't complete within +// If a polling request (AuthValidate) doesn't complete within // this interval, it will be canceled and retried to prevent hung requests from // stopping the periodic polling indefinitely. inline constexpr base::TimeDelta kWatchdogInterval = base::Seconds(15); diff --git a/components/brave_account/brave_account_service_unittest.cc b/components/brave_account/brave_account_service_unittest.cc index a2a7f47cff0..31c0cc11883 100644 --- a/components/brave_account/brave_account_service_unittest.cc +++ b/components/brave_account/brave_account_service_unittest.cc @@ -18,6 +18,7 @@ #include "base/values.h" #include "brave/components/brave_account/brave_account_service_constants.h" #include "brave/components/brave_account/brave_account_service_test.h" +#include "brave/components/brave_account/brave_account_state_prefs.h" #include "brave/components/brave_account/endpoints/auth_validate.h" #include "brave/components/brave_account/endpoints/login_finalize.h" #include "brave/components/brave_account/endpoints/login_init.h" @@ -29,6 +30,7 @@ #include "brave/components/brave_account/mojom/brave_account.mojom.h" #include "brave/components/brave_account/pref_names.h" #include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" #include "net/http/http_status_code.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -82,6 +84,7 @@ struct AuthenticationObserverTestCase { static void Run(const AuthenticationObserverTestCase& test_case, PrefService& pref_service, mojom::Authentication& authentication) { + AccountStatePrefs account_state_prefs(pref_service); const auto account_state_eq = [](const mojom::AccountStatePtr& expected) { return testing::Truly([&](const mojom::AccountStatePtr& state) { return state.Equals(expected); @@ -90,15 +93,15 @@ struct AuthenticationObserverTestCase { switch (CHECK_DEREF(test_case.from).which()) { case mojom::AccountState::Tag::kLoggedOut: - break; - case mojom::AccountState::Tag::kVerification: - pref_service.SetString(prefs::kBraveAccountVerificationToken, - EncryptedVerificationToken()); + if (test_case.from->get_logged_out()->verification) { + account_state_prefs.SetLoggedOutWithVerification( + EncryptedVerificationToken(), + mojom::LoggedOutVerificationIntent::kRegistration); + } break; case mojom::AccountState::Tag::kLoggedIn: - pref_service.SetString(prefs::kBraveAccountEmailAddress, kEmailAddress); - pref_service.SetString(prefs::kBraveAccountAuthenticationToken, - EncryptedAuthenticationToken()); + account_state_prefs.SetLoggedIn(kEmailAddress, + EncryptedAuthenticationToken()); break; } @@ -118,19 +121,19 @@ struct AuthenticationObserverTestCase { switch (test_case.action) { case StateAction::kSwitchToVerification: - pref_service.SetString(prefs::kBraveAccountVerificationToken, - EncryptedVerificationToken()); + account_state_prefs.SetLoggedOutWithVerification( + EncryptedVerificationToken(), + mojom::LoggedOutVerificationIntent::kRegistration); break; case StateAction::kSwitchToLoggedIn: - pref_service.SetString(prefs::kBraveAccountEmailAddress, kEmailAddress); - pref_service.SetString(prefs::kBraveAccountAuthenticationToken, - EncryptedAuthenticationToken()); + account_state_prefs.SetLoggedIn(kEmailAddress, + EncryptedAuthenticationToken()); break; case StateAction::kSwitchToLoggedOut: - pref_service.ClearPref(prefs::kBraveAccountAuthenticationToken); + account_state_prefs.SetLoggedOut(); break; case StateAction::kUpdateEmailAddress: - pref_service.SetString(prefs::kBraveAccountEmailAddress, "new_email"); + account_state_prefs.UpdateEmail("new_email"); break; } @@ -145,17 +148,30 @@ struct AuthenticationObserverTestCase { namespace { +mojom::AccountStatePtr LoggedOut() { + return mojom::AccountState::NewLoggedOut(mojom::LoggedOutState::New(nullptr)); +} + +mojom::AccountStatePtr LoggedOutWithVerification() { + return mojom::AccountState::NewLoggedOut( + mojom::LoggedOutState::New(mojom::LoggedOutVerification::New( + mojom::LoggedOutVerificationIntent::kRegistration))); +} + +mojom::AccountStatePtr LoggedIn(const std::string& email) { + return mojom::AccountState::NewLoggedIn( + mojom::LoggedInState::New(email, nullptr)); +} + const AuthenticationObserverTestCase* AuthenticationObserverLoggedOutToVerification() { static const base::NoDestructor kAuthenticationObserverLoggedOutToVerification( {.test_name = "authentication_observer_logged_out_to_verification", - .from = - mojom::AccountState::NewLoggedOut(mojom::LoggedOutState::New()), + .from = LoggedOut(), .action = AuthenticationObserverTestCase::StateAction:: kSwitchToVerification, - .to = mojom::AccountState::NewVerification( - mojom::VerificationState::New())}); + .to = LoggedOutWithVerification()}); return kAuthenticationObserverLoggedOutToVerification.get(); } @@ -164,12 +180,10 @@ AuthenticationObserverVerificationToLoggedIn() { static const base::NoDestructor kAuthenticationObserverVerificationToLoggedIn( {.test_name = "authentication_observer_verification_to_logged_in", - .from = mojom::AccountState::NewVerification( - mojom::VerificationState::New()), + .from = LoggedOutWithVerification(), .action = AuthenticationObserverTestCase::StateAction::kSwitchToLoggedIn, - .to = mojom::AccountState::NewLoggedIn( - mojom::LoggedInState::New(kEmailAddress))}); + .to = LoggedIn(kEmailAddress)}); return kAuthenticationObserverVerificationToLoggedIn.get(); } @@ -178,12 +192,10 @@ AuthenticationObserverLoggedInToLoggedOut() { static const base::NoDestructor kAuthenticationObserverLoggedInToLoggedOut( {.test_name = "authentication_observer_logged_in_to_logged_out", - .from = mojom::AccountState::NewLoggedIn( - mojom::LoggedInState::New(kEmailAddress)), + .from = LoggedIn(kEmailAddress), .action = AuthenticationObserverTestCase::StateAction::kSwitchToLoggedOut, - .to = mojom::AccountState::NewLoggedOut( - mojom::LoggedOutState::New())}); + .to = LoggedOut()}); return kAuthenticationObserverLoggedInToLoggedOut.get(); } @@ -192,12 +204,10 @@ AuthenticationObserverLoggedOutToLoggedIn() { static const base::NoDestructor kAuthenticationObserverLoggedOutToLoggedIn( {.test_name = "authentication_observer_logged_out_to_logged_in", - .from = - mojom::AccountState::NewLoggedOut(mojom::LoggedOutState::New()), + .from = LoggedOut(), .action = AuthenticationObserverTestCase::StateAction::kSwitchToLoggedIn, - .to = mojom::AccountState::NewLoggedIn( - mojom::LoggedInState::New(kEmailAddress))}); + .to = LoggedIn(kEmailAddress)}); return kAuthenticationObserverLoggedOutToLoggedIn.get(); } @@ -207,12 +217,10 @@ AuthenticationObserverLoggedInToLoggedInEmailChange() { kAuthenticationObserverLoggedInToLoggedInEmailChange( {.test_name = "authentication_observer_logged_in_to_logged_in_email_change", - .from = mojom::AccountState::NewLoggedIn( - mojom::LoggedInState::New(kEmailAddress)), + .from = LoggedIn(kEmailAddress), .action = AuthenticationObserverTestCase::StateAction::kUpdateEmailAddress, - .to = mojom::AccountState::NewLoggedIn( - mojom::LoggedInState::New("new_email"))}); + .to = LoggedIn("new_email")}); return kAuthenticationObserverLoggedInToLoggedInEmailChange.get(); } @@ -900,7 +908,14 @@ TEST_P(BraveAccountServiceRegisterFinalizeTest, if (const auto& test_case = CHECK_DEREF(this->GetParam()); test_case.mojo_expected.has_value()) { - EXPECT_EQ(pref_service_.GetString(prefs::kBraveAccountVerificationToken), + AccountStatePrefs account_state_prefs(pref_service_); + const auto state = account_state_prefs.GetAccountState(); + ASSERT_TRUE(state->is_logged_out()); + ASSERT_TRUE(state->get_logged_out()->verification); + EXPECT_EQ(state->get_logged_out()->verification->intent, + mojom::LoggedOutVerificationIntent::kRegistration); + EXPECT_EQ(account_state_prefs.GetVerificationToken( + mojom::LoggedOutVerificationIntent::kRegistration), test_case.encrypted_verification_token); } } @@ -932,8 +947,10 @@ struct RegisterVerifyTestCase { mojom::Authentication& authentication, base::OnceCallback callback) { if (!test_case.encrypted_verification_token.empty()) { - pref_service.SetString(prefs::kBraveAccountVerificationToken, - test_case.encrypted_verification_token); + AccountStatePrefs(pref_service) + .SetLoggedOutWithVerification( + test_case.encrypted_verification_token, + mojom::LoggedOutVerificationIntent::kRegistration); } authentication.RegisterVerify( @@ -941,28 +958,27 @@ struct RegisterVerifyTestCase { std::move(callback).Then(base::BindOnce( [](PrefService* pref_service, std::string initial_verification_token, bool success) { + AccountStatePrefs account_state_prefs(*pref_service); + const auto state = account_state_prefs.GetAccountState(); if (success) { - EXPECT_EQ( - pref_service->GetString(prefs::kBraveAccountEmailAddress), - kEmailAddress); - EXPECT_EQ(pref_service->GetString( - prefs::kBraveAccountAuthenticationToken), + ASSERT_TRUE(state->is_logged_in()); + EXPECT_EQ(state->get_logged_in()->email, kEmailAddress); + EXPECT_FALSE(state->get_logged_in()->verification); + EXPECT_EQ(account_state_prefs.GetAuthenticationToken(), EncryptedAuthenticationToken()); - EXPECT_TRUE( - pref_service - ->GetString(prefs::kBraveAccountVerificationToken) - .empty()); } else { - EXPECT_TRUE( - pref_service->GetString(prefs::kBraveAccountEmailAddress) - .empty()); - EXPECT_TRUE( - pref_service - ->GetString(prefs::kBraveAccountAuthenticationToken) - .empty()); - EXPECT_EQ(pref_service->GetString( - prefs::kBraveAccountVerificationToken), - initial_verification_token); + ASSERT_TRUE(state->is_logged_out()); + if (initial_verification_token.empty()) { + EXPECT_FALSE(state->get_logged_out()->verification); + } else { + ASSERT_TRUE(state->get_logged_out()->verification); + EXPECT_EQ(state->get_logged_out()->verification->intent, + mojom::LoggedOutVerificationIntent::kRegistration); + EXPECT_EQ( + account_state_prefs.GetVerificationToken( + mojom::LoggedOutVerificationIntent::kRegistration), + initial_verification_token); + } } }, base::Unretained(&pref_service), @@ -994,7 +1010,7 @@ const RegisterVerifyTestCase* RegisterVerifyVerificationTokenEmpty() { base::unexpected(mojom::RegisterError::NewClientError( mojom::RegisterClientError::New( mojom::RegisterClientErrorCode:: - kUserNotInTheVerificationState))), + kNoRegistrationInProgress))), }); return kRegisterVerifyVerificationTokenEmpty.get(); } @@ -1353,8 +1369,10 @@ struct ResendConfirmationEmailTestCase { mojom::Authentication& authentication, base::OnceCallback callback) { if (!test_case.encrypted_verification_token.empty()) { - pref_service.SetString(prefs::kBraveAccountVerificationToken, - test_case.encrypted_verification_token); + AccountStatePrefs(pref_service) + .SetLoggedOutWithVerification( + test_case.encrypted_verification_token, + mojom::LoggedOutVerificationIntent::kRegistration); } authentication.ResendConfirmationEmail(std::move(callback)); @@ -1381,7 +1399,7 @@ ResendConfirmationEmailVerificationTokenEmpty() { mojom::ResendConfirmationEmailError::NewClientError( mojom::ResendConfirmationEmailClientError::New( mojom::ResendConfirmationEmailClientErrorCode:: - kUserNotInTheVerificationState))), + kNoRegistrationInProgress))), }); return kResendConfirmationEmailVerificationTokenEmpty.get(); } @@ -1609,19 +1627,24 @@ struct AuthValidateTestCase { PrefService& pref_service, base::test::TaskEnvironment& task_environment, base::OneShotTimer& auth_validate_timer) { + AccountStatePrefs account_state_prefs(pref_service); if (test_case.logged_in) { - pref_service.SetString(prefs::kBraveAccountEmailAddress, kEmailAddress); - pref_service.SetString(prefs::kBraveAccountAuthenticationToken, - EncryptedAuthenticationToken()); + account_state_prefs.SetLoggedIn(kEmailAddress, + EncryptedAuthenticationToken()); } task_environment.FastForwardBy(kAuthValidatePollInterval - base::Seconds(1)); - EXPECT_EQ(pref_service.GetString(prefs::kBraveAccountEmailAddress), - test_case.expected_email); - EXPECT_EQ(pref_service.GetString(prefs::kBraveAccountAuthenticationToken), - test_case.expected_authentication_token); + const auto state = account_state_prefs.GetAccountState(); + if (test_case.expected_authentication_token.empty()) { + EXPECT_TRUE(state->is_logged_out()); + } else { + ASSERT_TRUE(state->is_logged_in()); + EXPECT_EQ(state->get_logged_in()->email, test_case.expected_email); + EXPECT_EQ(account_state_prefs.GetAuthenticationToken(), + test_case.expected_authentication_token); + } if (test_case.expected_auth_validate_timer_delay.is_zero()) { EXPECT_FALSE(auth_validate_timer.IsRunning()); } else { @@ -1813,16 +1836,20 @@ struct CancelRegistrationTestCase { static void Run(const CancelRegistrationTestCase& test_case, PrefService& pref_service, mojom::Authentication& authentication) { - pref_service.SetString(prefs::kBraveAccountVerificationToken, - test_case.encrypted_verification_token); + AccountStatePrefs account_state_prefs(pref_service); + if (!test_case.encrypted_verification_token.empty()) { + account_state_prefs.SetLoggedOutWithVerification( + test_case.encrypted_verification_token, + mojom::LoggedOutVerificationIntent::kRegistration); + } authentication.CancelRegistration(); - EXPECT_EQ(pref_service.GetString(prefs::kBraveAccountVerificationToken), - test_case.expected_verification_token); + const auto state = account_state_prefs.GetAccountState(); + ASSERT_TRUE(state->is_logged_out()); + EXPECT_FALSE(state->get_logged_out()->verification); } std::string test_name; std::string encrypted_verification_token; - std::string expected_verification_token; }; namespace { @@ -1832,7 +1859,6 @@ const CancelRegistrationTestCase* CancelRegistrationVerificationTokenEmpty() { kCancelRegistrationVerificationTokenEmpty({ .test_name = "cancel_registration_verification_token_empty", .encrypted_verification_token = "", - .expected_verification_token = "", }); return kCancelRegistrationVerificationTokenEmpty.get(); } @@ -1843,7 +1869,6 @@ CancelRegistrationVerificationTokenNonEmpty() { kCancelRegistrationVerificationTokenNonEmpty({ .test_name = "cancel_registration_verification_token_non_empty", .encrypted_verification_token = EncryptedVerificationToken(), - .expected_verification_token = "", }); return kCancelRegistrationVerificationTokenNonEmpty.get(); } @@ -1869,19 +1894,20 @@ struct LogOutTestCase { static void Run(const LogOutTestCase& test_case, PrefService& pref_service, mojom::Authentication& authentication) { - pref_service.SetString(prefs::kBraveAccountEmailAddress, - test_case.email_address); - pref_service.SetString(prefs::kBraveAccountAuthenticationToken, - test_case.encrypted_authentication_token); + AccountStatePrefs account_state_prefs(pref_service); + if (!test_case.encrypted_authentication_token.empty()) { + account_state_prefs.SetLoggedIn(test_case.email_address, + test_case.encrypted_authentication_token); + } authentication.LogOut(); - EXPECT_EQ(pref_service.GetString(prefs::kBraveAccountAuthenticationToken), - test_case.expected_authentication_token); + const auto state = account_state_prefs.GetAccountState(); + ASSERT_TRUE(state->is_logged_out()); + EXPECT_FALSE(state->get_logged_out()->verification); } std::string test_name; std::string email_address; std::string encrypted_authentication_token; - std::string expected_authentication_token; }; namespace { @@ -1892,7 +1918,6 @@ const LogOutTestCase* LogOutAuthenticationTokenEmpty() { .test_name = "log_out_authentication_token_empty", .email_address = "", .encrypted_authentication_token = "", - .expected_authentication_token = "", }); return kLogOutAuthenticationTokenEmpty.get(); } @@ -1903,7 +1928,6 @@ const LogOutTestCase* LogOutAuthenticationTokenNonEmpty() { .test_name = "log_out_authentication_token_non_empty", .email_address = kEmailAddress, .encrypted_authentication_token = EncryptedAuthenticationToken(), - .expected_authentication_token = "", }); return kLogOutAuthenticationTokenNonEmpty.get(); } @@ -2267,12 +2291,16 @@ struct LoginFinalizeTestCase { std::move(callback).Then(base::BindOnce( [](PrefService* pref_service, std::string expected_email, std::string expected_authentication_token) { - EXPECT_EQ( - pref_service->GetString(prefs::kBraveAccountEmailAddress), - expected_email); - EXPECT_EQ(pref_service->GetString( - prefs::kBraveAccountAuthenticationToken), - expected_authentication_token); + AccountStatePrefs account_state_prefs(*pref_service); + const auto state = account_state_prefs.GetAccountState(); + if (expected_authentication_token.empty()) { + EXPECT_TRUE(state->is_logged_out()); + } else { + ASSERT_TRUE(state->is_logged_in()); + EXPECT_EQ(state->get_logged_in()->email, expected_email); + EXPECT_EQ(account_state_prefs.GetAuthenticationToken(), + expected_authentication_token); + } }, base::Unretained(&pref_service), test_case.expected_email, test_case.expected_authentication_token))); @@ -2701,14 +2729,13 @@ struct GetServiceTokenTestCase { base::test::TaskEnvironment& task_environment, mojom::Authentication& authentication, base::OnceCallback callback) { - pref_service.SetDict( - prefs::kBraveAccountServiceTokens, - std::move(test_case.service_tokens_dict).Run(base::Time::Now())); - if (test_case.logged_in) { - pref_service.SetString(prefs::kBraveAccountEmailAddress, kEmailAddress); - pref_service.SetString(prefs::kBraveAccountAuthenticationToken, - EncryptedAuthenticationToken()); + AccountStatePrefs(pref_service) + .SetLoggedIn(kEmailAddress, EncryptedAuthenticationToken()); + ScopedDictPrefUpdate(&pref_service, prefs::kBraveAccountState) + ->Set( + prefs::keys::kServiceTokens, + std::move(test_case.service_tokens_dict).Run(base::Time::Now())); } task_environment.FastForwardBy(test_case.time_advance); @@ -2726,13 +2753,16 @@ struct GetServiceTokenTestCase { std::move(callback).Then(base::BindOnce( [](PrefService* pref_service, std::string expected_service_token) { if (!expected_service_token.empty()) { + const auto* service_tokens = + pref_service->GetDict(prefs::kBraveAccountState) + .FindDict(prefs::keys::kServiceTokens); + ASSERT_TRUE(service_tokens); const auto* email_aliases = - pref_service->GetDict(prefs::kBraveAccountServiceTokens) - .FindDict("email-aliases"); - EXPECT_TRUE(email_aliases); + service_tokens->FindDict("email-aliases"); + ASSERT_TRUE(email_aliases); const auto* service_token = email_aliases->FindString(prefs::keys::kServiceToken); - EXPECT_TRUE(service_token); + ASSERT_TRUE(service_token); EXPECT_EQ(*service_token, base::Base64Encode(expected_service_token)); } @@ -2744,7 +2774,7 @@ struct GetServiceTokenTestCase { // be processed on the next message pump iteration, so this runs before the // request completes. if (test_case.clear_authentication_token) { - pref_service.ClearPref(prefs::kBraveAccountAuthenticationToken); + AccountStatePrefs(pref_service).SetLoggedOut(); } } @@ -2778,7 +2808,7 @@ const GetServiceTokenTestCase* GetServiceTokenCacheHit() { .Set(prefs::keys::kLastFetched, base::TimeToValue(mock_now))); }), - .logged_in = {}, // not used + .logged_in = true, .fail_decryption = {}, // not used .clear_authentication_token = {}, // not used .fail_encryption = {}, // not used diff --git a/components/brave_account/brave_account_state_prefs.cc b/components/brave_account/brave_account_state_prefs.cc new file mode 100644 index 00000000000..3991cfccafa --- /dev/null +++ b/components/brave_account/brave_account_state_prefs.cc @@ -0,0 +1,159 @@ +/* Copyright (c) 2026 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_account/brave_account_state_prefs.h" + +#include + +#include "base/check.h" +#include "base/json/values_util.h" +#include "base/time/time.h" +#include "base/values.h" +#include "brave/components/brave_account/brave_account_service_constants.h" +#include "components/prefs/scoped_user_pref_update.h" + +namespace brave_account { + +namespace { + +template +auto MakeVerification(std::optional verification_intent) { + VerificationPtr verification; + + if (verification_intent) { + if (const auto intent = + static_castintent)>(*verification_intent); + mojom::IsKnownEnumValue(intent)) { + verification = VerificationPtr::Struct::New(intent); + } + } + + return verification; +} + +} // namespace + +void AccountStatePrefs::StartObserving(base::RepeatingClosure on_change) { + pref_change_registrar_.Init(&*pref_service_); + pref_change_registrar_.Add(prefs::kBraveAccountState, std::move(on_change)); +} + +void AccountStatePrefs::SetLoggedOut() { + pref_service_->SetDict(prefs::kBraveAccountState, + base::DictValue().Set(prefs::keys::kKind, + prefs::state_kinds::kLoggedOut)); +} + +void AccountStatePrefs::SetLoggedOutWithVerification( + const std::string& encrypted_verification_token, + mojom::LoggedOutVerificationIntent intent) { + CHECK(!encrypted_verification_token.empty()); + + pref_service_->SetDict( + prefs::kBraveAccountState, + base::DictValue() + .Set(prefs::keys::kKind, prefs::state_kinds::kLoggedOut) + .Set(prefs::keys::kVerification, + base::DictValue() + .Set(prefs::keys::kVerificationToken, + encrypted_verification_token) + .Set(prefs::keys::kVerificationIntent, + static_cast(intent)))); +} + +void AccountStatePrefs::SetLoggedIn( + const std::string& email, + const std::string& encrypted_authentication_token) { + CHECK(!email.empty()); + CHECK(!encrypted_authentication_token.empty()); + + pref_service_->SetDict( + prefs::kBraveAccountState, + base::DictValue() + .Set(prefs::keys::kKind, prefs::state_kinds::kLoggedIn) + .Set(prefs::keys::kEmail, email) + .Set(prefs::keys::kAuthenticationToken, + encrypted_authentication_token)); +} + +mojom::AccountStatePtr AccountStatePrefs::GetAccountState() const { + const auto& account_state = pref_service_->GetDict(prefs::kBraveAccountState); + const auto* kind = account_state.FindString(prefs::keys::kKind); + const auto* verification = account_state.FindDict(prefs::keys::kVerification); + const auto intent = + verification ? verification->FindInt(prefs::keys::kVerificationIntent) + : std::nullopt; + + if (kind && *kind == prefs::state_kinds::kLoggedIn) { + const auto* email = account_state.FindString(prefs::keys::kEmail); + CHECK(email && !email->empty()); + return mojom::AccountState::NewLoggedIn(mojom::LoggedInState::New( + *email, MakeVerification(intent))); + } + + return mojom::AccountState::NewLoggedOut(mojom::LoggedOutState::New( + MakeVerification(intent))); +} + +std::string AccountStatePrefs::GetAuthenticationToken() const { + const auto* token = pref_service_->GetDict(prefs::kBraveAccountState) + .FindString(prefs::keys::kAuthenticationToken); + return token ? *token : ""; +} + +std::string AccountStatePrefs::GetCachedServiceToken( + const std::string& service_name) const { + CHECK(!service_name.empty()); + + const auto* service_tokens = pref_service_->GetDict(prefs::kBraveAccountState) + .FindDict(prefs::keys::kServiceTokens); + const auto* service = + service_tokens ? service_tokens->FindDict(service_name) : nullptr; + if (!service) { + return ""; + } + + const auto* encrypted_service_token = + service->FindString(prefs::keys::kServiceToken); + const auto* last_fetched_value = service->Find(prefs::keys::kLastFetched); + + if (!encrypted_service_token || !last_fetched_value) { + return ""; + } + + const auto last_fetched_time = base::ValueToTime(*last_fetched_value); + if (!last_fetched_time) { + return ""; + } + + if (base::Time::Now() - *last_fetched_time >= kServiceTokenMaxAge) { + return ""; + } + + return *encrypted_service_token; +} + +void AccountStatePrefs::UpdateEmail(const std::string& email) { + CHECK(!email.empty()); + + ScopedDictPrefUpdate(&*pref_service_, prefs::kBraveAccountState) + ->Set(prefs::keys::kEmail, email); +} + +void AccountStatePrefs::CacheServiceToken(const std::string& service_name, + std::string encrypted_service_token) { + CHECK(!service_name.empty()); + CHECK(!encrypted_service_token.empty()); + + ScopedDictPrefUpdate(&*pref_service_, prefs::kBraveAccountState) + ->EnsureDict(prefs::keys::kServiceTokens) + ->Set(service_name, base::DictValue() + .Set(prefs::keys::kServiceToken, + std::move(encrypted_service_token)) + .Set(prefs::keys::kLastFetched, + base::TimeToValue(base::Time::Now()))); +} + +} // namespace brave_account diff --git a/components/brave_account/brave_account_state_prefs.h b/components/brave_account/brave_account_state_prefs.h new file mode 100644 index 00000000000..5132f0cd2d2 --- /dev/null +++ b/components/brave_account/brave_account_state_prefs.h @@ -0,0 +1,91 @@ +/* Copyright (c) 2026 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_ACCOUNT_BRAVE_ACCOUNT_STATE_PREFS_H_ +#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_BRAVE_ACCOUNT_STATE_PREFS_H_ + +#include +#include +#include + +#include "base/check_deref.h" +#include "base/functional/callback.h" +#include "base/memory/raw_ref.h" +#include "brave/components/brave_account/mojom/brave_account.mojom.h" +#include "brave/components/brave_account/pref_names.h" +#include "components/prefs/pref_change_registrar.h" +#include "components/prefs/pref_service.h" + +namespace brave_account { + +// Typed view onto the kBraveAccountState pref dict. +// Encapsulates the storage schema so callers (the service, tests) +// don't have to know how the dict is laid out. +class AccountStatePrefs { + public: + explicit AccountStatePrefs(PrefService& pref_service) + : pref_service_(pref_service) {} + + AccountStatePrefs(const AccountStatePrefs&) = delete; + AccountStatePrefs& operator=(const AccountStatePrefs&) = delete; + + void StartObserving(base::RepeatingClosure on_change); + + void SetLoggedOut(); + + void SetLoggedOutWithVerification( + const std::string& encrypted_verification_token, + mojom::LoggedOutVerificationIntent intent); + + void SetLoggedIn(const std::string& email, + const std::string& encrypted_authentication_token); + + mojom::AccountStatePtr GetAccountState() const; + + std::string GetAuthenticationToken() const; + + template + requires std::same_as || + std::same_as + std::string GetVerificationToken(Intent intent) const { + const auto verification = [&] { + const auto account_state = GetAccountState(); + if constexpr (std::same_as) { + return account_state->is_logged_out() + ? std::move(account_state->get_logged_out()->verification) + : nullptr; + } else { + return account_state->is_logged_in() + ? std::move(account_state->get_logged_in()->verification) + : nullptr; + } + }(); + + if (!verification || verification->intent != intent) { + return ""; + } + + const auto* token = + CHECK_DEREF(pref_service_->GetDict(prefs::kBraveAccountState) + .FindDict(prefs::keys::kVerification)) + .FindString(prefs::keys::kVerificationToken); + return token ? *token : ""; + } + + std::string GetCachedServiceToken(const std::string& service_name) const; + + void UpdateEmail(const std::string& email); + + void CacheServiceToken(const std::string& service_name, + std::string encrypted_service_token); + + private: + const raw_ref pref_service_; + PrefChangeRegistrar pref_change_registrar_; +}; + +} // namespace brave_account + +#endif // BRAVE_COMPONENTS_BRAVE_ACCOUNT_BRAVE_ACCOUNT_STATE_PREFS_H_ diff --git a/components/brave_account/mojom/brave_account.mojom b/components/brave_account/mojom/brave_account.mojom index 1ebf5b50898..2fc2687b47c 100644 --- a/components/brave_account/mojom/brave_account.mojom +++ b/components/brave_account/mojom/brave_account.mojom @@ -5,17 +5,40 @@ module brave_account.mojom; -struct LoggedOutState {}; +// These enum values are persisted to Preferences. Never reorder, repurpose, +// or recycle a value — only append new ones with the next unused number. +// When removing an enumerator, leave its number reserved as a comment. +enum LoggedOutVerificationIntent { + kRegistration = 0, + kResetPassword = 1, +}; -struct VerificationState {}; +// These enum values are persisted to Preferences. Never reorder, repurpose, +// or recycle a value — only append new ones with the next unused number. +// When removing an enumerator, leave its number reserved as a comment. +enum LoggedInVerificationIntent { + kChangePassword = 0, +}; + +struct LoggedOutVerification { + LoggedOutVerificationIntent intent; +}; + +struct LoggedInVerification { + LoggedInVerificationIntent intent; +}; + +struct LoggedOutState { + LoggedOutVerification? verification; +}; struct LoggedInState { string email; + LoggedInVerification? verification; }; union AccountState { LoggedOutState logged_out; - VerificationState verification; LoggedInState logged_in; }; @@ -39,7 +62,7 @@ enum RegisterClientErrorCode { kOpaqueError, kVerificationTokenEncryptionFailed, kVerificationTokenDecryptionFailed, - kUserNotInTheVerificationState, + kNoRegistrationInProgress, kAuthenticationTokenEncryptionFailed, }; @@ -158,7 +181,7 @@ struct ResendConfirmationEmailResult { enum ResendConfirmationEmailClientErrorCode { kUnexpected, - kUserNotInTheVerificationState, + kNoRegistrationInProgress, kVerificationTokenDecryptionFailed, }; diff --git a/components/brave_account/pref_names.h b/components/brave_account/pref_names.h index 09459f0b99d..937bb958779 100644 --- a/components/brave_account/pref_names.h +++ b/components/brave_account/pref_names.h @@ -8,57 +8,102 @@ namespace brave_account::prefs { -// This preference will store (`OSCrypt`-encrypted) the `verificationToken` -// returned by the POST /v2/accounts/password/init endpoint. -// It is then used for: -// - calling POST /v2/accounts/password/finalize -// to complete the registration flow -// - polling POST /v2/verify/result -// to exchange the `verificationToken` for the `authToken` -// after the user has verified their email -inline constexpr char kBraveAccountVerificationToken[] = - "brave.account.verification_token"; - -// This preference will store (`OSCrypt`-encrypted) the (JWT) `authToken` -// returned by the POST /v2/verify/result endpoint. -// It is then used for: -// - authenticating subsequent Brave Account requests -// - creating new service tokens -inline constexpr char kBraveAccountAuthenticationToken[] = - "brave.account.authentication_token"; - -// This preference will store the email address of the authenticated user -// returned by the GET /v2/auth/validate endpoint. -inline constexpr char kBraveAccountEmailAddress[] = - "brave.account.email_address"; - -// This preference will store a dictionary of service tokens with their -// fetch times. Each entry maps a service name to a dict containing: -// - "service_token": the service token (`OSCrypt`-encrypted) -// - "last_fetched": when the service token was retrieved (base::Time as -// base::Value via base::TimeToValue()) +// This preference stores the entire account state as a single dictionary, +// keyed by "kind" ("logged-out" or "logged-in"). All state transitions write +// the whole dictionary in one shot, so observers see one consistent state per +// notification. // -// Example: -// ... -// "service_tokens": { -// "email-aliases": { -// "last_fetched": "...", -// "service_token": "..." -// }, -// ... +// First launch, no Brave Account ever used — equivalent to logged-out: +// The "account" dict in Preferences doesn't exist. +// +// Registration in progress — logged-out, with verification: +// "account": { +// "state": { +// "kind": "logged-out", +// "verification": { +// "intent": 0, +// "token": "..." +// } +// } // }, -// ... -inline constexpr char kBraveAccountServiceTokens[] = - "brave.account.service_tokens"; +// +// Email verified, or returning user logged in — logged-in, +// optionally including service tokens: +// "account": { +// "state": { +// "authentication_token": "...", +// "email": "...", +// "kind": "logged-in", +// "service_tokens": { +// "email-aliases": { +// "last_fetched": "13422130435353472", +// "service_token": "..." +// } +// } +// } +// }, +// +// Password change in progress — logged-in, with verification: +// "account": { +// "state": { +// "authentication_token": "...", +// "email": "...", +// "kind": "logged-in" +// "verification": { +// "intent": 0, +// "token": "..." +// } +// } +// }, +// +// Logged out — logged-out: +// "account": { +// "state": { +// "kind": "logged-out" +// } +// }, +// +// Password reset in progress — logged-out, with verification: +// "account": { +// "state": { +// "kind": "logged-out", +// "verification": { +// "intent": 1, +// "token": "..." +// } +// } +// }, +inline constexpr char kBraveAccountState[] = "brave.account.state"; namespace keys { -// Dictionary keys used within kBraveAccountServiceTokens. -inline constexpr char kServiceToken[] = "service_token"; +// Top-level dictionary keys used within kBraveAccountState. +inline constexpr char kAuthenticationToken[] = "authentication_token"; +inline constexpr char kEmail[] = "email"; +inline constexpr char kKind[] = "kind"; +inline constexpr char kServiceTokens[] = "service_tokens"; +inline constexpr char kVerification[] = "verification"; + +// Dictionary keys used within the "verification" sub-dict of +// kBraveAccountState. +inline constexpr char kVerificationIntent[] = "intent"; +inline constexpr char kVerificationToken[] = "token"; + +// Dictionary keys used within entries of the "service_tokens" sub-dict of +// kBraveAccountState. inline constexpr char kLastFetched[] = "last_fetched"; +inline constexpr char kServiceToken[] = "service_token"; } // namespace keys +namespace state_kinds { + +// Values for the kKind field within kBraveAccountState. +inline constexpr char kLoggedIn[] = "logged-in"; +inline constexpr char kLoggedOut[] = "logged-out"; + +} // namespace state_kinds + } // namespace brave_account::prefs #endif // BRAVE_COMPONENTS_BRAVE_ACCOUNT_PREF_NAMES_H_ diff --git a/components/brave_account/prefs.cc b/components/brave_account/prefs.cc index dd543f2662f..935f7bb0a89 100644 --- a/components/brave_account/prefs.cc +++ b/components/brave_account/prefs.cc @@ -5,8 +5,6 @@ #include "brave/components/brave_account/prefs.h" -#include - #include "base/values.h" #include "brave/components/brave_account/features.h" #include "brave/components/brave_account/pref_names.h" @@ -16,12 +14,7 @@ namespace brave_account::prefs { void RegisterPrefs(PrefRegistrySimple* registry) { if (features::IsBraveAccountEnabled()) { - registry->RegisterStringPref(kBraveAccountVerificationToken, std::string()); - registry->RegisterStringPref(kBraveAccountAuthenticationToken, - std::string()); - registry->RegisterStringPref(kBraveAccountEmailAddress, std::string()); - registry->RegisterDictionaryPref(kBraveAccountServiceTokens, - base::DictValue()); + registry->RegisterDictionaryPref(kBraveAccountState, base::DictValue()); } } diff --git a/components/brave_account/resources/brave_account_dialogs.ts b/components/brave_account/resources/brave_account_dialogs.ts index e55848d4466..b4ad3454119 100644 --- a/components/brave_account/resources/brave_account_dialogs.ts +++ b/components/brave_account/resources/brave_account_dialogs.ts @@ -68,8 +68,8 @@ export class BraveAccountDialogsElement extends CrLitElement { // // Handle account state changes. - // LOGGED_OUT: show the ENTRY dialog - // VERIFICATION: show the OTP dialog + // LOGGED_OUT (no verification): show the ENTRY dialog + // LOGGED_OUT (with verification): show the OTP dialog // LOGGED_IN: close the native dialog // Since account state is profile-wide, this automatically updates dialogs // across all tabs. @@ -78,10 +78,7 @@ export class BraveAccountDialogsElement extends CrLitElement { (state: AccountState) => { switch (whichAccountState(state)) { case AccountStateFieldTags.LOGGED_OUT: - this.dialog = 'ENTRY' - break - case AccountStateFieldTags.VERIFICATION: - this.dialog = 'OTP' + this.dialog = state.loggedOut!.verification ? 'OTP' : 'ENTRY' break case AccountStateFieldTags.LOGGED_IN: this.onCloseDialog() diff --git a/components/email_aliases/BUILD.gn b/components/email_aliases/BUILD.gn index e2a39c9f579..ecd3441fa50 100644 --- a/components/email_aliases/BUILD.gn +++ b/components/email_aliases/BUILD.gn @@ -53,6 +53,7 @@ static_library("service") { "//base", "//brave/brave_domains", "//brave/components/brave_account", + "//brave/components/brave_account:internal", "//brave/components/brave_account/endpoint_client", "//brave/components/brave_account/mojom", "//brave/components/constants", diff --git a/components/email_aliases/email_aliases_auth.cc b/components/email_aliases/email_aliases_auth.cc index 865361e01d5..c2498df9782 100644 --- a/components/email_aliases/email_aliases_auth.cc +++ b/components/email_aliases/email_aliases_auth.cc @@ -28,17 +28,10 @@ EmailAliasesAuth::EmailAliasesAuth( base::Unretained( this))); // Unretained is safe because we own the remote<> + // TODO(https://github.com/brave/brave-browser/issues/55179) pref_change_registrar_.Init(&prefs_service_.get()); pref_change_registrar_.Add( - brave_account::prefs::kBraveAccountServiceTokens, - base::BindRepeating(&EmailAliasesAuth::OnPrefChanged, - base::Unretained(this))); - pref_change_registrar_.Add( - brave_account::prefs::kBraveAccountAuthenticationToken, - base::BindRepeating(&EmailAliasesAuth::OnPrefChanged, - base::Unretained(this))); - pref_change_registrar_.Add( - brave_account::prefs::kBraveAccountEmailAddress, + brave_account::prefs::kBraveAccountState, base::BindRepeating(&EmailAliasesAuth::OnPrefChanged, base::Unretained(this))); } @@ -54,8 +47,10 @@ std::string EmailAliasesAuth::GetAuthEmail() const { CHECK_IS_TEST(); return auth_email_for_testing_.value(); } - return prefs_service_->GetString( - brave_account::prefs::kBraveAccountEmailAddress); + const auto* email = + prefs_service_->GetDict(brave_account::prefs::kBraveAccountState) + .FindString(brave_account::prefs::keys::kEmail); + return email ? *email : ""; } void EmailAliasesAuth::GetServiceToken( diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift index 818d576ebc4..7032706ab78 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift @@ -447,7 +447,7 @@ class SettingsViewController: TableViewController, BraveAccountAuthenticationObs ), ] ) - case .verification: + case .loggedOut where braveAccountState.loggedOut!.verification != nil: return Static.Section( header: .title(L10nUtils.string(messageId: .BRAVE_ACCOUNT_TITLE)), rows: [