refactor(auth): add tenant scoping and permission enhancements
Update authentication system for multi-tenant support: - API key management with tenant scoping - Permission checks for multi-tenant operations - Database layer with tenant isolation - Keychain integration with audit logging
This commit is contained in:
parent
420de879ff
commit
ef11d88a75
4 changed files with 24 additions and 22 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue