Добавлен основные классы для сервиса авторизаци
This commit is contained in:
142
internal/service/auth.go
Normal file
142
internal/service/auth.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"authorization/internal"
|
||||
"authorization/internal/config"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
)
|
||||
|
||||
const (
|
||||
refresh = "refresh"
|
||||
access = "access"
|
||||
)
|
||||
|
||||
type tokenClaims struct {
|
||||
jwt.StandardClaims
|
||||
UserId int `json:"user_id"`
|
||||
UserRole string `json:"user_role"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
type AuthService struct {
|
||||
userService UserService
|
||||
tokenConfigs config.TokenConfig
|
||||
}
|
||||
|
||||
func newAuthService(userService UserService) *AuthService {
|
||||
return &AuthService{userService: userService}
|
||||
}
|
||||
func (s *AuthService) CreateUser(user internal.User) (int, error) {
|
||||
user.Password = s.generatePasswordHash(user.Password)
|
||||
return s.userService.CreateUser(user)
|
||||
}
|
||||
|
||||
func (s *AuthService) GenerateToken(username string, password string) (string, string, error) {
|
||||
user, err := s.userService.GetUser(username, s.generatePasswordHash(password))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
accessTokenClaims := s.generateClaims(user.Id, user.UserRole, access)
|
||||
|
||||
refreshTokenClaims := s.generateClaims(user.Id, user.UserRole, refresh)
|
||||
|
||||
accessToken, err := accessTokenClaims.SignedString([]byte(s.tokenConfigs.AccessToken.GetSecretBytes()))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
refreshToken, err := refreshTokenClaims.SignedString([]byte(s.tokenConfigs.RefreshToken.GetSecretBytes()))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return accessToken, refreshToken, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) ChangeUserRole(username string, userrole string) (string, error) {
|
||||
user, err := s.userService.ChangeUserRole(username, userrole)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) generateClaims(userId int, userRole string, tokenType string) *jwt.Token {
|
||||
tokenTTL := s.tokenConfigs.RefreshToken.GetTTL()
|
||||
if tokenType == access {
|
||||
tokenTTL = s.tokenConfigs.AccessToken.GetTTL()
|
||||
}
|
||||
return jwt.NewWithClaims(jwt.SigningMethodHS256, &tokenClaims{
|
||||
jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(tokenTTL).Unix(),
|
||||
IssuedAt: time.Now().Unix(),
|
||||
},
|
||||
userId,
|
||||
userRole,
|
||||
tokenType,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *AuthService) generatePasswordHash(password string) string {
|
||||
hash := sha1.New()
|
||||
hash.Write([]byte(password))
|
||||
return fmt.Sprintf("%x", hash)
|
||||
}
|
||||
|
||||
func (s *AuthService) ParseToken(accessToken string) (string, error) {
|
||||
claims, err := s.parseTokenWithSecret(accessToken, s.tokenConfigs.AccessToken.GetSecretBytes())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return claims.UserRole, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) RefreshToken(refreshToken string) (string, string, error) {
|
||||
// Парсим refresh token
|
||||
claims, err := s.parseTokenWithSecret(refreshToken, s.tokenConfigs.RefreshToken.GetSecretBytes())
|
||||
if err != nil {
|
||||
return "", "", errors.New("invalid refresh token")
|
||||
}
|
||||
|
||||
// Проверяем, что это именно refresh token
|
||||
if claims.TokenType != refresh {
|
||||
return "", "", errors.New("token is not a refresh token")
|
||||
}
|
||||
|
||||
// Генерируем новую пару токенов
|
||||
newAccessTokenClaims := s.generateClaims(claims.UserId, claims.UserRole, access)
|
||||
newRefreshTokenClaims := s.generateClaims(claims.UserId, claims.UserRole, refresh)
|
||||
|
||||
newAccessToken, err := newAccessTokenClaims.SignedString([]byte(s.tokenConfigs.AccessToken.GetSecretBytes()))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
newRefreshToken, err := newRefreshTokenClaims.SignedString([]byte(s.tokenConfigs.RefreshToken.GetSecretBytes()))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return newAccessToken, newRefreshToken, nil
|
||||
}
|
||||
|
||||
// parseTokenWithSecret - общий метод для парсинга токена с заданным секретным ключом
|
||||
func (s *AuthService) parseTokenWithSecret(tokenString string, secret []byte) (*tokenClaims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &tokenClaims{}, func(t *jwt.Token) (interface{}, error) {
|
||||
return secret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*tokenClaims)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid token claims")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
117
internal/service/auth_test.go
Normal file
117
internal/service/auth_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"authorization/internal/config"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAuthService_ParseToken(t *testing.T) {
|
||||
// Настройка тестового сервиса
|
||||
service := &AuthService{
|
||||
tokenConfigs: config.TokenConfig{
|
||||
AccessToken: config.TokenSettings{
|
||||
TTLInMinutes: 15,
|
||||
SecretWord: "test-secret-key",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Valid token", func(t *testing.T) {
|
||||
// Создаем валидный токен
|
||||
claims := &tokenClaims{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
|
||||
IssuedAt: time.Now().Unix(),
|
||||
},
|
||||
UserId: 1,
|
||||
UserRole: "admin",
|
||||
TokenType: "access",
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte("test-secret-key"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Парсим токен
|
||||
role, err := service.ParseToken(tokenString)
|
||||
|
||||
// Проверяем результат
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "admin", role)
|
||||
})
|
||||
|
||||
t.Run("Expired token", func(t *testing.T) {
|
||||
// Создаем истекший токен (истек 1 час назад)
|
||||
claims := &tokenClaims{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(-1 * time.Hour).Unix(),
|
||||
IssuedAt: time.Now().Add(-2 * time.Hour).Unix(),
|
||||
},
|
||||
UserId: 1,
|
||||
UserRole: "admin",
|
||||
TokenType: "access",
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte("test-secret-key"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Парсим истекший токен
|
||||
role, err := service.ParseToken(tokenString)
|
||||
|
||||
// Проверяем, что получили ошибку
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, role)
|
||||
|
||||
// Проверяем, что это именно ошибка истечения срока
|
||||
if ve, ok := err.(*jwt.ValidationError); ok {
|
||||
assert.True(t, ve.Errors&jwt.ValidationErrorExpired != 0, "Expected token expired error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Invalid signature", func(t *testing.T) {
|
||||
// Создаем токен с другим секретным ключом
|
||||
claims := &tokenClaims{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
|
||||
IssuedAt: time.Now().Unix(),
|
||||
},
|
||||
UserId: 1,
|
||||
UserRole: "admin",
|
||||
TokenType: "access",
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte("wrong-secret-key"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Пытаемся парсить токен с неправильной подписью
|
||||
role, err := service.ParseToken(tokenString)
|
||||
|
||||
// Проверяем, что получили ошибку
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, role)
|
||||
})
|
||||
|
||||
t.Run("Malformed token", func(t *testing.T) {
|
||||
// Пытаемся парсить невалидный токен
|
||||
role, err := service.ParseToken("invalid.token.string")
|
||||
|
||||
// Проверяем, что получили ошибку
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, role)
|
||||
})
|
||||
|
||||
t.Run("Empty token", func(t *testing.T) {
|
||||
// Пытаемся парсить пустой токен
|
||||
role, err := service.ParseToken("")
|
||||
|
||||
// Проверяем, что получили ошибку
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, role)
|
||||
})
|
||||
}
|
||||
29
internal/service/service.go
Normal file
29
internal/service/service.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"authorization/internal"
|
||||
"authorization/internal/repository"
|
||||
)
|
||||
|
||||
type Authorization interface {
|
||||
CreateUser(internal.User) (int, error)
|
||||
GenerateToken(username string, password string) (accessToken string, refreshToken string, err error)
|
||||
ParseToken(token string) (string, error)
|
||||
RefreshToken(refreshToken string) (accessToken string, newRefreshToken string, err error)
|
||||
ChangeUserRole(username string, newRole string) (string, error)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Authorization
|
||||
}
|
||||
|
||||
func NewServices(repository *repository.Repository) *Service {
|
||||
|
||||
userService := newUserService(repository)
|
||||
|
||||
authService := newAuthService(userService)
|
||||
|
||||
return &Service{
|
||||
Authorization: authService,
|
||||
}
|
||||
}
|
||||
44
internal/service/user.go
Normal file
44
internal/service/user.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"authorization/internal"
|
||||
"authorization/internal/repository"
|
||||
)
|
||||
|
||||
type UserService interface {
|
||||
CreateUser(internal.User) (int, error)
|
||||
ChangeUserRole(username string, Role string) (string, error)
|
||||
GetUser(username string, hashedPassword string) (*internal.User, error)
|
||||
}
|
||||
|
||||
type UserServiceImpl struct {
|
||||
repo repository.UserResository
|
||||
}
|
||||
|
||||
func newUserService(repo repository.UserResository) *UserServiceImpl {
|
||||
return &UserServiceImpl{repo: repo}
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) CreateUser(user internal.User) (int, error) {
|
||||
return s.repo.CreateUser(user)
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) ChangeUserRole(username string, userRole string) (string, error) {
|
||||
newRole, err := internal.FromString(userRole)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
user, err := s.repo.UpdateUserRole(username, newRole)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) GetUser(username string, hashedPassword string) (*internal.User, error) {
|
||||
user, err := s.repo.GetUser(username, hashedPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
Reference in New Issue
Block a user