mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-13 18:29:07 +02:00
# Conflicts: # client/android/client.go # client/android/login.go # client/android/profile_prefs.go # client/internal/connect.go # client/internal/engine.go # client/mobile/profile_state.go # client/ui/i18n/locales/de/common.json # client/ui/i18n/locales/en/common.json # client/ui/i18n/locales/es/common.json # client/ui/i18n/locales/fr/common.json # client/ui/i18n/locales/hu/common.json # client/ui/i18n/locales/it/common.json # client/ui/i18n/locales/ja/common.json # client/ui/i18n/locales/pt/common.json # client/ui/i18n/locales/ru/common.json # client/ui/i18n/locales/zh-CN/common.json # client/ui/main.go
132 lines
4.3 KiB
Go
132 lines
4.3 KiB
Go
package mobile
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
|
"github.com/netbirdio/netbird/util"
|
|
)
|
|
|
|
const (
|
|
// profileAccountSuffix names the file holding the profile's account email.
|
|
// Deliberately not ".state.json", which desktop uses for the same data:
|
|
// there the email and the engine's state manager live in different
|
|
// directories, but on mobile both resolve under configDir, so sharing the
|
|
// name would have the two overwrite each other — the state manager rewrites
|
|
// the whole file from its own keys (see statemanager.Manager.PersistState),
|
|
// and this package's writer does the same in reverse.
|
|
profileAccountSuffix = ".account.json"
|
|
)
|
|
|
|
// profileAccountPathFor derives the account file path from a profile's config
|
|
// path: netbird.cfg -> netbird.account.json, <id>.json -> <id>.account.json.
|
|
//
|
|
// Deriving from the config path rather than resolving the active profile keeps
|
|
// the write on the profile the login actually ran for: login flows run in a
|
|
// goroutine, so the active profile can change under a flow already in flight.
|
|
func profileAccountPathFor(configPath string) (string, error) {
|
|
if configPath == "" {
|
|
return "", fmt.Errorf("empty config path")
|
|
}
|
|
|
|
base := filepath.Base(configPath)
|
|
stem := strings.TrimSuffix(base, filepath.Ext(base))
|
|
if stem == "" || stem == "." {
|
|
return "", fmt.Errorf("config path %q has no filename stem", configPath)
|
|
}
|
|
|
|
return filepath.Join(filepath.Dir(configPath), stem+profileAccountSuffix), nil
|
|
}
|
|
|
|
// ProfileLocationFor splits a profile's config path back into the config dir and
|
|
// the profile ID: <dir>/netbird.cfg is the default profile, while
|
|
// <dir>/profiles/<id>.json is a named one.
|
|
func ProfileLocationFor(configPath string) (string, string, error) {
|
|
if configPath == "" {
|
|
return "", "", fmt.Errorf("empty config path")
|
|
}
|
|
|
|
base := filepath.Base(configPath)
|
|
dir := filepath.Dir(configPath)
|
|
|
|
if base == defaultConfigFilename {
|
|
return dir, profilemanager.DefaultProfileName, nil
|
|
}
|
|
|
|
if filepath.Base(dir) != profilesSubdir {
|
|
return "", "", fmt.Errorf("config path %q is outside the profiles directory", configPath)
|
|
}
|
|
|
|
id := strings.TrimSuffix(base, filepath.Ext(base))
|
|
if !profilemanager.IsValidProfileFilenameStem(profilemanager.ID(id)) {
|
|
return "", "", fmt.Errorf("config path %q has no valid profile ID", configPath)
|
|
}
|
|
|
|
return filepath.Dir(dir), id, nil
|
|
}
|
|
|
|
// ReadProfileEmail returns the account email stored for the profile whose
|
|
// config lives at configPath. A missing or unreadable file yields "", which
|
|
// leaves the account choice to the IdP.
|
|
func ReadProfileEmail(configPath string) string {
|
|
accountPath, err := profileAccountPathFor(configPath)
|
|
if err != nil {
|
|
log.Debugf("no profile account path for login hint: %v", err)
|
|
return ""
|
|
}
|
|
|
|
var state profilemanager.ProfileState
|
|
if _, err := util.ReadJson(accountPath, &state); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
log.Debugf("failed to read profile account for login hint: %v", err)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
return state.Email
|
|
}
|
|
|
|
// WriteProfileEmail records the account email for the profile whose config
|
|
// lives at configPath, so later logins can pass it as an OIDC login_hint. An
|
|
// empty email is ignored rather than blanking what is already stored.
|
|
func WriteProfileEmail(configPath string, email string) error {
|
|
if email == "" {
|
|
return nil
|
|
}
|
|
|
|
accountPath, err := profileAccountPathFor(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve profile account path: %w", err)
|
|
}
|
|
|
|
state := profilemanager.ProfileState{Email: email}
|
|
if err := util.WriteJsonWithRestrictedPermission(context.Background(), accountPath, state); err != nil {
|
|
return fmt.Errorf("write profile account: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// removeProfileEmail drops the stored account email. Called on profile removal,
|
|
// not on logout: a logged-out profile keeps its email so the next login passes
|
|
// it as the login_hint, matching the desktop and CLI semantics. Mirrors the
|
|
// desktop UI's RemoveProfileState call.
|
|
func removeProfileEmail(configPath string) error {
|
|
accountPath, err := profileAccountPathFor(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve profile account path: %w", err)
|
|
}
|
|
|
|
if err := os.Remove(accountPath); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("remove profile account: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|