diff --git a/client/android/profile_manager.go b/client/android/profile_manager.go index 00c9cbde8..78d908530 100644 --- a/client/android/profile_manager.go +++ b/client/android/profile_manager.go @@ -23,7 +23,7 @@ const ( // Profile represents a profile for gomobile type Profile struct { - ID string + ID profilemanager.ID Name string IsActive bool } @@ -119,7 +119,7 @@ func (pm *ProfileManager) GetActiveProfile() (*Profile, error) { // ActiveProfileState only stores the ID (and username), not the display // name. Resolve the ID to the full profile so callers get the real Name. - prof, err := pm.serviceMgr.ResolveProfile(activeState.ID, androidUsername) + prof, err := pm.serviceMgr.ResolveProfile(activeState.ID.String(), androidUsername) if err != nil { return nil, fmt.Errorf("failed to resolve active profile %q: %w", activeState.ID, err) } @@ -127,7 +127,7 @@ func (pm *ProfileManager) GetActiveProfile() (*Profile, error) { } // SwitchProfile switches to a different profile -func (pm *ProfileManager) SwitchProfile(id string) error { +func (pm *ProfileManager) SwitchProfile(id profilemanager.ID) error { // Use ServiceManager to stay consistent with ListProfiles // ServiceManager uses active_profile.json err := pm.serviceMgr.SetActiveProfileState(&profilemanager.ActiveProfileState{ @@ -155,7 +155,7 @@ func (pm *ProfileManager) AddProfile(profileName string) error { } // LogoutProfile logs out from a profile (clears authentication) -func (pm *ProfileManager) LogoutProfile(id string) error { +func (pm *ProfileManager) LogoutProfile(id profilemanager.ID) error { configPath, err := pm.getProfileConfigPath(id) if err != nil { return err @@ -190,7 +190,7 @@ func (pm *ProfileManager) LogoutProfile(id string) error { } // RemoveProfile deletes a profile -func (pm *ProfileManager) RemoveProfile(id string) error { +func (pm *ProfileManager) RemoveProfile(id profilemanager.ID) error { // Use ServiceManager (removes profile from profiles/ directory) if err := pm.serviceMgr.RemoveProfile(id, androidUsername); err != nil { return fmt.Errorf("failed to remove profile: %w", err) @@ -202,7 +202,7 @@ func (pm *ProfileManager) RemoveProfile(id string) error { // getProfileConfigPath returns the config file path for a profile // This is needed for Android-specific path handling (netbird.cfg for default profile) -func (pm *ProfileManager) getProfileConfigPath(id string) (string, error) { +func (pm *ProfileManager) getProfileConfigPath(id profilemanager.ID) (string, error) { if id == "" || id == profilemanager.DefaultProfileName { // Android uses netbird.cfg for default profile instead of default.json // Default profile is stored in root configDir, not in profiles/ @@ -210,24 +210,24 @@ func (pm *ProfileManager) getProfileConfigPath(id string) (string, error) { } profilesDir := filepath.Join(pm.configDir, profilesSubdir) - return filepath.Join(profilesDir, id+".json"), nil + return filepath.Join(profilesDir, id.String()+".json"), nil } // GetConfigPath returns the config file path for a given profile id // Java should call this instead of constructing paths with Preferences.configFile() -func (pm *ProfileManager) GetConfigPath(id string) (string, error) { +func (pm *ProfileManager) GetConfigPath(id profilemanager.ID) (string, error) { return pm.getProfileConfigPath(id) } // GetStateFilePath returns the state file path for a given profile // Java should call this instead of constructing paths with Preferences.stateFile() -func (pm *ProfileManager) GetStateFilePath(id string) (string, error) { +func (pm *ProfileManager) GetStateFilePath(id profilemanager.ID) (string, error) { if id == "" || id == profilemanager.DefaultProfileName { return filepath.Join(pm.configDir, "state.json"), nil } profilesDir := filepath.Join(pm.configDir, profilesSubdir) - return filepath.Join(profilesDir, id+".state.json"), nil + return filepath.Join(profilesDir, id.String()+".state.json"), nil } // GetActiveConfigPath returns the config file path for the currently active profile diff --git a/client/cmd/login.go b/client/cmd/login.go index fa0eb546a..2f7677901 100644 --- a/client/cmd/login.go +++ b/client/cmd/login.go @@ -96,13 +96,15 @@ func doDaemonLogin(ctx context.Context, cmd *cobra.Command, providedSetupKey str dnsLabelsReq = dnsLabelsValidated.ToSafeStringList() } + handle := activeProf.ID.String() + loginRequest := proto.LoginRequest{ SetupKey: providedSetupKey, ManagementUrl: managementURL, IsUnixDesktopClient: isUnixRunningDesktop(), Hostname: hostName, DnsLabels: dnsLabelsReq, - ProfileName: &activeProf.ID, + ProfileName: &handle, Username: &username, } @@ -208,7 +210,7 @@ func switchProfileOnDaemon(ctx context.Context, pm *profilemanager.ProfileManage // handle (a name, ID, or unique ID prefix). Returns the resolved profile // ID so the caller can update the local active-profile state without // re-resolving the handle. -func switchProfile(ctx context.Context, handle string, username string) (string, error) { +func switchProfile(ctx context.Context, handle string, username string) (profilemanager.ID, error) { conn, err := DialClientGRPCServer(ctx, daemonAddr) if err != nil { //nolint @@ -228,7 +230,7 @@ func switchProfile(ctx context.Context, handle string, username string) (string, return "", fmt.Errorf("switch profile failed: %v", err) } - return resp.Id, nil + return profilemanager.ID(resp.Id), nil } func doForegroundLogin(ctx context.Context, cmd *cobra.Command, setupKey string, activeProf *profilemanager.Profile) error { @@ -280,7 +282,7 @@ func handleSSOLogin(ctx context.Context, cmd *cobra.Command, loginResp *proto.Lo return nil } -func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *profilemanager.Config, setupKey, profileName string) error { +func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *profilemanager.Config, setupKey string, profileID profilemanager.ID) error { authClient, err := auth.NewAuth(ctx, config.PrivateKey, config.ManagementURL, config) if err != nil { return fmt.Errorf("failed to create auth client: %v", err) @@ -294,7 +296,7 @@ func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *profileman jwtToken := "" if setupKey == "" && needsLogin { - tokenInfo, err := foregroundGetTokenInfo(ctx, cmd, config, profileName) + tokenInfo, err := foregroundGetTokenInfo(ctx, cmd, config, profileID) if err != nil { return fmt.Errorf("interactive sso login failed: %v", err) } @@ -309,10 +311,10 @@ func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *profileman return nil } -func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *profilemanager.Config, profileName string) (*auth.TokenInfo, error) { +func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *profilemanager.Config, profileID profilemanager.ID) (*auth.TokenInfo, error) { hint := "" pm := profilemanager.NewProfileManager() - profileState, err := pm.GetProfileState(profileName) + profileState, err := pm.GetProfileState(profileID) if err != nil { log.Debugf("failed to get profile state for login hint: %v", err) } else if profileState.Email != "" { diff --git a/client/cmd/profile.go b/client/cmd/profile.go index d6ea6354e..c11ed57dd 100644 --- a/client/cmd/profile.go +++ b/client/cmd/profile.go @@ -115,8 +115,9 @@ func listProfilesFunc(cmd *cobra.Command, _ []string) error { marker = "✓" } name := profilemanager.StripCtrlChars(profile.Name) + id := profilemanager.ID(profile.Id) if profileListShowID { - fmt.Fprintf(tw, "%s\t%s\t%s\n", profilemanager.ShortID(profile.Id), name, marker) + fmt.Fprintf(tw, "%s\t%s\t%s\n", id.ShortID(), name, marker) } else { fmt.Fprintf(tw, "%s\t%s\n", name, marker) } @@ -147,8 +148,9 @@ func addProfileFunc(cmd *cobra.Command, args []string) error { ProfileName: profileName, Username: currUser.Username, }) + id := profilemanager.ID(resp.Id) if err == nil { - cmd.Printf("Profile added: %s %s\n", profilemanager.ShortID(resp.Id), profilemanager.StripCtrlChars(profileName)) + cmd.Printf("Profile added: %s %s\n", id.ShortID(), profilemanager.StripCtrlChars(profileName)) return nil } @@ -165,7 +167,8 @@ func addProfileFunc(cmd *cobra.Command, args []string) error { if err != nil { return err } - cmd.Printf("Profile added: %s %s\n", profilemanager.ShortID(resp.Id), profilemanager.StripCtrlChars(profileName)) + id = profilemanager.ID(resp.Id) + cmd.Printf("Profile added: %s %s\n", id.ShortID(), profilemanager.StripCtrlChars(profileName)) return nil } @@ -248,7 +251,7 @@ func selectProfileFunc(cmd *cobra.Command, args []string) error { return wrapAmbiguityError(err, handle) } - if err := profileManager.SwitchProfile(switchResp.Id); err != nil { + if err := profileManager.SwitchProfile(profilemanager.ID(switchResp.Id)); err != nil { return err } @@ -263,7 +266,8 @@ func selectProfileFunc(cmd *cobra.Command, args []string) error { } } - cmd.Printf("Profile switched to: %s\n", profilemanager.ShortID(switchResp.Id)) + id := profilemanager.ID(switchResp.Id) + cmd.Printf("Profile switched to: %s\n", id.ShortID()) return nil } diff --git a/client/cmd/up.go b/client/cmd/up.go index 96403f883..9050f50e9 100644 --- a/client/cmd/up.go +++ b/client/cmd/up.go @@ -189,7 +189,7 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command, activeProf *pr _, _ = profilemanager.UpdateOldManagementURL(ctx, config, configFilePath) - err = foregroundLogin(ctx, cmd, config, providedSetupKey, activeProf.Name) + err = foregroundLogin(ctx, cmd, config, providedSetupKey, activeProf.ID) if err != nil { return fmt.Errorf("foreground login failed: %v", err) } @@ -260,7 +260,7 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command, pm *profilemanager } // set the new config - req := setupSetConfigReq(customDNSAddressConverted, cmd, activeProf.ID, username.Username) + req := setupSetConfigReq(customDNSAddressConverted, cmd, activeProf.ID.String(), username.Username) if _, err := client.SetConfig(ctx, req); err != nil { if st, ok := gstatus.FromError(err); ok && st.Code() == codes.Unavailable { log.Warnf("setConfig method is not available in the daemon: %s", st.Message()) @@ -288,7 +288,8 @@ func doDaemonUp(ctx context.Context, cmd *cobra.Command, client proto.DaemonServ return fmt.Errorf("setup login request: %v", err) } - loginRequest.ProfileName = &activeProf.ID + handle := activeProf.ID.String() + loginRequest.ProfileName = &handle loginRequest.Username = &username profileState, err := pm.GetProfileState(activeProf.ID) @@ -328,7 +329,7 @@ func doDaemonUp(ctx context.Context, cmd *cobra.Command, client proto.DaemonServ } if _, err := client.Up(ctx, &proto.UpRequest{ - ProfileName: &activeProf.ID, + ProfileName: &profileName, Username: &username, }); err != nil { return fmt.Errorf("call service up method: %v", err) diff --git a/client/internal/profilemanager/id.go b/client/internal/profilemanager/id.go index 808ca814b..d96925a70 100644 --- a/client/internal/profilemanager/id.go +++ b/client/internal/profilemanager/id.go @@ -31,18 +31,21 @@ const ( maxProfileIDLen = 64 ) +type ID string + // generateProfileID returns a new random hex ID for a profile file. -func generateProfileID() (string, error) { +func generateProfileID() (ID, error) { buf := make([]byte, profileIDByteLen) if _, err := rand.Read(buf); err != nil { return "", fmt.Errorf("read random bytes: %w", err) } - return hex.EncodeToString(buf), nil + return ID(hex.EncodeToString(buf)), nil } -// IsValidProfileFilenameStem reports whether s is safe to use as the stem +// IsValidProfileFilenameStem reports whether id is safe to use as the stem // of a profile JSON filename. -func IsValidProfileFilenameStem(s string) bool { +func IsValidProfileFilenameStem(id ID) bool { + s := id.String() if s == "" || len(s) > maxProfileIDLen { return false } @@ -99,12 +102,16 @@ func StripCtrlChars(name string) string { } // ShortID truncates an ID for display. -func ShortID(id string) string { +func (id ID) ShortID() string { if id == DefaultProfileName { - return id + return DefaultProfileName } if len(id) <= shortIDLen { - return id + return id.String() } - return id[:shortIDLen] + return id.String()[:shortIDLen] +} + +func (id ID) String() string { + return string(id) } diff --git a/client/internal/profilemanager/profilemanager.go b/client/internal/profilemanager/profilemanager.go index b72348ca0..e25d493d5 100644 --- a/client/internal/profilemanager/profilemanager.go +++ b/client/internal/profilemanager/profilemanager.go @@ -23,7 +23,7 @@ type Profile struct { // it is a 32-char hex string; legacy profiles created before the // ID-keyed layout keep their original name as their ID. The reserved // value "default" identifies the special default profile. - ID string + ID ID // Name is the human-readable display name. Falls back to ID when the // underlying JSON has no "name" field set. Name string @@ -40,7 +40,7 @@ func (p *Profile) FilePath() (string, error) { id := p.ID if id == "" { - id = p.Name + id = ID(p.Name) } if id == "" { return "", fmt.Errorf("profile ID is empty") @@ -64,7 +64,7 @@ func (p *Profile) FilePath() (string, error) { return "", fmt.Errorf("failed to get config directory for user %s: %w", username.Username, err) } - return filepath.Join(configDir, id+".json"), nil + return filepath.Join(configDir, id.String()+".json"), nil } func (p *Profile) IsDefault() bool { @@ -94,7 +94,7 @@ func (pm *ProfileManager) GetActiveProfile() (*Profile, error) { // SwitchProfile records the given profile ID as active in the local user // state file. -func (pm *ProfileManager) SwitchProfile(id string) error { +func (pm *ProfileManager) SwitchProfile(id ID) error { if id != defaultProfileName && !IsValidProfileFilenameStem(id) { return fmt.Errorf("invalid profile ID: %q", id) } @@ -116,7 +116,7 @@ func sanitizeProfileName(name string) string { }, name) } -func (pm *ProfileManager) getActiveProfileState() string { +func (pm *ProfileManager) getActiveProfileState() ID { configDir, err := getConfigDir() if err != nil { @@ -144,10 +144,10 @@ func (pm *ProfileManager) getActiveProfileState() string { return defaultProfileName } - return profileName + return ID(profileName) } -func (pm *ProfileManager) setActiveProfileState(profileName string) error { +func (pm *ProfileManager) setActiveProfileState(id ID) error { configDir, err := getConfigDir() if err != nil { @@ -156,7 +156,7 @@ func (pm *ProfileManager) setActiveProfileState(profileName string) error { statePath := filepath.Join(configDir, activeProfileStateFilename) - err = os.WriteFile(statePath, []byte(profileName), 0600) + err = os.WriteFile(statePath, []byte(id), 0600) if err != nil { return fmt.Errorf("failed to write active profile state: %w", err) } diff --git a/client/internal/profilemanager/service.go b/client/internal/profilemanager/service.go index 268f87b2b..69259c06b 100644 --- a/client/internal/profilemanager/service.go +++ b/client/internal/profilemanager/service.go @@ -91,7 +91,7 @@ type ActiveProfileState struct { // before the ID-based config files. Legacy values were profile names, which // were also the legacy filename stems, so they still resolve to the correct // file on disk. - ID string `json:"name"` + ID ID `json:"name"` Username string `json:"username"` } @@ -113,7 +113,7 @@ func (a *ActiveProfileState) FilePath() (string, error) { return "", fmt.Errorf("failed to get config directory for user %s: %w", a.Username, err) } - return filepath.Join(configDir, a.ID+".json"), nil + return filepath.Join(configDir, a.ID.String()+".json"), nil } type ServiceManager struct { @@ -315,7 +315,7 @@ func (s *ServiceManager) AddProfile(displayName, username string) (*Profile, err return nil, fmt.Errorf("generate profile id: %w", err) } - profPath := filepath.Join(configDir, id+".json") + profPath := filepath.Join(configDir, id.String()+".json") cfg, err := createNewConfig(ConfigInput{ConfigPath: profPath}) if err != nil { return nil, fmt.Errorf("failed to create new config: %w", err) @@ -336,7 +336,7 @@ func (s *ServiceManager) AddProfile(displayName, username string) (*Profile, err // RemoveProfile deletes the profile identified by id. Callers must have // already resolved any user-supplied handle to a concrete ID via // ResolveProfile. -func (s *ServiceManager) RemoveProfile(id, username string) error { +func (s *ServiceManager) RemoveProfile(id ID, username string) error { if id == defaultProfileName { return fmt.Errorf("cannot remove profile with reserved name: %s", defaultProfileName) } @@ -372,7 +372,7 @@ func (s *ServiceManager) RemoveProfile(id, username string) error { return fmt.Errorf("failed to remove profile config: %w", err) } - stateFile := filepath.Join(filepath.Dir(target.Path), id+".state.json") + stateFile := filepath.Join(filepath.Dir(target.Path), id.String()+".state.json") if err := os.Remove(stateFile); err != nil && !os.IsNotExist(err) { log.Warnf("failed to remove profile state file %s: %v", stateFile, err) } @@ -416,7 +416,7 @@ func (s *ServiceManager) GetStatePath() string { return defaultStatePath } - return filepath.Join(configDir, activeProf.ID+".state.json") + return filepath.Join(configDir, activeProf.ID.String()+".state.json") } // getConfigDir returns the profiles directory, using profilesDir if set, otherwise getConfigDirForUser @@ -470,24 +470,24 @@ func (s *ServiceManager) loadAllProfiles(username string) ([]Profile, error) { if strings.HasSuffix(base, ".state.json") { continue } - stem := strings.TrimSuffix(base, ".json") + stem := ID(strings.TrimSuffix(base, ".json")) if stem == defaultProfileName { // default lives at the top-level config dir, not under / continue } - if !IsValidProfileFilenameStem(stem) { + if !IsValidProfileFilenameStem(ID(stem)) { continue } path := filepath.Join(configDir, base) name := readProfileName(path) if name == "" { - name = stem + name = stem.String() } fileProfiles = append(fileProfiles, Profile{ ID: stem, Name: name, Path: path, - IsActive: stem == activeID, + IsActive: stem == ID(activeID), }) } @@ -517,7 +517,7 @@ func readProfileName(path string) string { // activeProfileID returns the currently-active profile's ID. The second // return value is true when the active profile is the default one. -func (s *ServiceManager) activeProfileID() (string, bool) { +func (s *ServiceManager) activeProfileID() (ID, bool) { state, err := s.GetActiveProfileState() if err != nil || state == nil { return defaultProfileName, true @@ -543,7 +543,7 @@ func (s *ServiceManager) ResolveProfile(handle, username string) (*Profile, erro } for i := range profiles { - if profiles[i].ID == handle { + if profiles[i].ID == ID(handle) { return &profiles[i], nil } } @@ -555,7 +555,7 @@ func (s *ServiceManager) ResolveProfile(handle, username string) (*Profile, erro if profiles[i].ID == defaultProfileName { continue } - if strings.HasPrefix(profiles[i].ID, handle) { + if strings.HasPrefix(profiles[i].ID.String(), handle) { prefixMatches = append(prefixMatches, profiles[i]) } } diff --git a/client/internal/profilemanager/service_test.go b/client/internal/profilemanager/service_test.go index 98fac31e0..76204e4e3 100644 --- a/client/internal/profilemanager/service_test.go +++ b/client/internal/profilemanager/service_test.go @@ -35,7 +35,7 @@ func TestServiceProfile_ExactID(t *testing.T) { created, err := sm.AddProfile("work", username) require.NoError(t, err) - got, err := sm.ResolveProfile(created.ID, username) + got, err := sm.ResolveProfile(created.ID.String(), username) require.NoError(t, err) assert.Equal(t, created.ID, got.ID) assert.Equal(t, "work", got.Name) @@ -48,7 +48,7 @@ func TestServiceProfile_IDPrefix(t *testing.T) { require.NoError(t, err) prefix := created.ID[:4] - got, err := sm.ResolveProfile(prefix, username) + got, err := sm.ResolveProfile(prefix.String(), username) require.NoError(t, err) assert.Equal(t, created.ID, got.ID) }) @@ -208,7 +208,7 @@ func TestIsValidProfileFilenameStem(t *testing.T) { {strings.Repeat("a", maxProfileIDLen+1), false}, } for _, tc := range cases { - got := IsValidProfileFilenameStem(tc.in) + got := IsValidProfileFilenameStem(ID(tc.in)) assert.Equal(t, tc.want, got, "case %q", tc.in) } } @@ -220,7 +220,7 @@ func TestRemoveProfile_DeletesStateFile(t *testing.T) { configDir, err := sm.getConfigDir(username) require.NoError(t, err) - statePath := filepath.Join(configDir, created.ID+".state.json") + statePath := filepath.Join(configDir, created.ID.String()+".state.json") require.NoError(t, os.WriteFile(statePath, []byte(`{"email":"a@b"}`), 0600)) require.NoError(t, sm.RemoveProfile(created.ID, username)) diff --git a/client/internal/profilemanager/state.go b/client/internal/profilemanager/state.go index 4729f992e..1bf3318af 100644 --- a/client/internal/profilemanager/state.go +++ b/client/internal/profilemanager/state.go @@ -16,7 +16,7 @@ type ProfileState struct { // GetProfileState reads the per-profile state file keyed by profile ID. // The state file lives in the user's config directory. Legacy state files // keyed by the old profile name remain readable. -func (pm *ProfileManager) GetProfileState(id string) (*ProfileState, error) { +func (pm *ProfileManager) GetProfileState(id ID) (*ProfileState, error) { configDir, err := getConfigDir() if err != nil { return nil, fmt.Errorf("get config directory: %w", err) @@ -26,7 +26,7 @@ func (pm *ProfileManager) GetProfileState(id string) (*ProfileState, error) { return nil, fmt.Errorf("invalid profile ID: %q", id) } - stateFile := filepath.Join(configDir, id+".state.json") + stateFile := filepath.Join(configDir, id.String()+".state.json") stateFileExists, err := fileExists(stateFile) if err != nil { return nil, fmt.Errorf("failed to check if profile state file exists: %w", err) @@ -63,7 +63,7 @@ func (pm *ProfileManager) SetActiveProfileState(state *ProfileState) error { return fmt.Errorf("invalid active profile ID: %q", id) } - stateFile := filepath.Join(configDir, id+".state.json") + stateFile := filepath.Join(configDir, id.String()+".state.json") err = util.WriteJsonWithRestrictedPermission(context.Background(), stateFile, state) if err != nil { return fmt.Errorf("write profile state: %w", err) diff --git a/client/server/server.go b/client/server/server.go index 0675e8449..03aa4b56a 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -832,7 +832,7 @@ func (s *Server) SwitchProfile(callerCtx context.Context, msg *proto.SwitchProfi s.config = config - return &proto.SwitchProfileResponse{Id: activeProf.ID}, nil + return &proto.SwitchProfileResponse{Id: activeProf.ID.String()}, nil } // Down engine work in the daemon. @@ -998,7 +998,7 @@ func (s *Server) getConfig(activeProf *profilemanager.ActiveProfileState) (*prof return config, configExisted, nil } -func (s *Server) canRemoveProfile(id string) error { +func (s *Server) canRemoveProfile(id profilemanager.ID) error { if id == profilemanager.DefaultProfileName { return fmt.Errorf("remove profile with reserved name: %s", profilemanager.DefaultProfileName) } @@ -1011,7 +1011,7 @@ func (s *Server) canRemoveProfile(id string) error { return nil } -func (s *Server) validateProfileOperation(id string, allowActiveProfile bool) error { +func (s *Server) validateProfileOperation(id profilemanager.ID, allowActiveProfile bool) error { if s.checkProfilesDisabled() { return gstatus.Errorf(codes.Unavailable, errProfilesDisabled) } @@ -1571,7 +1571,7 @@ func (s *Server) AddProfile(ctx context.Context, msg *proto.AddProfileRequest) ( return nil, fmt.Errorf("failed to create profile: %w", err) } - return &proto.AddProfileResponse{Id: created.ID}, nil + return &proto.AddProfileResponse{Id: created.ID.String()}, nil } // RemoveProfile removes a profile from the daemon. @@ -1601,7 +1601,7 @@ func (s *Server) RemoveProfile(ctx context.Context, msg *proto.RemoveProfileRequ return nil, fmt.Errorf("failed to remove profile: %w", err) } - return &proto.RemoveProfileResponse{Id: resolved.ID}, nil + return &proto.RemoveProfileResponse{Id: resolved.ID.String()}, nil } // ListProfiles lists all profiles in the daemon. @@ -1624,7 +1624,7 @@ func (s *Server) ListProfiles(ctx context.Context, msg *proto.ListProfilesReques } for i, profile := range profiles { response.Profiles[i] = &proto.Profile{ - Id: profile.ID, + Id: profile.ID.String(), Name: profile.Name, IsActive: profile.IsActive, } @@ -1633,9 +1633,9 @@ func (s *Server) ListProfiles(ctx context.Context, msg *proto.ListProfilesReques return response, nil } -// GetActiveProfile returns the active profile in the daemon. The -// ProfileName field carries the display name for backwards compatibility -// with UI clients, new callers should prefer Id. +// GetActiveProfile returns the active profile in the daemon. The ProfileName +// field carries the display name for backwards compatibility with UI clients, +// new callers should prefer Id. func (s *Server) GetActiveProfile(ctx context.Context, msg *proto.GetActiveProfileRequest) (*proto.GetActiveProfileResponse, error) { s.mutex.Lock() defer s.mutex.Unlock() @@ -1646,7 +1646,8 @@ func (s *Server) GetActiveProfile(ctx context.Context, msg *proto.GetActiveProfi return nil, fmt.Errorf("failed to get active profile state: %w", err) } - displayName := activeProfile.ID + // Fallback to legacy name == ID + displayName := activeProfile.ID.String() if activeProfile.ID != profilemanager.DefaultProfileName { if profiles, lerr := s.profileManager.ListProfiles(activeProfile.Username); lerr == nil { for _, p := range profiles { @@ -1661,7 +1662,7 @@ func (s *Server) GetActiveProfile(ctx context.Context, msg *proto.GetActiveProfi return &proto.GetActiveProfileResponse{ ProfileName: displayName, Username: activeProfile.Username, - Id: activeProfile.ID, + Id: activeProfile.ID.String(), }, nil } diff --git a/client/server/server_test.go b/client/server/server_test.go index c2ee2b6a7..fa9599818 100644 --- a/client/server/server_test.go +++ b/client/server/server_test.go @@ -158,7 +158,7 @@ func TestServer_Up(t *testing.T) { pm := profilemanager.ServiceManager{} err = pm.SetActiveProfileState(&profilemanager.ActiveProfileState{ - ID: profName, + ID: profilemanager.ID(profName), Username: currUser.Username, }) if err != nil { diff --git a/client/server/setconfig_test.go b/client/server/setconfig_test.go index 6d441e133..7c85d16ce 100644 --- a/client/server/setconfig_test.go +++ b/client/server/setconfig_test.go @@ -47,7 +47,7 @@ func TestSetConfig_AllFieldsSaved(t *testing.T) { pm := profilemanager.ServiceManager{} err = pm.SetActiveProfileState(&profilemanager.ActiveProfileState{ - ID: profName, + ID: profilemanager.ID(profName), Username: currUser.Username, }) require.NoError(t, err) @@ -96,7 +96,7 @@ func TestSetConfig_AllFieldsSaved(t *testing.T) { DisableNotifications: &disableNotifications, LazyConnectionEnabled: &lazyConnectionEnabled, BlockInbound: &blockInbound, - DisableIpv6: &disableIPv6, + DisableIpv6: &disableIPv6, NatExternalIPs: []string{"1.2.3.4", "5.6.7.8"}, CleanNATExternalIPs: false, CustomDNSAddress: []byte("1.1.1.1:53"), @@ -112,7 +112,7 @@ func TestSetConfig_AllFieldsSaved(t *testing.T) { require.NoError(t, err) profState := profilemanager.ActiveProfileState{ - ID: profName, + ID: profilemanager.ID(profName), Username: currUser.Username, } cfgPath, err := profState.FilePath() diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go index 1a451070b..690683009 100644 --- a/client/ui/client_ui.go +++ b/client/ui/client_ui.go @@ -622,7 +622,7 @@ func (s *serviceClient) buildSetConfigRequest(iMngURL string, port, mtu int64) ( } req := &proto.SetConfigRequest{ - ProfileName: activeProf.ID, + ProfileName: activeProf.ID.String(), Username: currUser.Username, } @@ -787,9 +787,11 @@ func (s *serviceClient) login(ctx context.Context, openURL bool) (*proto.LoginRe return nil, fmt.Errorf("get current user: %w", err) } + handle := activeProf.ID.String() + loginReq := &proto.LoginRequest{ IsUnixDesktopClient: runtime.GOOS == "linux" || runtime.GOOS == "freebsd", - ProfileName: &activeProf.ID, + ProfileName: &handle, Username: &currUser.Username, } @@ -1309,7 +1311,7 @@ func (s *serviceClient) getSrvConfig() { } srvCfg, err := conn.GetConfig(s.ctx, &proto.GetConfigRequest{ - ProfileName: activeProf.ID, + ProfileName: activeProf.ID.String(), Username: currUser.Username, }) if err != nil { @@ -1533,7 +1535,7 @@ func (s *serviceClient) loadSettings() { } cfg, err := conn.GetConfig(s.ctx, &proto.GetConfigRequest{ - ProfileName: activeProf.ID, + ProfileName: activeProf.ID.String(), Username: currUser.Username, }) if err != nil { @@ -1610,7 +1612,7 @@ func (s *serviceClient) updateConfig() error { } req := proto.SetConfigRequest{ - ProfileName: activeProf.ID, + ProfileName: activeProf.ID.String(), Username: currUser.Username, DisableAutoConnect: &disableAutoStart, ServerSSHAllowed: &sshAllowed, diff --git a/client/ui/profile.go b/client/ui/profile.go index 8a6b8bc9e..6d6ba466b 100644 --- a/client/ui/profile.go +++ b/client/ui/profile.go @@ -269,7 +269,7 @@ func (s *serviceClient) switchProfile(handle string) error { return fmt.Errorf("switch profile failed: %w", err) } - if err := s.profileManager.SwitchProfile(resp.Id); err != nil { + if err := s.profileManager.SwitchProfile(profilemanager.ID(resp.Id)); err != nil { return fmt.Errorf("switch profile: %w", err) } @@ -506,7 +506,7 @@ func (p *profileMenu) refresh() { } if activeProf.ProfileName == "default" || activeProf.Username == currUser.Username { - activeProfState, err := p.profileManager.GetProfileState(activeProf.Id) + activeProfState, err := p.profileManager.GetProfileState(profilemanager.ID(activeProf.Id)) if err != nil { log.Warnf("failed to get active profile state: %v", err) p.emailMenuItem.Hide() @@ -557,7 +557,7 @@ func (p *profileMenu) refresh() { return } - err = p.profileManager.SwitchProfile(switchResp.Id) + err = p.profileManager.SwitchProfile(profilemanager.ID(switchResp.Id)) if err != nil { log.Errorf("failed to switch profile '%s': %v", profile.Name, err) return