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
This commit is contained in:
Aska committed 2026-08-12 12:21:36 +08:00
1 parent 6811480786
commit fbb331ed4b
4 files changed
+158 -10

No files matched your search

+2 -1
View File
@@ -176,7 +176,8 @@ func main() {
log.Printf("[orchestrator] pipeline ready")
// === 12. Gateway ===
authMgr := api.NewStubAuthManager()
userStore := api.NewMemUserStore(cfg.AdminPassword)
authMgr := api.NewRealAuthManager(cfg.JWTSecret, cfg.JWTExpiryHours, userStore)
// Build dependencies for gateway handlers
gwDeps := api.Dependencies{
+124
View File
@@ -0,0 +1,124 @@
// 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)
}
+11
View File
@@ -54,3 +54,14 @@ func (m *StubAuthManager) Login(username, password string) (string, error) {
m.tokens[token] = TokenInfo{UserID: username, Username: username, Role: user.Role}
return token, nil
}
// Register creates a new user and returns a stub token.
func (m *StubAuthManager) Register(username, password string) (string, error) {
if _, exists := m.users[username]; exists {
return "", http.ErrNoCookie
}
m.users[username] = UserInfo{Username: username, Password: password, Role: "user"}
token := "stub-token-" + username
m.tokens[token] = TokenInfo{UserID: username, Username: username, Role: "user"}
return token, nil
}
+21 -9
View File
@@ -15,9 +15,11 @@ import (
// --- Interfaces for injected services ---
// AuthManager validates JWT tokens.
// AuthManager validates JWT tokens and handles login/register.
type AuthManager interface {
ValidateToken(token string) (userID, username, role string, err error)
Login(username, password string) (token string, err error)
Register(username, password string) (token string, err error)
}
// MemoryEntry is a simplified memory entry for API responses.
@@ -161,12 +163,7 @@ func (r *Router) loginHandler(w http.ResponseWriter, req *http.Request) {
}
json.NewDecoder(req.Body).Decode(&body)
mgr, ok := r.deps.AuthMgr.(*StubAuthManager)
if !ok {
writeJSON(w, 500, M{"error": "auth not configured"})
return
}
token, err := mgr.Login(body.Username, body.Password)
token, err := r.deps.AuthMgr.Login(body.Username, body.Password)
if err != nil {
writeJSON(w, 401, M{"error": "invalid credentials"})
return
@@ -174,8 +171,23 @@ func (r *Router) loginHandler(w http.ResponseWriter, req *http.Request) {
writeJSON(w, 200, M{"token": token})
}
func (r *Router) registerHandler(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, 200, M{"message": "registration not implemented"})
func (r *Router) registerHandler(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
writeJSON(w, 405, M{"error": "POST required"})
return
}
var body struct {
Username string `json:"username"`
Password string `json:"password"`
}
json.NewDecoder(req.Body).Decode(&body)
token, err := r.deps.AuthMgr.Register(body.Username, body.Password)
if err != nil {
writeJSON(w, 400, M{"error": err.Error()})
return
}
writeJSON(w, 201, M{"token": token})
}
// --- Sessions ---