feat(ui): add identity management for tenants and users
CI / build-and-test (push) Successful in 1m47s
CI / build-and-test (push) Successful in 1m47s
* Implement tenant and user creation in IdentityPage * Add API calls for managing tenants and users * Introduce tenant-scoped API requests * Update sidebar to include identity navigation * Create BooleanStatusBadge component for key status
This commit is contained in:
+113
-2
@@ -1,14 +1,27 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.warky.dev/wdevs/amcs/internal/config"
|
||||
)
|
||||
|
||||
type Keyring struct {
|
||||
keys []config.APIKey
|
||||
mu sync.RWMutex
|
||||
keys []config.APIKey
|
||||
tenantsByKeyID map[string]string
|
||||
managed map[string]managedKey
|
||||
}
|
||||
|
||||
type managedKey struct {
|
||||
secretHash string
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func NewKeyring(keys []config.APIKey) (*Keyring, error) {
|
||||
@@ -16,14 +29,112 @@ func NewKeyring(keys []config.APIKey) (*Keyring, error) {
|
||||
return nil, fmt.Errorf("keyring requires at least one key")
|
||||
}
|
||||
|
||||
return &Keyring{keys: append([]config.APIKey(nil), keys...)}, nil
|
||||
tenantsByKeyID := make(map[string]string)
|
||||
for _, key := range keys {
|
||||
if tenantID := strings.TrimSpace(key.TenantID); tenantID != "" {
|
||||
tenantsByKeyID[key.ID] = tenantID
|
||||
}
|
||||
}
|
||||
return &Keyring{keys: append([]config.APIKey(nil), keys...), tenantsByKeyID: tenantsByKeyID, managed: make(map[string]managedKey)}, nil
|
||||
}
|
||||
|
||||
// NewManagedKeyring is used when API credentials are administered in the
|
||||
// database rather than supplied through static configuration.
|
||||
func NewManagedKeyring() *Keyring {
|
||||
return &Keyring{tenantsByKeyID: make(map[string]string), managed: make(map[string]managedKey)}
|
||||
}
|
||||
|
||||
func (k *Keyring) Lookup(value string) (string, bool) {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
for _, key := range k.keys {
|
||||
if subtle.ConstantTimeCompare([]byte(key.Value), []byte(value)) == 1 {
|
||||
return key.ID, true
|
||||
}
|
||||
}
|
||||
hash := secretHash(value)
|
||||
for keyID, key := range k.managed {
|
||||
if key.enabled && subtle.ConstantTimeCompare([]byte(key.secretHash), []byte(hash)) == 1 {
|
||||
return keyID, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// TenantForKey returns the tenant boundary assigned to keyID. Unassigned
|
||||
// configured keys retain the historical key-ID boundary for compatibility.
|
||||
func (k *Keyring) TenantForKey(keyID string) string {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
if tenantID := k.tenantsByKeyID[keyID]; tenantID != "" {
|
||||
return tenantID
|
||||
}
|
||||
return keyID
|
||||
}
|
||||
|
||||
func (k *Keyring) AssignTenant(keyID, tenantID string) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
if tenantID == "" {
|
||||
delete(k.tenantsByKeyID, keyID)
|
||||
return
|
||||
}
|
||||
k.tenantsByKeyID[keyID] = tenantID
|
||||
}
|
||||
|
||||
func (k *Keyring) AddManaged(keyID, secretHash string, enabled bool) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
k.managed[keyID] = managedKey{secretHash: secretHash, enabled: enabled}
|
||||
}
|
||||
|
||||
func (k *Keyring) ConfiguredKeys() []config.APIKey {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
return append([]config.APIKey(nil), k.keys...)
|
||||
}
|
||||
|
||||
func (k *Keyring) IsConfigured(keyID string) bool {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
for _, key := range k.keys {
|
||||
if key.ID == keyID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *Keyring) IsSuperadmin(keyID string) bool {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
for _, key := range k.keys {
|
||||
if key.ID == keyID {
|
||||
return key.Superadmin
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *Keyring) SetManagedEnabled(keyID string, enabled bool) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
if key, ok := k.managed[keyID]; ok {
|
||||
key.enabled = enabled
|
||||
k.managed[keyID] = key
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateSecret() (string, string, error) {
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
secret := "amcs_" + hex.EncodeToString(b)
|
||||
return secret, secretHash(secret), nil
|
||||
}
|
||||
|
||||
func secretHash(secret string) string {
|
||||
sum := sha256.Sum256([]byte(secret))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user