package service import ( "crypto/sha256" "encoding/hex" "errors" "fmt" "golang.org/x/crypto/bcrypt" "zeromesh/internal/config" "zeromesh/internal/middleware" "zeromesh/internal/model" "zeromesh/internal/repository" ) type AuthService struct { userRepo *repository.UserRepo jwtCfg config.JWTConfig } func NewAuthService(userRepo *repository.UserRepo, jwtCfg config.JWTConfig) *AuthService { return &AuthService{userRepo: userRepo, jwtCfg: jwtCfg} } func (s *AuthService) Login(username, password string) (string, *model.User, error) { user, err := s.userRepo.FindByUsername(username) if err != nil { return "", nil, errors.New("invalid credentials") } // Migrate old SHA256 hashes to bcrypt on login if len(user.Password) < 4 || user.Password[:4] != "$2a$" { if sha256Hash(password) != user.Password { return "", nil, errors.New("invalid credentials") } hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", nil, fmt.Errorf("migrate password: %w", err) } user.Password = string(hashed) _ = s.userRepo.UpdatePassword(user.ID, user.Password) } else if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { return "", nil, errors.New("invalid credentials") } token, err := middleware.GenerateToken(s.jwtCfg.Secret, user.ID, user.Username, user.Role, s.jwtCfg.ExpireHours) if err != nil { return "", nil, fmt.Errorf("generate token: %w", err) } user.Token = token return token, user, nil } func (s *AuthService) Register(username, password string) (*model.User, string, error) { hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return nil, "", fmt.Errorf("hash password: %w", err) } user := &model.User{ Username: username, Password: string(hashed), Role: "user", } if err := s.userRepo.Create(user); err != nil { return nil, "", err } token, err := middleware.GenerateToken(s.jwtCfg.Secret, user.ID, user.Username, user.Role, s.jwtCfg.ExpireHours) if err != nil { return nil, "", fmt.Errorf("generate token: %w", err) } user.Token = token return user, token, nil } func (s *AuthService) InitAdmin(username, password string) (*model.User, string, error) { count, err := s.userRepo.Count() if err != nil { return nil, "", err } if count > 0 { return nil, "", fmt.Errorf("admin already exists") } hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return nil, "", fmt.Errorf("hash password: %w", err) } user := &model.User{ Username: username, Password: string(hashed), Role: "admin", QuotaNetworks: -1, QuotaNodes: -1, } if err := s.userRepo.Create(user); err != nil { return nil, "", err } token, err := middleware.GenerateToken(s.jwtCfg.Secret, user.ID, user.Username, user.Role, s.jwtCfg.ExpireHours) if err != nil { return nil, "", fmt.Errorf("generate token: %w", err) } user.Token = token return user, token, nil } func sha256Hash(password string) string { h := sha256.Sum256([]byte(password)) return hex.EncodeToString(h[:]) } func (s *AuthService) CheckAdmin() (bool, error) { return s.userRepo.HasAdmin() }