Skip to content

Commit

Permalink
Add new purchase state OUT_OF_CREDENTIALS for VPN service
Browse files Browse the repository at this point in the history
This handles the condition where the all the credentials for a given day
are redeemed and there are none left. This shouldn't happen - but can
happen if there are network issues. Specifically, when the client doesn't
receive response from server after redeeming the credential.

Fixes brave/brave-browser#33031
  • Loading branch information
bsclifton committed Oct 28, 2024
1 parent 8fbee3c commit 3245b05
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ void AddLocalizedStrings(content::WebUIDataSource* html_source) {
{"braveVpnSettingsTooltip", IDS_BRAVE_VPN_MAIN_PANEL_VPN_SETTINGS_TITLE},
{"braveVpnSessionExpiredContent",
IDS_BRAVE_VPN_MAIN_PANEL_SESSION_EXPIRED_PART_CONTENT},
{"braveVpnOutOfCredentials",
IDS_BRAVE_VPN_MAIN_PANEL_OUT_OF_CREDENTIALS_TITLE},
};

for (const auto& str : kLocalizedStrings) {
Expand Down
61 changes: 45 additions & 16 deletions components/brave_vpn/browser/brave_vpn_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,33 @@ void BraveVpnService::UpdatePurchasedStateForSessionExpired(
return;
}

// Person ran out of credentials.
// This should only happen if communication bewteen client and VPN provider
// is lost after the credential is redeemed (multiple times).
if (out_of_credentials_) {
const auto last_credential_expiry =
local_prefs_->GetTime(prefs::kBraveVPNLastCredentialExpiry);
if (!last_credential_expiry.is_null()) {
std::stringstream ss;
base::TimeDelta delta = (last_credential_expiry - base::Time::Now());
ss << "Out of credentials; check again in ";
if (delta.InHours() == 0) {
ss << delta.InMinutes() << " minutes.";
} else {
int delta_hours = delta.InHours();
ss << delta.InHours() << " hours ";
base::TimeDelta delta2 = (delta - base::Hours(delta_hours));
ss << delta2.InMinutes() << " minutes.";
}
VLOG(2) << __func__ << " : " << ss.str();
SetPurchasedState(
env, PurchasedState::OUT_OF_CREDENTIALS,
l10n_util::GetStringUTF8(
IDS_BRAVE_VPN_MAIN_PANEL_OUT_OF_CREDENTIALS_CONTENT));
return;
}
}

// Weird state. Maybe we don't see this condition.
// Just checking for safe.
if (session_expired_time > base::Time::Now()) {
Expand Down Expand Up @@ -619,6 +646,7 @@ void BraveVpnService::OnCredentialSummary(const std::string& domain,
VLOG(1) << __func__ << " : Treat it as not purchased state in android.";
SetPurchasedState(env, PurchasedState::NOT_PURCHASED);
#else
out_of_credentials_ = true;
VLOG(1) << __func__ << " : Treat it as session expired state in desktop.";
UpdatePurchasedStateForSessionExpired(env);
#endif
Expand Down Expand Up @@ -678,6 +706,7 @@ void BraveVpnService::OnPrepareCredentialsPresentation(
return;
}

out_of_credentials_ = false;
SetSkusCredential(local_prefs_, credential, time);

if (GetCurrentEnvironment() != env) {
Expand Down Expand Up @@ -716,28 +745,28 @@ void BraveVpnService::OnGetSubscriberCredentialV12(
return;
}

// If we get here, we've already tried two credentials (the retry failed).
// We can set the state as FAILED and do not attempt to get another
// credential. The cached credential will eventually expire and user will
// fetch a new one.
//
// There could be two reasons for this.

// 1. We've already tried two credentials (the retry failed).
if (token_no_longer_valid && IsRetriedSkusCredential(local_prefs_)) {
VLOG(2) << __func__
<< " : Got TokenNoLongerValid again with retried skus credential";
out_of_credentials_ = true;
SetPurchasedState(
GetCurrentEnvironment(), PurchasedState::FAILED,
l10n_util::GetStringUTF8(IDS_BRAVE_VPN_PURCHASE_TOKEN_NOT_VALID));
return;
}

// When this path is reached:
// - The cached credential is considered good but vendor side has an error.
// That could be a network outage or a server side error on vendor side.
// OR
// - The cached credential is consumed and we've now tried two different
// credentials.
//
// We set the state as FAILED and do not attempt to get another credential.
// Cached credential will eventually expire and user will fetch a new one.
//
// This logic can be updated if we issue more than two credentials per day.
auto message_id = token_no_longer_valid
? IDS_BRAVE_VPN_PURCHASE_TOKEN_NOT_VALID
: IDS_BRAVE_VPN_PURCHASE_CREDENTIALS_FETCH_FAILED;
// 2. The cached credential is considered good but vendor side has an error.
// That could be a network outage or a server side error on vendor side.
SetPurchasedState(GetCurrentEnvironment(), PurchasedState::FAILED,
l10n_util::GetStringUTF8(message_id));
l10n_util::GetStringUTF8(
IDS_BRAVE_VPN_PURCHASE_CREDENTIALS_FETCH_FAILED));
#endif
return;
}
Expand Down
1 change: 1 addition & 0 deletions components/brave_vpn/browser/brave_vpn_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ class BraveVpnService :
std::unique_ptr<BraveVPNServiceDelegate> delegate_;
base::RepeatingTimer p3a_timer_;
base::OneShotTimer subs_cred_refresh_timer_;
bool out_of_credentials_ = false;
base::WeakPtrFactory<BraveVpnService> weak_ptr_factory_{this};
};

Expand Down
1 change: 1 addition & 0 deletions components/brave_vpn/browser/brave_vpn_service_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ void SetSkusCredential(PrefService* local_prefs,
base::TimeToValue(expiration_time));
local_prefs->SetDict(prefs::kBraveVPNSubscriberCredential,
std::move(cred_dict));
local_prefs->SetTime(prefs::kBraveVPNLastCredentialExpiry, expiration_time);
}

void SetSkusCredentialFetchingRetried(PrefService* local_prefs, bool retried) {
Expand Down
1 change: 1 addition & 0 deletions components/brave_vpn/common/brave_vpn_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ void RegisterVPNLocalStatePrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(prefs::kBraveVPNWireguardProfileCredentials, "");
registry->RegisterDictionaryPref(prefs::kBraveVPNRootPref);
registry->RegisterDictionaryPref(prefs::kBraveVPNSubscriberCredential);
registry->RegisterTimePref(prefs::kBraveVPNLastCredentialExpiry, {});
registry->RegisterBooleanPref(prefs::kBraveVPNLocalStateMigrated, false);
registry->RegisterTimePref(prefs::kBraveVPNSessionExpiredDate, {});
#if BUILDFLAG(ENABLE_BRAVE_VPN_WIREGUARD)
Expand Down
1 change: 1 addition & 0 deletions components/brave_vpn/common/mojom/brave_vpn.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ enum PurchasedState {
LOADING,
SESSION_EXPIRED,
FAILED,
OUT_OF_CREDENTIALS,
};

struct PurchasedInfo {
Expand Down
4 changes: 4 additions & 0 deletions components/brave_vpn/common/pref_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ inline constexpr char kBraveVPNEnvironment[] = "brave.brave_vpn.env";
// Dict that has subscriber credential its expiration date.
inline constexpr char kBraveVPNSubscriberCredential[] =
"brave.brave_vpn.subscriber_credential";
// Set the expiry of the last redeemed credential.
// Useful in case redemption fails and person uses all credentials.
inline constexpr char kBraveVPNLastCredentialExpiry[] =
"brave.brave_vpn.last_credential_expiry";

// Time that session expired occurs.
inline constexpr char kBraveVPNSessionExpiredDate[] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ function MainPanel() {
const isSelectingRegion = useSelector((state) => state.isSelectingRegion)
const connectionStatus = useSelector((state) => state.connectionStatus)
const expired = useSelector((state) => state.expired)
const outOfCredentials = useSelector((state) => state.outOfCredentials)
const regions = useSelector((state) => state.regions)
const stateDescription = useSelector((state) => state.stateDescription)

const onSelectRegionButtonClick = () => {
dispatch(Actions.toggleRegionSelector(true))
Expand Down Expand Up @@ -184,6 +186,16 @@ function MainPanel() {
<SessionExpiredContent />
</S.StyledAlert>
)}
{outOfCredentials && (
<S.StyledAlert
type='warning'
mode='full'
hideIcon
>
<div slot='title'>{getLocale('braveVpnOutOfCredentials')}</div>
<div>{stateDescription}</div>
</S.StyledAlert>
)}
<S.RegionSelectorButton
type='button'
onClick={onSelectRegionButtonClick}
Expand Down
3 changes: 3 additions & 0 deletions components/brave_vpn/resources/panel/state/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export type showMainViewPayload = {
regions: Region[]
connectionStatus: ConnectionState
expired: boolean
outOfCredentials: boolean
stateDescription: string
}

export type initializedPayload = {
Expand All @@ -45,6 +47,7 @@ export const connectionFailed = createAction('connectionFailed')
export const initialize = createAction('initialize')
export const purchaseConfirmed = createAction('purchaseConfirmed')
export const purchaseExpired = createAction('purchaseExpired')
export const outOfCredentials = createAction('outOfCredentials')
export const showSellView = createAction('showSellView')
export const showLoadingView = createAction('showLoadingView')
export const resetConnectionState = createAction('resetConnectionState')
Expand Down
27 changes: 25 additions & 2 deletions components/brave_vpn/resources/panel/state/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ handler.on(Actions.purchaseConfirmed.getType(), async (store) => {
state === ConnectionState.CONNECT_FAILED
? ConnectionState.DISCONNECTED
: state /* Treat connection failure on startup as disconnected */,
expired: false
expired: false,
outOfCredentials: false,
stateDescription: ''
})
)
})
Expand All @@ -86,7 +88,28 @@ handler.on(Actions.purchaseExpired.getType(), async (store) => {
currentRegion,
regions,
connectionStatus: ConnectionState.DISCONNECTED,
expired: true
expired: true,
outOfCredentials: false,
stateDescription: ''
})
)
})

handler.on(Actions.outOfCredentials.getType(), async (store) => {
const [{ state }, { currentRegion }, { regions }] = await Promise.all([
getPanelBrowserAPI().serviceHandler.getPurchasedState(),
getPanelBrowserAPI().serviceHandler.getSelectedRegion(),
getPanelBrowserAPI().serviceHandler.getAllRegions()
])

store.dispatch(
Actions.showMainView({
currentRegion,
regions,
connectionStatus: ConnectionState.DISCONNECTED,
expired: false,
stateDescription: state.description || '',
outOfCredentials: true
})
)
})
Expand Down
4 changes: 4 additions & 0 deletions components/brave_vpn/resources/panel/state/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type RootState = {
hasError: boolean
isSelectingRegion: boolean
expired: boolean
outOfCredentials: boolean
connectionStatus: ConnectionState
regions: Region[]
currentRegion: Region
Expand All @@ -25,6 +26,7 @@ const defaultState: RootState = {
hasError: false,
isSelectingRegion: false,
expired: false,
outOfCredentials: false,
connectionStatus: ConnectionState.DISCONNECTED,
regions: [],
currentRegion: new Region(),
Expand Down Expand Up @@ -148,9 +150,11 @@ reducer.on(Actions.showMainView, (state, payload): RootState => {
return {
...state,
expired: payload.expired,
outOfCredentials: payload.outOfCredentials,
currentRegion: payload.currentRegion,
regions: payload.regions,
connectionStatus: payload.connectionStatus,
stateDescription: payload.stateDescription,
currentView: ViewType.Main
}
})
Expand Down
3 changes: 3 additions & 0 deletions components/brave_vpn/resources/panel/state/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const observer = {
case PurchasedState.SESSION_EXPIRED:
store.dispatch(Actions.purchaseExpired())
break
case PurchasedState.OUT_OF_CREDENTIALS:
store.dispatch(Actions.outOfCredentials())
break
case PurchasedState.FAILED:
store.dispatch(Actions.purchaseFailed({
state: PurchasedState.FAILED, stateDescription: description
Expand Down
7 changes: 7 additions & 0 deletions components/resources/brave_vpn_strings.grdp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@
Credentials expiry date elapsed.
</message>

<message name="IDS_BRAVE_VPN_MAIN_PANEL_OUT_OF_CREDENTIALS_CONTENT" desc="Message to show when person is out of credentials and needs to wait for more">
Brave VPN is having trouble authenticating to the VPN server. Please contact support or try again later.
</message>

<message name="IDS_BRAVE_VPN_PURCHASE_CREDENTIALS_FETCH_FAILED" desc="Message to show when credentials cannot be loaded">
Unable to load credentials.
</message>
Expand Down Expand Up @@ -367,6 +371,9 @@ Let's get you connected!
<message name="IDS_BRAVE_VPN_MAIN_PANEL_SESSION_EXPIRED_PART_TITLE" desc="Title text for session expired panel">
Session expired
</message>
<message name="IDS_BRAVE_VPN_MAIN_PANEL_OUT_OF_CREDENTIALS_TITLE" desc="Title text for when user is out of credentials and they can't authenticate">
Can't authenticate to VPN
</message>
<message name="IDS_BRAVE_VPN_MAIN_PANEL_VPN_SETTINGS_TITLE" desc="Settings button tooltip">
Settings
</message>
Expand Down

0 comments on commit 3245b05

Please sign in to comment.