diff --git a/internal/auth/api_key.go b/internal/auth/api_key.go index 4a84e5e..9d8afbf 100644 --- a/internal/auth/api_key.go +++ b/internal/auth/api_key.go @@ -15,10 +15,10 @@ import ( // User represents an authenticated user type User struct { - Name string `json:"name"` - Admin bool `json:"admin"` - Roles []string `json:"roles"` Permissions map[string]bool `json:"permissions"` + Name string `json:"name"` + Roles []string `json:"roles"` + Admin bool `json:"admin"` } // ExtractAPIKeyFromRequest extracts an API key from the standard headers. @@ -41,12 +41,12 @@ type APIKeyHash string // APIKeyEntry represents an API key configuration type APIKeyEntry struct { - Hash APIKeyHash `yaml:"hash"` - Salt string `yaml:"salt,omitempty"` // Salt for Argon2id hashing - Algorithm string `yaml:"algorithm,omitempty"` // "sha256" or "argon2id" - Admin bool `yaml:"admin"` - Roles []string `yaml:"roles,omitempty"` Permissions map[string]bool `yaml:"permissions,omitempty"` + Hash APIKeyHash `yaml:"hash"` + Salt string `yaml:"salt,omitempty"` + Algorithm string `yaml:"algorithm,omitempty"` + Roles []string `yaml:"roles,omitempty"` + Admin bool `yaml:"admin"` } // Username represents a user identifier @@ -54,8 +54,8 @@ type Username string // Config represents the authentication configuration type Config struct { - Enabled bool `yaml:"enabled"` APIKeys map[Username]APIKeyEntry `yaml:"api_keys"` + Enabled bool `yaml:"enabled"` } // Store interface for different authentication backends @@ -81,12 +81,12 @@ const userContextKey = contextKey("user") // UserInfo represents user information from authentication store type UserInfo struct { - UserID string `json:"user_id"` - Admin bool `json:"admin"` - KeyHash string `json:"key_hash"` Created time.Time `json:"created"` Expires *time.Time `json:"expires,omitempty"` Revoked *time.Time `json:"revoked,omitempty"` + UserID string `json:"user_id"` + KeyHash string `json:"key_hash"` + Admin bool `json:"admin"` } // ValidateAPIKey validates an API key and returns user information diff --git a/internal/auth/database.go b/internal/auth/database.go index cef7014..69d305a 100644 --- a/internal/auth/database.go +++ b/internal/auth/database.go @@ -18,15 +18,15 @@ type DatabaseAuthStore struct { // APIKeyRecord represents an API key in the database type APIKeyRecord struct { - ID int `json:"id"` - UserID string `json:"user_id"` - KeyHash string `json:"key_hash"` - Admin bool `json:"admin"` - Roles string `json:"roles"` // JSON array - Permissions string `json:"permissions"` // JSON object CreatedAt time.Time `json:"created_at"` ExpiresAt *time.Time `json:"expires_at,omitempty"` RevokedAt *time.Time `json:"revoked_at,omitempty"` + UserID string `json:"user_id"` + KeyHash string `json:"key_hash"` + Roles string `json:"roles"` + Permissions string `json:"permissions"` + ID int `json:"id"` + Admin bool `json:"admin"` } // NewDatabaseAuthStore creates a new database-backed auth store diff --git a/internal/auth/keychain.go b/internal/auth/keychain.go index 128ba89..4126942 100644 --- a/internal/auth/keychain.go +++ b/internal/auth/keychain.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/jfraeys/fetch_ml/internal/fileutil" "github.com/zalando/go-keyring" ) @@ -95,7 +96,7 @@ func (km *KeychainManager) DeleteAPIKey(service, account string) error { // Try to delete from primary keyring, but don't fail on keyring errors // (e.g., dbus unavailable, permission denied) - just clean up fallback _ = km.primary.Delete(service, account) - + // Always clean up fallback if err := km.fallback.delete(service, account); err != nil && !errors.Is(err, os.ErrNotExist) { return err @@ -136,7 +137,8 @@ func (f *fileKeyStore) store(service, account, secret string) error { return fmt.Errorf("failed to prepare key store: %w", err) } path := f.path(service, account) - return os.WriteFile(path, []byte(secret), 0o600) + // SECURITY: Write with fsync for crash safety + return fileutil.WriteFileSafe(path, []byte(secret), 0o600) } func (f *fileKeyStore) get(service, account string) (string, error) { diff --git a/internal/auth/permissions.go b/internal/auth/permissions.go index a2d917f..63149b3 100644 --- a/internal/auth/permissions.go +++ b/internal/auth/permissions.go @@ -47,8 +47,8 @@ const ( // PermissionGroup represents a group of related permissions type PermissionGroup struct { Name string - Permissions []string Description string + Permissions []string } // PermissionGroups defines built-in permission groups. @@ -167,11 +167,11 @@ func ExpandPermissionGroups(groups []string) ([]string, error) { // PermissionCheckResult represents the result of a permission check type PermissionCheckResult struct { - Allowed bool `json:"allowed"` Permission string `json:"permission"` User string `json:"user"` Roles []string `json:"roles"` Missing []string `json:"missing,omitempty"` + Allowed bool `json:"allowed"` } // CheckMultiplePermissions checks multiple permissions at once