- POST /api/v1/admin/restart: admin-only restart (forks new process) - Fix: MemUserStore now uses cfg.AdminUsername instead of hardcoded 'admin' - RestartFunc injected by main.go for clean process fork + exit
127 lines
3.3 KiB
Go
127 lines
3.3 KiB
Go
// Package api — RealAuthManager provides production JWT + bcrypt authentication.
|
|
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
|
|
)
|
|
|
|
// UserRecord holds a stored user's credentials.
|
|
type UserRecord struct {
|
|
UserID string
|
|
Username string
|
|
PasswordHash string
|
|
Role string // "admin" | "user"
|
|
}
|
|
|
|
// UserStore is the interface for user persistence.
|
|
type UserStore interface {
|
|
FindByUsername(username string) (*UserRecord, error)
|
|
Create(user *UserRecord) error
|
|
}
|
|
|
|
// MemUserStore is an in-memory user store for development and testing.
|
|
type MemUserStore struct {
|
|
mu sync.RWMutex
|
|
users map[string]*UserRecord
|
|
}
|
|
|
|
// NewMemUserStore creates an in-memory user store with the configured admin account.
|
|
func NewMemUserStore(adminUsername, adminPassword string) *MemUserStore {
|
|
s := &MemUserStore{users: make(map[string]*UserRecord)}
|
|
|
|
hash, _ := auth.HashPassword(adminPassword)
|
|
s.users[adminUsername] = &UserRecord{
|
|
UserID: adminUsername,
|
|
Username: adminUsername,
|
|
PasswordHash: hash,
|
|
Role: "admin",
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *MemUserStore) FindByUsername(username string) (*UserRecord, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
u, ok := s.users[username]
|
|
if !ok {
|
|
return nil, fmt.Errorf("user %s not found", username)
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
func (s *MemUserStore) Create(user *UserRecord) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if _, exists := s.users[user.Username]; exists {
|
|
return fmt.Errorf("username %s already exists", user.Username)
|
|
}
|
|
s.users[user.Username] = user
|
|
return nil
|
|
}
|
|
|
|
// RealAuthManager implements AuthManager using JWT + bcrypt.
|
|
type RealAuthManager struct {
|
|
tokenMgr *auth.TokenManager
|
|
store UserStore
|
|
}
|
|
|
|
// NewRealAuthManager creates a production-grade auth manager.
|
|
func NewRealAuthManager(secret string, expiryHours int, store UserStore) *RealAuthManager {
|
|
return &RealAuthManager{
|
|
tokenMgr: auth.NewTokenManager(secret, expiryHours),
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
// ValidateToken parses and validates a JWT token.
|
|
func (m *RealAuthManager) ValidateToken(tokenStr string) (string, string, string, error) {
|
|
claims, err := m.tokenMgr.Validate(tokenStr)
|
|
if err != nil {
|
|
return "", "", "", err
|
|
}
|
|
return claims.UserID, claims.Username, claims.Role, nil
|
|
}
|
|
|
|
// Login authenticates a user with username and password.
|
|
func (m *RealAuthManager) Login(username, password string) (string, error) {
|
|
user, err := m.store.FindByUsername(username)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid credentials")
|
|
}
|
|
if !auth.CheckPassword(password, user.PasswordHash) {
|
|
return "", fmt.Errorf("invalid credentials")
|
|
}
|
|
return m.tokenMgr.Generate(user.UserID, user.Username, user.Role)
|
|
}
|
|
|
|
// Register creates a new user account and returns a JWT token.
|
|
func (m *RealAuthManager) Register(username, password string) (string, error) {
|
|
if username == "" || password == "" {
|
|
return "", fmt.Errorf("username and password required")
|
|
}
|
|
if len(password) < 6 {
|
|
return "", fmt.Errorf("password must be at least 6 characters")
|
|
}
|
|
|
|
hash, err := auth.HashPassword(password)
|
|
if err != nil {
|
|
return "", fmt.Errorf("hash password: %w", err)
|
|
}
|
|
|
|
user := &UserRecord{
|
|
UserID: username,
|
|
Username: username,
|
|
PasswordHash: hash,
|
|
Role: "user",
|
|
}
|
|
|
|
if err := m.store.Create(user); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return m.tokenMgr.Generate(user.UserID, user.Username, user.Role)
|
|
}
|