Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions lantern-core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -1170,3 +1170,58 @@ func itemsForType(filter vpn.SplitTunnelFilter, filterType string) []string {
return nil
}
}

// ApplyUserDataToSettings parses a JSON-marshaled account.UserData (the
// result of an identity-changing call: Login, OAuthLoginCallback, Logout,
// DeleteAccount, FetchUserData after an account switch) and pushes the
// resulting user_id / token / user_level / email / jwt_token values through
// c.PatchSettings.
//
// radiance's internal account.Client.setData already writes these via
// settings.Set, but those writes only update the caller's in-process
// settings cache. The macOS client (and others) runs radiance in two
// processes — UI app and network extension — each with their own cache;
// the on-disk settings.json is owned by the NE process. After login the
// UI process knows the user is Pro, but the NE-side cache (and the
// canonical settings.json) keep the pre-login identity, so the config
// fetcher keeps sending /v1/config-new with the old user_id / pro_token
// and the server treats the user as free.
//
// PatchSettings goes through the IPC layer that owns the canonical store,
// matching the pattern AcknowledgeGooglePurchase / restoreSubscription
// already use for UserIDKey + TokenKey on a cross-account purchase.
//
// Both lantern-core/mobile/mobile.go (gomobile path: iOS / Android /
// macOS) and lantern-core/ffi/ffi.go (cgo path: Windows / Linux) call
// this so all five platforms route identity updates through one place.
func ApplyUserDataToSettings(c Core, b []byte) {
var resp account.UserData
if err := json.Unmarshal(b, &resp); err != nil {
slog.Error("failed to parse user data after identity change", "error", err)
return
}
if resp.LegacyID == 0 {
// Server returned no identity (e.g. device-limit on first Login
// attempt). radiance handles the partial-state case internally;
// don't push empty values through PatchSettings.
return
}
updates := settings.Settings{
settings.UserIDKey: fmt.Sprintf("%d", resp.LegacyID),
settings.TokenKey: resp.LegacyToken,
}
if resp.LegacyUserData != nil {
if resp.LegacyUserData.UserLevel != "" {
updates[settings.UserLevelKey] = resp.LegacyUserData.UserLevel
}
if resp.LegacyUserData.Email != "" {
updates[settings.EmailKey] = resp.LegacyUserData.Email
}
}
if resp.Token != "" {
updates[settings.JwtTokenKey] = resp.Token
}
if err := c.PatchSettings(updates); err != nil {
slog.Error("failed to apply user data to settings", "error", err)
}
}
51 changes: 4 additions & 47 deletions lantern-core/ffi/ffi.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"github.com/getlantern/lantern/lantern-core/logs"
"github.com/getlantern/lantern/lantern-core/utils"

"github.com/getlantern/radiance/account"
"github.com/getlantern/radiance/common/settings"
"github.com/getlantern/radiance/vpn"
)
Expand Down Expand Up @@ -749,53 +748,11 @@ func oAuthLoginCallback(_oAuthToken *C.char) *C.char {
if err != nil {
return SendError(err)
}
applyUserDataToSettings(c, bytes)
lanterncore.ApplyUserDataToSettings(c, bytes)
return C.CString(string(bytes))
})
}

// applyUserDataToSettings extracts the identity fields from a JSON-marshaled
// account.UserData and pushes them through c.PatchSettings. radiance's
// internal account.Client.setData already writes these via settings.Set,
// but those writes don't reliably reach the canonical on-disk settings the
// system-extension config fetcher reads from — config-new requests then
// keep going out with the pre-login identity, the server treats the user
// as free, and the Pro location list shrinks to the free subset.
//
// PatchSettings goes through the IPC layer that owns the canonical store,
// matching the pattern the purchase / restore handlers already use
// post-account-switch. Mirror of the same helper in lantern-core/mobile.
func applyUserDataToSettings(c lanterncore.Core, b []byte) {
var resp account.UserData
if err := json.Unmarshal(b, &resp); err != nil {
slog.Error("failed to parse user data after identity change", "error", err)
return
}
if resp.LegacyID == 0 {
// Server returned no identity (e.g. device-limit on first Login
// attempt). radiance handles the partial-state case internally;
// don't push empty values through PatchSettings.
return
}
updates := settings.Settings{
settings.UserIDKey: fmt.Sprintf("%d", resp.LegacyID),
settings.TokenKey: resp.LegacyToken,
}
if resp.LegacyUserData != nil {
if resp.LegacyUserData.UserLevel != "" {
updates[settings.UserLevelKey] = resp.LegacyUserData.UserLevel
}
if resp.LegacyUserData.Email != "" {
updates[settings.EmailKey] = resp.LegacyUserData.Email
}
}
if resp.Token != "" {
updates[settings.JwtTokenKey] = resp.Token
}
if err := c.PatchSettings(updates); err != nil {
slog.Error("failed to apply user data to settings", "error", err)
}
}

// User management
//
Expand All @@ -813,7 +770,7 @@ func login(_email, _password *C.char) *C.char {
if err != nil {
return SendError(err)
}
applyUserDataToSettings(c, bytes)
lanterncore.ApplyUserDataToSettings(c, bytes)
return C.CString(string(bytes))
})
}
Expand Down Expand Up @@ -848,7 +805,7 @@ func logout(_email *C.char) *C.char {
// Logout server-side returns the new anonymous user data; apply it
// so the config fetcher sees the post-logout identity rather than
// the stale Pro user_id / token.
applyUserDataToSettings(c, bytes)
lanterncore.ApplyUserDataToSettings(c, bytes)
return C.CString(string(bytes))
})
}
Expand Down Expand Up @@ -994,7 +951,7 @@ func deleteAccount(_email, _password *C.char) *C.char {
// NewUser internally after clearing the old identity); push the
// new id/token through PatchSettings for the same reason logout
// does.
applyUserDataToSettings(c, bytes)
lanterncore.ApplyUserDataToSettings(c, bytes)
return C.CString(string(bytes))
})
}
Expand Down
65 changes: 19 additions & 46 deletions lantern-core/mobile/mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,53 +489,11 @@ func OAuthLoginCallback(oAuthToken string) (string, error) {
if err != nil {
return "", err
}
applyUserDataToSettings(c, b)
lanterncore.ApplyUserDataToSettings(c, b)
return string(b), nil
})
}

// applyUserDataToSettings extracts the identity fields from a JSON-marshaled
// account.UserData and pushes them through c.PatchSettings. radiance's
// internal account.Client.setData already writes these via settings.Set,
// but those writes don't reliably reach the canonical on-disk settings the
// system-extension config fetcher reads from — config-new requests then
// keep going out with the pre-login identity, the server treats the user
// as free, and the Pro location list shrinks to the free subset.
//
// PatchSettings goes through the IPC layer that owns the canonical store,
// matching the pattern AcknowledgeGooglePurchase / restoreSubscription
// already use post-account-switch.
func applyUserDataToSettings(c lanterncore.Core, b []byte) {
var resp account.UserData
if err := json.Unmarshal(b, &resp); err != nil {
slog.Error("failed to parse user data after identity change", "error", err)
return
}
if resp.LegacyID == 0 {
// Server returned no identity (e.g. device-limit on first Login
// attempt). radiance handles the partial-state case internally;
// don't push empty values through PatchSettings.
return
}
updates := settings.Settings{
settings.UserIDKey: fmt.Sprintf("%d", resp.LegacyID),
settings.TokenKey: resp.LegacyToken,
}
if resp.LegacyUserData != nil {
if resp.LegacyUserData.UserLevel != "" {
updates[settings.UserLevelKey] = resp.LegacyUserData.UserLevel
}
if resp.LegacyUserData.Email != "" {
updates[settings.EmailKey] = resp.LegacyUserData.Email
}
}
if resp.Token != "" {
updates[settings.JwtTokenKey] = resp.Token
}
if err := c.PatchSettings(updates); err != nil {
slog.Error("failed to apply user data to settings", "error", err)
}
}

func StripeSubscription(email, planID string) (string, error) {
return withCoreR(func(c lanterncore.Core) (string, error) { return c.StripeSubscription(email, planID) })
Expand Down Expand Up @@ -572,6 +530,11 @@ func AcknowledgeGooglePurchase(purchaseToken, planId string) (string, error) {
if err != nil {
return "", err
}
// FetchUserData's internal setData updates only the calling
// process's settings cache; propagate the full identity
// (user_level, email, jwt_token) through PatchSettings too
// so the NE-side cache and canonical settings.json catch up.
lanterncore.ApplyUserDataToSettings(c, []byte(userData))
return userData, nil
}
/// Purchase was made on the same account, just return "" to indicate success
Expand Down Expand Up @@ -604,6 +567,7 @@ func AcknowledgeApplePurchase(receipt, planII string) (string, error) {
return "", err
}
slog.Debug("fetched user data after account switch", "userdata", userData)
lanterncore.ApplyUserDataToSettings(c, []byte(userData))
return userData, nil
}
/// Purchase was made on the same account, just return "" to indicate success
Expand Down Expand Up @@ -641,6 +605,15 @@ func restoreSubscription(c lanterncore.Core, fn func(string) (string, error), to
}); err != nil {
return "", fmt.Errorf("error updating settings after account switch: %v", err)
}
// Propagate the full identity (user_level, email, jwt_token) too —
// PatchSettings above only covers user_id + token; without this the
// post-restore user_level can stay stale on the canonical settings
// store until the next FetchUserData triggered elsewhere.
if userData, err := FetchUserData(); err != nil {
slog.Warn("failed to refresh user data after restore account switch", "error", err)
} else {
lanterncore.ApplyUserDataToSettings(c, []byte(userData))
}
}
return data, nil
}
Expand Down Expand Up @@ -669,7 +642,7 @@ func Login(email, password string) (string, error) {
if err != nil {
return "", err
}
applyUserDataToSettings(c, b)
lanterncore.ApplyUserDataToSettings(c, b)
return string(b), nil
})
}
Expand All @@ -695,7 +668,7 @@ func Logout(email string) (string, error) {
// Logout server-side returns the new anonymous user data; apply it
// to settings so the config fetcher sees the post-logout identity
// rather than the stale Pro user_id / token.
applyUserDataToSettings(c, b)
lanterncore.ApplyUserDataToSettings(c, b)
return string(b), nil
})
}
Expand Down Expand Up @@ -750,7 +723,7 @@ func DeleteAccount(email, password string) (string, error) {
// NewUser internally after clearing the old identity); push the
// new id/token through PatchSettings for the same reason Logout
// does.
applyUserDataToSettings(c, b)
lanterncore.ApplyUserDataToSettings(c, b)
return string(b), nil
})
}
Expand Down
Loading