diff options
Diffstat (limited to '')
| -rw-r--r-- | internal/authz/model.go | 131 |
1 files changed, 117 insertions, 14 deletions
diff --git a/internal/authz/model.go b/internal/authz/model.go index 0c55c15..d48d9e9 100644 --- a/internal/authz/model.go +++ b/internal/authz/model.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "os" + "sync" "golang.org/x/crypto/bcrypt" ) @@ -14,34 +15,136 @@ import ( // TokenSize is the number of random bytes used for token generation const TokenSize = 32 -// NewTokenMap create a new token map +// AccessID represents a unique authentication identifier +type AccessID string + +// FriendlyName represents a human-readable identifier +type FriendlyName string + +// TokenMap maps AccessIDs to password hashes +type TokenMap map[AccessID]string + +// SafeTokenMap provides thread-safe access to TokenMap +type SafeTokenMap struct { + mu sync.RWMutex + tokens TokenMap +} + +// NewSafeTokenMap creates a new thread-safe token map +func NewSafeTokenMap() *SafeTokenMap { + return &SafeTokenMap{ + tokens: make(TokenMap), + } +} + +// Get retrieves a hash for the given AccessID +func (s *SafeTokenMap) Get(id AccessID) (string, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + hash, exists := s.tokens[id] + return hash, exists +} + +// Set stores a hash for the given AccessID +func (s *SafeTokenMap) Set(id AccessID, hash string) { + s.mu.Lock() + defer s.mu.Unlock() + s.tokens[id] = hash +} + +// LoadFromFile loads tokens from a CSV file +func (s *SafeTokenMap) LoadFromFile(path string) error { + tokens, _, err := LoadTokensFromFile(path) + if err != nil { + return err + } + + s.mu.Lock() + defer s.mu.Unlock() + s.tokens = tokens + return nil +} + +// IdentityMap manages mappings between AccessIDs and FriendlyNames +type IdentityMap struct { + mu sync.RWMutex + IDToName map[AccessID]FriendlyName + NameToID map[FriendlyName]AccessID +} + +// NewTokenMap creates a new token map func NewTokenMap() TokenMap { - return TokenMap{} + return make(TokenMap) +} + +// NewIdentityMap creates a new identity mapping +func NewIdentityMap() *IdentityMap { + return &IdentityMap{ + IDToName: make(map[AccessID]FriendlyName), + NameToID: make(map[FriendlyName]AccessID), + } } -// TokenMap a map of username,hash -type TokenMap map[string]string +// LoadTokensFromFile loads tokens and identities from a csv file +func LoadTokensFromFile(path string) (TokenMap, *IdentityMap, error) { + tm := make(TokenMap) + im := NewIdentityMap() -// LoadTokensFromFile load tokens from a csv into a map -func (tm TokenMap) LoadTokensFromFile(path string) error { - // TODO this should be configurable contents, err := os.Open(path) if err != nil { slog.Error("File reading error", slog.Any("error", err)) - return err + return nil, nil, err } defer contents.Close() + r := csv.NewReader(contents) tokens, err := r.ReadAll() if err != nil { - fmt.Println("File reading error", err) - return err + return nil, nil, fmt.Errorf("file reading error: %w", err) + } + + for _, row := range tokens { + if len(row) != 3 { + return nil, nil, fmt.Errorf("invalid row format, expected: access_id,friendly_name,hash") + } + accessID, friendlyName, hash := AccessID(row[0]), FriendlyName(row[1]), row[2] + tm[accessID] = hash + im.Register(accessID, friendlyName) } - for _, acctToken := range tokens { - acct, hash := acctToken[0], acctToken[1] - tm[acct] = hash + return tm, im, nil +} + +// Register adds a mapping between an AccessID and FriendlyName +func (im *IdentityMap) Register(id AccessID, name FriendlyName) { + im.mu.Lock() + defer im.mu.Unlock() + im.IDToName[id] = name + im.NameToID[name] = id +} + +// GetID retrieves the AccessID for a given FriendlyName +func (im *IdentityMap) GetID(name FriendlyName) (AccessID, bool) { + im.mu.RLock() + defer im.mu.RUnlock() + id, exists := im.NameToID[name] + return id, exists +} + +// GetName retrieves the FriendlyName for a given AccessID +func (im *IdentityMap) GetName(id AccessID) (FriendlyName, bool) { + im.mu.RLock() + defer im.mu.RUnlock() + name, exists := im.IDToName[id] + return name, exists +} + +// GenerateAccessID creates a new random access identifier +func GenerateAccessID() (AccessID, error) { + idBytes := make([]byte, 16) // 16 bytes = 128 bits + if _, err := rand.Read(idBytes); err != nil { + return "", fmt.Errorf("failed to generate access ID: %w", err) } - return err + return AccessID(hex.EncodeToString(idBytes)), nil } // GenerateNewToken generates a new secure random token and its bcrypt hash |