CI / changes (push) Successful in 7s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 28s
CI / go (push) Failing after 24s
CI / bird2 (push) Has been skipped
CI / release (push) Has been skipped
- Added endpoints for managing API keys, including creation, retrieval, updating, and revocation. - Introduced a new Auth session endpoint to retrieve current tenant and role information. - Updated the authentication middleware to support API key-based authentication and track last used timestamps. - Enhanced documentation to reflect new API key functionalities and usage guidelines. - Improved logging for demo authentication scenarios.
185 lines
4.0 KiB
Go
185 lines
4.0 KiB
Go
package store
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"evobgp/internal/authkey"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func (m *Memory) ListAPIKeys(tenantID string) ([]*APIKey, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
var out []*APIKey
|
|
for _, rec := range m.apiKeys {
|
|
if rec.TenantID == tenantID {
|
|
out = append(out, apiKeyCopy(&rec.APIKey))
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) GetAPIKey(tenantID, id string) (*APIKey, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
rec, ok := m.apiKeys[id]
|
|
if !ok || rec.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
return apiKeyCopy(&rec.APIKey), nil
|
|
}
|
|
|
|
func (m *Memory) CreateAPIKey(tenantID string, in *APIKeyCreate) (*APIKeyWithSecret, error) {
|
|
if in == nil || strings.TrimSpace(in.Name) == "" || !ValidAPIKeyRole(in.Role) {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
tok, err := authkey.GenerateToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
now := time.Now().UTC()
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.tenants[tenantID]; !ok {
|
|
return nil, ErrTenantScope
|
|
}
|
|
id := uuid.NewString()
|
|
k := &apiKeyRec{
|
|
APIKey: APIKey{
|
|
ID: id,
|
|
TenantID: tenantID,
|
|
Name: strings.TrimSpace(in.Name),
|
|
Role: strings.ToLower(strings.TrimSpace(in.Role)),
|
|
Prefix: authkey.Prefix(tok),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
ExpiresAt: in.ExpiresAt,
|
|
},
|
|
TokenHash: authkey.HashToken(tok),
|
|
}
|
|
m.apiKeys[id] = k
|
|
return &APIKeyWithSecret{APIKey: *apiKeyCopy(&k.APIKey), Token: tok}, nil
|
|
}
|
|
|
|
func (m *Memory) UpdateAPIKey(tenantID, id string, patch *APIKeyPatch) (*APIKey, error) {
|
|
if patch == nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
rec, ok := m.apiKeys[id]
|
|
if !ok || rec.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if rec.RevokedAt != nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
if patch.Name != nil {
|
|
n := strings.TrimSpace(*patch.Name)
|
|
if n == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
rec.Name = n
|
|
}
|
|
if patch.Role != nil {
|
|
if !ValidAPIKeyRole(*patch.Role) {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
rec.Role = strings.ToLower(strings.TrimSpace(*patch.Role))
|
|
}
|
|
if patch.ClearExpiresAt {
|
|
rec.ExpiresAt = nil
|
|
} else if patch.ExpiresAt != nil {
|
|
rec.ExpiresAt = patch.ExpiresAt
|
|
}
|
|
rec.UpdatedAt = time.Now().UTC()
|
|
return apiKeyCopy(&rec.APIKey), nil
|
|
}
|
|
|
|
func (m *Memory) RevokeAPIKey(tenantID, id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
rec, ok := m.apiKeys[id]
|
|
if !ok || rec.TenantID != tenantID {
|
|
return ErrNotFound
|
|
}
|
|
now := time.Now().UTC()
|
|
rec.RevokedAt = &now
|
|
rec.UpdatedAt = now
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) RotateAPIKey(tenantID, id string) (*APIKeyWithSecret, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
rec, ok := m.apiKeys[id]
|
|
if !ok || rec.TenantID != tenantID {
|
|
return nil, ErrNotFound
|
|
}
|
|
if rec.RevokedAt != nil {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
tok, err := authkey.GenerateToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
now := time.Now().UTC()
|
|
rec.TokenHash = authkey.HashToken(tok)
|
|
rec.Prefix = authkey.Prefix(tok)
|
|
rec.UpdatedAt = now
|
|
return &APIKeyWithSecret{APIKey: *apiKeyCopy(&rec.APIKey), Token: tok}, nil
|
|
}
|
|
|
|
func (m *Memory) ListActiveAPIKeyHashes() ([]APIKeyAuthRow, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
now := time.Now().UTC()
|
|
var out []APIKeyAuthRow
|
|
for _, rec := range m.apiKeys {
|
|
if rec.RevokedAt != nil {
|
|
continue
|
|
}
|
|
if rec.ExpiresAt != nil && !rec.ExpiresAt.After(now) {
|
|
continue
|
|
}
|
|
out = append(out, APIKeyAuthRow{
|
|
ID: rec.ID,
|
|
TenantID: rec.TenantID,
|
|
Role: rec.Role,
|
|
TokenHash: append([]byte(nil), rec.TokenHash...),
|
|
})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) TouchAPIKeyLastUsed(id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
rec, ok := m.apiKeys[id]
|
|
if !ok {
|
|
return ErrNotFound
|
|
}
|
|
now := time.Now().UTC()
|
|
rec.LastUsedAt = &now
|
|
return nil
|
|
}
|
|
|
|
func apiKeyCopy(k *APIKey) *APIKey {
|
|
cp := *k
|
|
if k.ExpiresAt != nil {
|
|
t := *k.ExpiresAt
|
|
cp.ExpiresAt = &t
|
|
}
|
|
if k.RevokedAt != nil {
|
|
t := *k.RevokedAt
|
|
cp.RevokedAt = &t
|
|
}
|
|
if k.LastUsedAt != nil {
|
|
t := *k.LastUsedAt
|
|
cp.LastUsedAt = &t
|
|
}
|
|
return &cp
|
|
}
|