feat(api): implement API key management and authentication enhancements
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
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.
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user