aboutsummaryrefslogtreecommitdiff
path: root/internal/authz/model.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--internal/authz/model.go131
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