Files
Cyrene_v2/backend/services/gateway/api/auth.go
T
Aska fbb331ed4b feat(auth): replace stub auth with real JWT + bcrypt
- RealAuthManager: JWT signing/validation + bcrypt password hashing
- MemUserStore: in-memory user store with default admin
- Register endpoint: user creation with hashed password
- Login endpoint: bcrypt verification + JWT generation
- AuthManager interface extended with Login/Register methods
2026-08-12 12:21:36 +08:00

125 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 a default admin.
func NewMemUserStore(adminPassword string) *MemUserStore {
s := &MemUserStore{users: make(map[string]*UserRecord)}
hash, _ := auth.HashPassword(adminPassword)
s.users["admin"] = &UserRecord{
UserID: "admin", Username: "admin",
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)
}