material_manage_system/internal/service/auth.go
2026-09-16 23:30:25 +08:00

214 lines
5.3 KiB
Go

package service
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"time"
"github.com/wmms/wmms/internal/model"
"github.com/wmms/wmms/internal/repository"
"golang.org/x/crypto/bcrypt"
)
var (
ErrInvalidCredentials = errors.New("invalid username or password")
ErrUserNotFound = errors.New("user not found")
)
type AuthService struct {
userRepo *repository.UserRepository
}
func NewAuthService() *AuthService {
return &AuthService{
userRepo: repository.NewUserRepository(),
}
}
// SimpleToken represents a simple JWT-like token
type SimpleToken struct {
UserID int64 `json:"user_id"`
Username string `json:"username"`
Roles []string `json:"roles"`
Exp int64 `json:"exp"`
}
// GenerateToken creates a simple token for the user
func (s *AuthService) GenerateToken(user *model.User) (string, error) {
exp := time.Now().Add(24 * time.Hour).Unix()
roleCodes := make([]string, 0, len(user.Roles))
for _, role := range user.Roles {
if role.Enabled {
roleCodes = append(roleCodes, role.Code)
}
}
token := SimpleToken{
UserID: user.ID,
Username: user.Username,
Roles: roleCodes,
Exp: exp,
}
// Create header
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`))
// Create payload
payloadBytes, err := json.Marshal(token)
if err != nil {
return "", err
}
payload := base64.RawURLEncoding.EncodeToString(payloadBytes)
// Create signature using HMAC-SHA256 (cryptographically secure)
signatureInput := header + "." + payload
mac := hmac.New(sha256.New, []byte("warehouse-mms-secret-key"))
mac.Write([]byte(signatureInput))
signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
return header + "." + payload + "." + signature, nil
}
// ValidateToken validates a token and returns the user info
func (s *AuthService) ValidateToken(tokenString string) (*SimpleToken, error) {
parts := splitToken(tokenString)
if len(parts) != 3 {
return nil, errors.New("invalid token format")
}
header, payload, signature := parts[0], parts[1], parts[2]
// Verify signature using HMAC-SHA256 (cryptographically secure)
signatureInput := header + "." + payload
mac := hmac.New(sha256.New, []byte("warehouse-mms-secret-key"))
mac.Write([]byte(signatureInput))
expectedSig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(signature), []byte(expectedSig)) {
return nil, errors.New("invalid token signature")
}
// Decode payload
payloadBytes, err := base64.RawURLEncoding.DecodeString(payload)
if err != nil {
return nil, err
}
var token SimpleToken
if err := json.Unmarshal(payloadBytes, &token); err != nil {
return nil, err
}
// Check expiration
if time.Now().Unix() > token.Exp {
return nil, errors.New("token expired")
}
return &token, nil
}
func splitToken(token string) []string {
var parts []string
start := 0
for i := 0; i < len(token); i++ {
if token[i] == '.' {
parts = append(parts, token[start:i])
start = i + 1
}
}
parts = append(parts, token[start:])
return parts
}
// Login authenticates a user and returns a token
func (s *AuthService) Login(username, password string) (*model.LoginResponse, error) {
user, err := s.userRepo.GetByUsername(username)
if err != nil {
return nil, ErrInvalidCredentials
}
// Verify password
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
return nil, ErrInvalidCredentials
}
// Generate token
token, err := s.GenerateToken(user)
if err != nil {
return nil, err
}
return &model.LoginResponse{
Token: token,
User: *user,
}, nil
}
// GetUserByID returns a user by ID
func (s *AuthService) GetUserByID(id int64) (*model.User, error) {
return s.userRepo.GetByID(id)
}
// ListUsers returns all users with pagination
func (s *AuthService) ListUsers(search string, limit, offset int) ([]model.User, int64, error) {
return s.userRepo.List(search, limit, offset)
}
// CreateUser creates a new user
func (s *AuthService) CreateUser(username, password, description string, roleIDs []int64) (*model.User, error) {
// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
user := &model.User{
Username: username,
Password: string(hashedPassword),
Description: description,
}
if err := s.userRepo.Create(user, roleIDs); err != nil {
return nil, err
}
return s.userRepo.GetByID(user.ID)
}
// UpdateUser updates a user (admin function)
func (s *AuthService) UpdateUser(id int64, description string, roleIDs []int64, newPassword string) error {
user, err := s.userRepo.GetByID(id)
if err != nil {
return err
}
user.Description = description
if newPassword != "" {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return err
}
user.Password = string(hashedPassword)
return s.userRepo.UpdateWithPassword(user, roleIDs)
}
return s.userRepo.Update(user, roleIDs)
}
// DeleteUser deletes a user
func (s *AuthService) DeleteUser(id int64) error {
return s.userRepo.Delete(id)
}
// HashPassword hashes a password using bcrypt
func HashPassword(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}