Account: collapse 3-state model to 2 states with verification intent (#36111)

This commit is contained in:
Szilard Szaloki
2026-05-06 21:54:05 +01:00
committed by GitHub
parent e9fd22c534
commit b07823bc8f
16 changed files with 631 additions and 382 deletions
@@ -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);
@@ -182,60 +182,62 @@ export class SettingsBraveAccountRowElement extends I18nMixinLit(CrLitElement) {
</leo-button>
`
),
[AccountStateFieldTags.VERIFICATION]: () => html`
${this.createFirstRow(
this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_TITLE),
[
html`<localized-link
.localizedString=${`${
this.i18n(BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_DESCRIPTION_1)} ${
this.i18n(BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_DESCRIPTION_2)} ${
this.i18nAdvanced(BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_DESCRIPTION_3,
{tags: ['a'], attrs: ['href']})}`}
@link-clicked=${this.onResendConfirmationEmailLinkClicked}>
</localized-link>`
]
)}
<div class="second-row">
<leo-button kind="plain"
size="small"
@click=${this.openBraveAccountDialog}>
${this.i18n(
[AccountStateFieldTags.LOGGED_OUT]: () =>
this.state!.loggedOut!.verification
? html`
${this.createFirstRow(
this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_TITLE),
[
html`<localized-link
.localizedString=${`${
this.i18n(BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_DESCRIPTION_1)} ${
this.i18n(BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_DESCRIPTION_2)} ${
this.i18nAdvanced(BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_VERIFICATION_ROW_DESCRIPTION_3,
{tags: ['a'], attrs: ['href']})}`}
@link-clicked=${this.onResendConfirmationEmailLinkClicked}>
</localized-link>`
]
)}
<div class="second-row">
<leo-button kind="plain"
size="small"
@click=${this.openBraveAccountDialog}>
${this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_ENTER_REGISTRATION_CODE_BUTTON_LABEL)}
</leo-button>
<leo-button kind="plain"
size="small"
class="cancel-registration-button"
@click=${this.onCancelRegistrationButtonClicked}>
${this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_CANCEL_REGISTRATION_BUTTON_LABEL)}
</leo-button>
</div>
`
: this.createFirstRow(
this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_ENTER_REGISTRATION_CODE_BUTTON_LABEL)}
</leo-button>
<leo-button kind="plain"
size="small"
class="cancel-registration-button"
@click=${this.onCancelRegistrationButtonClicked}>
${this.i18n(
.SETTINGS_BRAVE_ACCOUNT_LOGGED_OUT_ROW_TITLE),
[this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_CANCEL_REGISTRATION_BUTTON_LABEL)}
</leo-button>
</div>
`,
[AccountStateFieldTags.LOGGED_OUT]: () => this.createFirstRow(
this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_LOGGED_OUT_ROW_TITLE),
[this.i18n(
BraveAccountSettingsStrings
.BRAVE_ACCOUNT_DESCRIPTION)],
html`
<leo-button kind="filled"
size="small"
@click=${this.openBraveAccountDialog}>
${this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_GET_STARTED_BUTTON_LABEL)}
</leo-button>
`
),
.BRAVE_ACCOUNT_DESCRIPTION)],
html`
<leo-button kind="filled"
size="small"
@click=${this.openBraveAccountDialog}>
${this.i18n(
BraveAccountSettingsStrings
.SETTINGS_BRAVE_ACCOUNT_GET_STARTED_BUTTON_LABEL)}
</leo-button>
`
),
}
return this.state === undefined
+22 -2
View File
@@ -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",
+41 -133
View File
@@ -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<network::SharedURLLoaderFactory> 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<mojom::AuthenticationObserver> 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::RegisterError>(
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::ResendConfirmationEmailError>(
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<mojom::GetServiceTokenError>(
@@ -513,8 +501,9 @@ void BraveAccountService::OnRegisterFinalize(
.and_then([&](auto success_body)
-> base::expected<mojom::RegisterFinalizeResultPtr,
mojom::RegisterErrorPtr> {
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();
@@ -11,11 +11,11 @@
#include <vector>
#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<PrefService> pref_service_;
AccountStatePrefs account_state_prefs_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
std::optional<os_crypt_async::Encryptor> encryptor_;
std::vector<mojo::PendingReceiver<mojom::Authentication>> pending_receivers_;
mojo::ReceiverSet<mojom::Authentication> authentication_receivers_;
mojo::RemoteSet<mojom::AuthenticationObserver> observers_;
StringPrefMember pref_verification_token_;
StringPrefMember pref_authentication_token_;
StringPrefMember pref_email_address_;
base::OneShotTimer auth_validate_timer_;
base::WeakPtrFactory<BraveAccountService> weak_factory_{this};
};
@@ -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);
@@ -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<AuthenticationObserverTestCase>
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<AuthenticationObserverTestCase>
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<AuthenticationObserverTestCase>
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<AuthenticationObserverTestCase>
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<void(MojoExpected)> 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<void(MojoExpected)> 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<void(MojoExpected)> 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
@@ -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 <optional>
#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 <typename VerificationPtr>
auto MakeVerification(std::optional<int> verification_intent) {
VerificationPtr verification;
if (verification_intent) {
if (const auto intent =
static_cast<decltype(verification->intent)>(*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<int>(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<mojom::LoggedInVerificationPtr>(intent)));
}
return mojom::AccountState::NewLoggedOut(mojom::LoggedOutState::New(
MakeVerification<mojom::LoggedOutVerificationPtr>(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
@@ -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 <concepts>
#include <string>
#include <utility>
#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 <typename Intent>
requires std::same_as<Intent, mojom::LoggedOutVerificationIntent> ||
std::same_as<Intent, mojom::LoggedInVerificationIntent>
std::string GetVerificationToken(Intent intent) const {
const auto verification = [&] {
const auto account_state = GetAccountState();
if constexpr (std::same_as<Intent, mojom::LoggedOutVerificationIntent>) {
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<PrefService> pref_service_;
PrefChangeRegistrar pref_change_registrar_;
};
} // namespace brave_account
#endif // BRAVE_COMPONENTS_BRAVE_ACCOUNT_BRAVE_ACCOUNT_STATE_PREFS_H_
@@ -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,
};
+87 -42
View File
@@ -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_
+1 -8
View File
@@ -5,8 +5,6 @@
#include "brave/components/brave_account/prefs.h"
#include <string>
#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());
}
}
@@ -68,8 +68,8 @@ export class BraveAccountDialogsElement extends CrLitElement {
// </if>
// 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()
+1
View File
@@ -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",
+6 -11
View File
@@ -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(
@@ -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: [