Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
73 changes: 73 additions & 0 deletions account/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//go:build !stealth_novpn

package account

import (
"context"
"fmt"
"log/slog"
"net/url"

"github.com/getlantern/radiance/account/protos"
"github.com/getlantern/radiance/common"
"github.com/getlantern/radiance/common/settings"
)

// OAuthLoginURL initiates the OAuth login process for the specified provider.
func (a *Client) OAuthLoginURL(ctx context.Context, provider string) (string, error) {
authURL := a.authURL
if authURL == "" {
authURL = common.GetBaseURL()
}
loginURL, err := url.Parse(authURL + "/users/oauth2/" + provider)
if err != nil {
return "", fmt.Errorf("failed to parse URL: %w", err)
}
query := loginURL.Query()
query.Set("deviceId", settings.GetString(settings.DeviceIDKey))
query.Set("userId", settings.GetString(settings.UserIDKey))
query.Set("proToken", settings.GetString(settings.TokenKey))
query.Set("returnTo", "lantern://auth")
loginURL.RawQuery = query.Encode()
// Persist the provider so it's available after the callback completes.
if err := settings.Set(settings.OAuthProviderKey, provider); err != nil {
return "", fmt.Errorf("failed to persist OAuth provider: %w", err)
}
return loginURL.String(), nil
}

func (a *Client) OAuthLoginCallback(ctx context.Context, oAuthToken string) (*UserData, error) {
slog.Debug("Getting OAuth login callback")
jwtUserInfo, err := decodeJWT(oAuthToken)
if err != nil {
return nil, fmt.Errorf("error decoding JWT: %w", err)
}

// Temporary set user data so the api can read it.
login := &UserData{
LegacyID: jwtUserInfo.LegacyUserID,
LegacyToken: jwtUserInfo.LegacyToken,
LegacyUserData: &protos.LoginResponse_UserData{
UserId: jwtUserInfo.LegacyUserID,
Token: jwtUserInfo.LegacyToken,
DeviceID: jwtUserInfo.DeviceID,
Email: jwtUserInfo.Email,
},
}
a.setData(login)
// Get user data from the api. This also saves data in user config.
user, err := a.fetchUserData(ctx)
if err != nil {
return nil, fmt.Errorf("error getting user data: %w", err)
}

if err := settings.Set(settings.JwtTokenKey, oAuthToken); err != nil {
slog.Error("Failed to persist JWT token", "error", err)
return nil, fmt.Errorf("failed to persist JWT token: %w", err)
}
settings.Set(settings.OAuthLoginKey, true)
user.Id = jwtUserInfo.Email
user.EmailConfirmed = true
a.setData(user)
return user, nil
}
60 changes: 0 additions & 60 deletions account/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"log/slog"
"net/url"
"os"
"strings"

Expand Down Expand Up @@ -501,65 +500,6 @@ func (a *Client) DeleteAccount(ctx context.Context, email, password string) (*Us
return a.NewUser(ctx)
}

// OAuthLoginURL initiates the OAuth login process for the specified provider.
func (a *Client) OAuthLoginURL(ctx context.Context, provider string) (string, error) {
authURL := a.authURL
if authURL == "" {
authURL = common.GetBaseURL()
}
loginURL, err := url.Parse(authURL + "/users/oauth2/" + provider)
if err != nil {
return "", fmt.Errorf("failed to parse URL: %w", err)
}
query := loginURL.Query()
query.Set("deviceId", settings.GetString(settings.DeviceIDKey))
query.Set("userId", settings.GetString(settings.UserIDKey))
query.Set("proToken", settings.GetString(settings.TokenKey))
query.Set("returnTo", "lantern://auth")
loginURL.RawQuery = query.Encode()
// Persist the provider so it's available after the callback completes.
if err := settings.Set(settings.OAuthProviderKey, provider); err != nil {
return "", fmt.Errorf("failed to persist OAuth provider: %w", err)
}
return loginURL.String(), nil
}

func (a *Client) OAuthLoginCallback(ctx context.Context, oAuthToken string) (*UserData, error) {
slog.Debug("Getting OAuth login callback")
jwtUserInfo, err := decodeJWT(oAuthToken)
if err != nil {
return nil, fmt.Errorf("error decoding JWT: %w", err)
}

// Temporary set user data to so api can read it
login := &UserData{
LegacyID: jwtUserInfo.LegacyUserID,
LegacyToken: jwtUserInfo.LegacyToken,
LegacyUserData: &protos.LoginResponse_UserData{
UserId: jwtUserInfo.LegacyUserID,
Token: jwtUserInfo.LegacyToken,
DeviceID: jwtUserInfo.DeviceID,
Email: jwtUserInfo.Email,
},
}
a.setData(login)
// Get user data from api this will also save data in user config
user, err := a.fetchUserData(ctx)
if err != nil {
return nil, fmt.Errorf("error getting user data: %w", err)
}

if err := settings.Set(settings.JwtTokenKey, oAuthToken); err != nil {
slog.Error("Failed to persist JWT token", "error", err)
return nil, fmt.Errorf("failed to persist JWT token: %w", err)
}
settings.Set(settings.OAuthLoginKey, true)
user.Id = jwtUserInfo.Email
user.EmailConfirmed = true
a.setData(user)
return user, nil
}

type LinkResponse struct {
*protos.BaseResponse `json:",inline"`
UserID int `json:"userID"`
Expand Down
17 changes: 17 additions & 0 deletions backend/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build !stealth_novpn

package backend

import (
"context"

"github.com/getlantern/radiance/account"
)

func (r *LocalBackend) OAuthLoginCallback(ctx context.Context, oAuthToken string) (*account.UserData, error) {
return r.accountClient.OAuthLoginCallback(ctx, oAuthToken)
}

func (r *LocalBackend) OAuthLoginURL(ctx context.Context, provider string) (string, error) {
Comment thread
reflog marked this conversation as resolved.
return r.accountClient.OAuthLoginURL(ctx, provider)
}
22 changes: 7 additions & 15 deletions backend/radiance.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,13 @@ func (r *LocalBackend) Start() {
var srvs []*servers.Server
for _, out := range cfg.Options.Outbounds {
srvs = append(srvs, &servers.Server{
Tag: out.Tag, Type: out.Type, IsLantern: true,
Tag: out.Tag, Type: out.Type, Managed: true,
Options: out, Location: locs[out.Tag],
})
}
for _, ep := range cfg.Options.Endpoints {
srvs = append(srvs, &servers.Server{
Tag: ep.Tag, Type: ep.Type, IsLantern: true,
Tag: ep.Tag, Type: ep.Type, Managed: true,
Options: ep, Location: locs[ep.Tag],
})
}
Expand Down Expand Up @@ -583,8 +583,8 @@ func (r *LocalBackend) RemoveServers(tags []string) error {
return nil
}

func (r *LocalBackend) setServers(list servers.ServerList, isLantern bool) error {
if err := r.srvManager.SetServers(list, isLantern); err != nil {
func (r *LocalBackend) setServers(list servers.ServerList, managed bool) error {
if err := r.srvManager.SetServers(list, managed); err != nil {
return fmt.Errorf("failed to set servers in ServerManager: %w", err)
}
// updateOutbounds evicts any outbound absent from the list; include all
Expand Down Expand Up @@ -730,7 +730,7 @@ func (r *LocalBackend) ConnectVPN(tag string) error {
bOptions := r.getBoxOptions()
bOptions.InitialServer = tag
if err := r.vpnClient.Connect(bOptions); err != nil {
return fmt.Errorf("failed to connect VPN: %w", err)
return fmt.Errorf("failed to connect session: %w", err)
}
r.persistSelection(tag)
return nil
Expand All @@ -755,7 +755,7 @@ func (r *LocalBackend) getBoxOptions() vpn.BoxOptions {
}
seed := make(map[string]adapter.URLTestHistory)
for _, srv := range r.srvManager.AllServers() {
if !srv.IsLantern {
if !srv.Managed {
switch opts := srv.Options.(type) {
case option.Outbound:
bOptions.Options.Outbounds = append(bOptions.Options.Outbounds, opts)
Expand Down Expand Up @@ -883,7 +883,7 @@ func (r *LocalBackend) SelectedServer() (*servers.Server, bool, error) {
}
server, found := r.srvManager.GetServerByTag(selected.Tag)
stillExists := found &&
server.IsLantern == selected.IsLantern &&
server.Managed == selected.Managed &&
server.Type == selected.Type &&
server.Location == selected.Location
return &selected, stillExists, nil
Expand Down Expand Up @@ -1082,14 +1082,6 @@ func (r *LocalBackend) RemoveDevice(ctx context.Context, deviceID string) (*acco
return r.accountClient.RemoveDevice(ctx, deviceID)
}

func (r *LocalBackend) OAuthLoginCallback(ctx context.Context, oAuthToken string) (*account.UserData, error) {
return r.accountClient.OAuthLoginCallback(ctx, oAuthToken)
}

func (r *LocalBackend) OAuthLoginURL(ctx context.Context, provider string) (string, error) {
return r.accountClient.OAuthLoginURL(ctx, provider)
}

func (r *LocalBackend) UserDevices() ([]settings.Device, error) {
return settings.Devices()
}
Expand Down
71 changes: 17 additions & 54 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,19 @@ import (
"sync/atomic"
"time"

"golang.zx2c4.com/wireguard/wgctrl/wgtypes"

C "github.com/getlantern/common"
lbO "github.com/getlantern/lantern-box/option"
"github.com/sagernet/sing-box/option"
singjson "github.com/sagernet/sing/common/json"

box "github.com/getlantern/lantern-box"
lbO "github.com/getlantern/lantern-box/option"

"github.com/getlantern/radiance/account"
"github.com/getlantern/radiance/common"
"github.com/getlantern/radiance/common/atomicfile"
"github.com/getlantern/radiance/common/fileperm"
"github.com/getlantern/radiance/common/settings"
"github.com/getlantern/radiance/events"
"github.com/getlantern/radiance/internal"
"github.com/getlantern/radiance/internal/boxctx"
)

const (
Expand Down Expand Up @@ -129,21 +126,15 @@ func (ch *ConfigHandler) Start() {

var ErrNoWGKey = errors.New("no wg key")

func (ch *ConfigHandler) loadWGKey() (wgtypes.Key, error) {
buf, err := atomicfile.ReadFile(ch.wgKeyPath)
if os.IsNotExist(err) {
return wgtypes.Key{}, ErrNoWGKey
}
if err != nil {
return wgtypes.Key{}, fmt.Errorf("reading wg key file: %w", err)
}
key, err := wgtypes.ParseKey(string(buf))
if err != nil {
return wgtypes.Key{}, fmt.Errorf("parsing wg key: %w", err)
}
return key, nil
type proxyConfigKey struct {
private string
public string
}

func (k proxyConfigKey) Private() string { return k.private }

func (k proxyConfigKey) Public() string { return k.public }

func (ch *ConfigHandler) fetchConfig() error {
if settings.GetBool(settings.ConfigFetchDisabledKey) {
ch.logger.Info("config fetch disabled, skipping")
Expand All @@ -158,20 +149,9 @@ func (ch *ConfigHandler) fetchConfig() error {
}
defer ch.fetching.Store(false)

privateKey, err := ch.loadWGKey()
if err != nil && !errors.Is(err, ErrNoWGKey) {
return fmt.Errorf("loading wg key: %w", err)
}

if errors.Is(err, ErrNoWGKey) {
var keyErr error
if privateKey, keyErr = wgtypes.GeneratePrivateKey(); keyErr != nil {
return fmt.Errorf("failed to generate wg keys: %w", keyErr)
}

if writeErr := atomicfile.WriteFile(ch.wgKeyPath, []byte(privateKey.String()), fileperm.File); writeErr != nil {
return fmt.Errorf("writing wg key file: %w", writeErr)
}
privateKey, err := ch.loadProxyConfigKey()
if err != nil {
return fmt.Errorf("loading proxy config key: %w", err)
}

ch.logger.Info("Fetching config")
Expand All @@ -180,7 +160,7 @@ func (ch *ConfigHandler) fetchConfig() error {
ch.logger.Error("failed to get preferred location from settings", "error", err)
}

resp, err := ch.ftr.fetchConfig(ch.ctx, preferred, privateKey.PublicKey().String())
resp, err := ch.ftr.fetchConfig(ch.ctx, preferred, privateKey.Public())
if err != nil {
return fmt.Errorf("%w: %w", ErrFetchingConfig, err)
}
Expand All @@ -200,14 +180,14 @@ func (ch *ConfigHandler) fetchConfig() error {
// because the error could have been due to temporary network issues, such as brief
// power loss or internet disconnection.
// On the other hand, if we have a new config, we want to overwrite any previous error.
confResp, err := singjson.UnmarshalExtendedContext[C.ConfigResponse](box.BaseContext(), resp)
confResp, err := singjson.UnmarshalExtendedContext[C.ConfigResponse](boxctx.BaseContext(), resp)
if err != nil {
ch.logger.Error("failed to parse config", "error", err)
return fmt.Errorf("parsing config: %w", err)
}
cleanTags(&confResp)

setWireGuardKeyInOptions(confResp.Options.Endpoints, privateKey)
setEndpointKeys(confResp.Options.Endpoints, privateKey)
setCustomProtocolOptions(confResp.Options.Outbounds)
if err := ch.setConfig(&confResp); err != nil {
ch.logger.Error("failed to set config", "error", err)
Expand Down Expand Up @@ -249,23 +229,6 @@ func cleanTags(cfg *C.ConfigResponse) {
cfg.OutboundLocations = nlocs
}

func setWireGuardKeyInOptions(endpoints []option.Endpoint, privateKey wgtypes.Key) {
// Requires privilege and cannot conflict with existing system interfaces
// System tries to use system env; for mobile we need to tun device
system := !(common.IsAndroid() || common.IsIOS() || common.IsMacOS())
for _, endpoint := range endpoints {
switch opts := endpoint.Options.(type) {
case *option.WireGuardEndpointOptions:
opts.PrivateKey = privateKey.String()
opts.System = opts.System && system
case *lbO.AmneziaEndpointOptions:
opts.PrivateKey = privateKey.String()
opts.System = opts.System && system
default:
}
}
}

// fetchLoop fetches the configuration periodically. It uses the server's
// recommended poll interval (PollIntervalSeconds) when available, falling
// back to the default pollInterval. This allows the bandit to control how
Expand Down Expand Up @@ -355,7 +318,7 @@ func load(path string) (*Config, error) {
if err != nil {
return nil, fmt.Errorf("reading config file: %w", err)
}
ctx := box.BaseContext()
ctx := boxctx.BaseContext()
cfg, err := singjson.UnmarshalExtendedContext[*Config](ctx, buf)
if err != nil {
// try to migrate from old format if parsing fails
Expand All @@ -379,7 +342,7 @@ func migrateToNewFmt(data []byte) (*Config, error) {
if err := json.Unmarshal(data, &tmp); err != nil {
return nil, err
}
opts, err := singjson.UnmarshalExtendedContext[C.ConfigResponse](box.BaseContext(), tmp.ConfigResponse)
opts, err := singjson.UnmarshalExtendedContext[C.ConfigResponse](boxctx.BaseContext(), tmp.ConfigResponse)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading