package appstate import ( "fmt" "strings" "git.julianfamily.org/keepassgo/internal/vault" ) type SyncMode string const ( SyncModeManual SyncMode = "manual" SyncModeAutomaticOnOpenSave SyncMode = "automatic_on_open_save" ) type RemoteBinding struct { LocalVaultPath string `json:"localVaultPath"` RemoteProfileID string `json:"remoteProfileId"` CredentialEntryID string `json:"credentialEntryId"` SyncMode SyncMode `json:"syncMode,omitempty"` } type ResolvedRemoteBinding struct { Profile vault.RemoteProfile Credentials vault.Entry } type RemoteBindingInput struct { LocalVaultPath string RemoteProfileID string RemoteProfileName string BaseURL string RemotePath string CredentialEntryID string CredentialTitle string Username string Password string CredentialPath []string SyncMode SyncMode } func (b RemoteBinding) Resolve(model vault.Model) (ResolvedRemoteBinding, error) { profile, err := model.RemoteProfileByID(b.RemoteProfileID) if err != nil { return ResolvedRemoteBinding{}, fmt.Errorf("resolve remote profile: %w", err) } credentials, err := model.EntryByID(b.CredentialEntryID) if err != nil { return ResolvedRemoteBinding{}, fmt.Errorf("resolve remote credentials: %w", err) } return ResolvedRemoteBinding{ Profile: profile, Credentials: credentials, }, nil } func ConfigureRemoteBinding(model *vault.Model, input RemoteBindingInput) (RemoteBinding, error) { if model == nil { return RemoteBinding{}, fmt.Errorf("model is required") } input.LocalVaultPath = strings.TrimSpace(input.LocalVaultPath) input.RemoteProfileID = strings.TrimSpace(input.RemoteProfileID) input.RemoteProfileName = strings.TrimSpace(input.RemoteProfileName) input.BaseURL = strings.TrimSpace(input.BaseURL) input.RemotePath = strings.TrimSpace(input.RemotePath) input.CredentialEntryID = strings.TrimSpace(input.CredentialEntryID) input.CredentialTitle = strings.TrimSpace(input.CredentialTitle) input.Username = strings.TrimSpace(input.Username) switch { case input.LocalVaultPath == "": return RemoteBinding{}, fmt.Errorf("local vault path is required") case input.RemoteProfileID == "": return RemoteBinding{}, fmt.Errorf("remote profile id is required") case input.BaseURL == "": return RemoteBinding{}, fmt.Errorf("remote base URL is required") case input.RemotePath == "": return RemoteBinding{}, fmt.Errorf("remote path is required") case input.CredentialEntryID == "": return RemoteBinding{}, fmt.Errorf("credential entry id is required") case input.Password == "": return RemoteBinding{}, fmt.Errorf("credential password is required") } if input.RemoteProfileName == "" { input.RemoteProfileName = input.RemoteProfileID } if input.CredentialTitle == "" { input.CredentialTitle = "Remote Sign-In" } model.UpsertRemoteProfile(vault.RemoteProfile{ ID: input.RemoteProfileID, Name: input.RemoteProfileName, Backend: vault.RemoteBackendWebDAV, BaseURL: input.BaseURL, Path: input.RemotePath, }) model.UpsertEntry(vault.Entry{ ID: input.CredentialEntryID, Title: input.CredentialTitle, Username: input.Username, Password: input.Password, URL: input.BaseURL, Path: append([]string(nil), input.CredentialPath...), }) return RemoteBinding{ LocalVaultPath: input.LocalVaultPath, RemoteProfileID: input.RemoteProfileID, CredentialEntryID: input.CredentialEntryID, SyncMode: normalizeSyncMode(input.SyncMode), }, nil } func RemoveRemoteBinding(model *vault.Model, binding RemoteBinding) error { if model == nil { return fmt.Errorf("model is required") } if strings.TrimSpace(binding.RemoteProfileID) == "" { return fmt.Errorf("remote profile id is required") } if strings.TrimSpace(binding.CredentialEntryID) == "" { return fmt.Errorf("credential entry id is required") } model.RemoveRemoteProfileByID(binding.RemoteProfileID) model.RemoveEntryByID(binding.CredentialEntryID) return nil } func normalizeSyncMode(mode SyncMode) SyncMode { switch mode { case SyncModeAutomaticOnOpenSave: return SyncModeAutomaticOnOpenSave default: return SyncModeManual } }