profile

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package profile owns connection profile persistence: the JSON record, UUIDv7 scope generation and validation, XDG path resolution, versioned AES-256-GCM at-rest encryption, secret-reference handling, and atomic hardened 0700/0600 saves.

It has no Bubble Tea dependency; list rendering and driver display labels belong to the workbench connection feature.

At-rest encryption

Literal passwords are stored in a versioned envelope ("enc:v2:" + base64(nonce|AES-256-GCM ciphertext|tag)) whose authenticated data binds the ciphertext to the profile's scope ID and the field identity ("pass" or the exact extra key). Connection targets that carry credentials (URL userinfo passwords, the libpq/pgx password query parameter, mysql: DSNs) are encrypted the same way under the "target" field identity; non-credential targets stay plaintext and readable. Pre-v2 "enc:" records (no binding) still load and are rewritten to the v2 envelope on the next successful save, and legacy plaintext credential-bearing targets are migrated to the encrypted form on the next successful save. Secret references (${ENV_VAR}, file://) are stored verbatim. A value that cannot be decrypted (tampering, wrong or missing key, unknown envelope) is never surfaced as a literal and never rewritten: Load retains it in the profile with an Undecryptable marker and reports secretFail, and Save refuses while the field still holds the retained blob. The key and the ciphertext both live under the same user-owned 0700 config directory, so the encryption protects against accidental disclosure and backups — not against an attacker with account access.

Index

Constants

View Source
const (
	// MaxProfiles caps the persisted profile list.
	MaxProfiles = 20
)

Variables

This section is empty.

Functions

func IsSecretRef

func IsSecretRef(pass string) bool

IsSecretRef returns true if pass is a reference (${ENV_VAR} or file:///path) rather than a literal password.

func NewID

func NewID() (string, error)

NewID returns a fresh UUIDv7 scope for a connection profile.

func Path

func Path() (string, error)

Path returns the XDG config location of the profiles file.

func ResolveSecretRef

func ResolveSecretRef(pass string) string

ResolveSecretRef expands ${ENV_VAR} and file:///path references.

func Save

func Save(path string, profiles []Profile) error

Save persists profiles atomically with 0600 permissions in a 0700 directory, skipping invalid records and encrypting literal passwords into the versioned v2 envelope bound to each profile's scope and field identity. Secret references are stored verbatim. The profiles target must be a regular file (never a symlink), the directory and file permissions are forced to 0700/0600 even when pre-existing, the temporary file is fsynced before the atomic rename, and the containing directory is fsynced after it; a failure at any point leaves the original file intact.

Save refuses a record while any field still holds a retained undecryptable blob (see Load): the ciphertext is never wrapped as a literal or silently rewritten. Re-entering the field clears the refusal.

func ValidID

func ValidID(id string) bool

ValidID reports whether id is a parseable UUIDv7, the only scope form this application generates for profiles.

Types

type Driver

type Driver string

Driver identifies a database driver.

const (
	DriverSQLite     Driver = "sqlite"
	DriverMySQL      Driver = "mysql"
	DriverPostgreSQL Driver = "postgres"
)

type MySQLTLS

type MySQLTLS string

MySQLTLS is the persisted MySQL TLS mode.

const (
	MySQLTLSVerify     MySQLTLS = "true"
	MySQLTLSSkipVerify MySQLTLS = "skip-verify"
	MySQLTLSDisabled   MySQLTLS = "false"
)

MySQL TLS modes, as stored in profiles and the connection form.

type PostgreSQLTLS

type PostgreSQLTLS string

PostgreSQLTLS is the persisted PostgreSQL TLS mode.

const (
	PostgreSQLTLSVerifyFull PostgreSQLTLS = "verify-full"
	PostgreSQLTLSEncrypt    PostgreSQLTLS = "require"
	PostgreSQLTLSDisabled   PostgreSQLTLS = "disable"
)

PostgreSQL TLS modes, as stored in profiles and the connection form.

type Profile

type Profile struct {
	ID string `json:"id"`
	// Plugin is the selected plugin instance. Driver remains the database
	// family for form rules and display.
	Plugin string `json:"plugin,omitempty"`
	Driver Driver `json:"driver"`
	Name   string `json:"name"`
	// Target is the driver's opener target body. It is persisted as-is
	// unless it carries credentials (URL userinfo password, a password
	// query parameter, or a mysql: DSN), in which case it is stored as a
	// v2 envelope bound to the "target" field identity and restored only
	// after authenticated decryption.
	Target        string        `json:"target"`
	Host          string        `json:"host,omitempty"`
	Port          string        `json:"port,omitempty"`
	User          string        `json:"user,omitempty"`
	Pass          string        `json:"pass,omitempty"`
	MySQLTLS      MySQLTLS      `json:"mysqlTLS,omitempty"`
	PostgreSQLTLS PostgreSQLTLS `json:"postgresTLS,omitempty"`
	ReadOnly      bool          `json:"readOnly,omitempty"`
	// Extras carries driver-specific form values beyond the fixed fields
	// (empty for the built-in drivers).
	Extras map[string]string `json:"extras,omitempty"`
	// Undecryptable maps a field identity ("pass" or an exact extra key)
	// to the stored enc: value that failed to decrypt at load. It is
	// in-memory only (json:"-"; the wire format is unchanged) and marks
	// a fail-closed load: the retained ciphertext is never surfaced as a
	// literal, and Save refuses to rewrite it until the user re-enters it.
	Undecryptable map[string]string `json:"-"`
}

Profile is one persisted connection record. The JSON keys are stable wire format; do not rename them.

func Load

func Load(path string) (profiles []Profile, migrated bool, secretFail bool)

Load reads persisted profiles, assigning a fresh UUIDv7 scope to every legacy profile whose ID is empty, invalid, or duplicated by an earlier profile, and marking legacy plaintext credential-bearing targets for encryption. migrated reports whether any profile was reassigned or a legacy credential target was found, so callers can persist the corrected file immediately. An unreadable or corrupt file yields an empty list; a symlink or non-regular profiles target is refused (treated as unreadable) rather than followed.

secretFail reports that at least one stored enc: value could not be decrypted (tampering, wrong or missing key). Such values are retained verbatim in the returned profiles with an Undecryptable marker — never surfaced as literals — and Save refuses to rewrite them until the user re-enters the field, so a transient key problem can never silently destroy the stored ciphertext.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL