Добавлен основные классы для сервиса авторизаци
This commit is contained in:
@@ -1,37 +1,49 @@
|
||||
package internal
|
||||
|
||||
type RegistrationUser struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
const (
|
||||
Student UserRole = iota
|
||||
Teacher
|
||||
Admin
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Username string
|
||||
Password string
|
||||
UserRole UserRole
|
||||
Id int `json:"-" db:"id"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
UserRole string `json:"-" db:"role"`
|
||||
}
|
||||
|
||||
func (user *User) ChangeRole(userRole UserRole) {
|
||||
user.UserRole = userRole
|
||||
type AuthUser struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type UserRole int
|
||||
type RefreshTokenRequest struct {
|
||||
RefreshToken string `json:"refreshToken" binding:"required"`
|
||||
}
|
||||
|
||||
func (role UserRole) String() string {
|
||||
switch role {
|
||||
case Student:
|
||||
return "student"
|
||||
case Teacher:
|
||||
return "teacher"
|
||||
case Admin:
|
||||
return "admin"
|
||||
default:
|
||||
return "unknown"
|
||||
type UserRole string
|
||||
|
||||
const (
|
||||
Student UserRole = "student"
|
||||
Teacher UserRole = "teacher"
|
||||
Admin UserRole = "admin"
|
||||
)
|
||||
|
||||
func (userrole UserRole) ToString() string {
|
||||
return string(userrole)
|
||||
}
|
||||
|
||||
var ErrInvalidRole = errors.New("invalid role")
|
||||
|
||||
func FromString(in string) (UserRole, error) {
|
||||
switch in {
|
||||
case Student.ToString():
|
||||
return Student, nil
|
||||
case Teacher.ToString():
|
||||
return Teacher, nil
|
||||
case Admin.ToString():
|
||||
return Admin, nil
|
||||
}
|
||||
return Student, fmt.Errorf("%q is not a valid role: %w", in, ErrInvalidRole)
|
||||
}
|
||||
|
||||
42
internal/config/config.go
Normal file
42
internal/config/config.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Token TokenConfig `yaml:"token" json:"token"`
|
||||
Server ServerConfig `yaml:"server" json:"server"`
|
||||
DB DatabaseConfig `yaml:"db" json:"db"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port string `yaml:"port" json:"port"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Username string `yaml:"username" json:"username"`
|
||||
Host string `yaml:"host" json:"host"`
|
||||
Port string `yaml:"port" json:"port"`
|
||||
Sslmode string `yaml:"sslmode" json:"sslmode"`
|
||||
DBname string `yaml:"dbname" json:"dbname"`
|
||||
}
|
||||
|
||||
func LoadConfig(absolutePath string) (*Config, error) {
|
||||
config := &Config{}
|
||||
|
||||
file, err := os.Open(absolutePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
decoder := yaml.NewDecoder(file)
|
||||
if err := decoder.Decode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
22
internal/config/token.go
Normal file
22
internal/config/token.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
type TokenConfig struct {
|
||||
AccessToken TokenSettings `yaml:"accessToken" json:"accessToken"`
|
||||
RefreshToken TokenSettings `yaml:"refreshToken" json:"refreshToken"`
|
||||
}
|
||||
|
||||
type TokenSettings struct {
|
||||
TTLInMinutes int `yaml:"TTL-in-min" json:"TTL-in-min"`
|
||||
SecretWord string `yaml:"secretWord" json:"secretWord"`
|
||||
}
|
||||
|
||||
// Методы для удобства
|
||||
func (t *TokenSettings) GetTTL() time.Duration {
|
||||
return time.Duration(t.TTLInMinutes) * time.Minute
|
||||
}
|
||||
|
||||
func (t *TokenSettings) GetSecretBytes() []byte {
|
||||
return []byte(t.SecretWord)
|
||||
}
|
||||
@@ -1,13 +1,65 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"authorization/internal"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (h *Handler) signUp(c *gin.Context) {
|
||||
var input internal.User
|
||||
|
||||
if err := c.BindJSON(&input); err != nil {
|
||||
newErrorResponse(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
id, err := h.services.Authorization.CreateUser(input)
|
||||
if err != nil {
|
||||
newErrorResponse(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"id": id,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) signIn(c *gin.Context) {
|
||||
var input internal.AuthUser
|
||||
|
||||
if err := c.BindJSON(&input); err != nil {
|
||||
newErrorResponse(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
accesstoken, refreshToken, err := h.services.Authorization.GenerateToken(input.Username, input.Password)
|
||||
if err != nil {
|
||||
newErrorResponse(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"accessToken": accesstoken,
|
||||
"refreshToken": refreshToken,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) refresh(c *gin.Context) {
|
||||
var input internal.RefreshTokenRequest
|
||||
|
||||
if err := c.BindJSON(&input); err != nil {
|
||||
newErrorResponse(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
accessToken, refreshToken, err := h.services.Authorization.RefreshToken(input.RefreshToken)
|
||||
if err != nil {
|
||||
newErrorResponse(c, http.StatusUnauthorized, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"accessToken": accessToken,
|
||||
"refreshToken": refreshToken,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,17 +1,40 @@
|
||||
package handler
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"authorization/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
services *service.Service
|
||||
}
|
||||
|
||||
func NewHandler(services *service.Service) *Handler {
|
||||
return &Handler{
|
||||
services: services,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) InitRoutes() *gin.Engine {
|
||||
router := gin.New()
|
||||
|
||||
auth := router.Group("/auth")
|
||||
serviceRouter := router.Group("/auth-service")
|
||||
{
|
||||
auth.POST("/sign-up", h.signUp)
|
||||
auth.POST("/sign-in", h.signIn)
|
||||
auth := serviceRouter.Group("/auth")
|
||||
{
|
||||
auth.POST("/sign-up", h.signUp)
|
||||
auth.POST("/sign-in", h.signIn)
|
||||
auth.POST("/refresh", h.refresh)
|
||||
}
|
||||
api := router.Group("/api")
|
||||
{
|
||||
users := api.Group("/users", h.checkAdminIdentity)
|
||||
{
|
||||
users.POST("/:username", h.changeUserRole)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
42
internal/handler/middleware.go
Normal file
42
internal/handler/middleware.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"authorization/internal"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
authorizationHeader = "Authorization"
|
||||
roleKey = "user_role"
|
||||
)
|
||||
|
||||
func (h *Handler) checkAdminIdentity(c *gin.Context) {
|
||||
header := c.GetHeader(authorizationHeader)
|
||||
if header == "" {
|
||||
newErrorResponse(c, http.StatusUnauthorized, "Пустой header авторизации")
|
||||
return
|
||||
}
|
||||
|
||||
headerParts := strings.Split(header, " ")
|
||||
if len(headerParts) != 2 {
|
||||
newErrorResponse(c, http.StatusUnauthorized, "Невалидный токен JWT")
|
||||
return
|
||||
}
|
||||
|
||||
userRole, err := h.services.ParseToken(headerParts[1])
|
||||
|
||||
if userRole != string(internal.Admin) {
|
||||
newErrorResponse(c, http.StatusUnauthorized, "Недостаточно прав для выполнения запроса")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
newErrorResponse(c, http.StatusUnauthorized, "Ошибка при извлечении claims")
|
||||
return
|
||||
}
|
||||
|
||||
c.Set(roleKey, userRole)
|
||||
}
|
||||
15
internal/handler/response.go
Normal file
15
internal/handler/response.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type error struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func newErrorResponse(c *gin.Context, statusCode int, message string) {
|
||||
logrus.Error(message)
|
||||
c.AbortWithStatusJSON(statusCode, error{Message: message})
|
||||
}
|
||||
36
internal/handler/users.go
Normal file
36
internal/handler/users.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ChangeUserRoleRequest struct {
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
func (h *Handler) changeUserRole(c *gin.Context) {
|
||||
var input ChangeUserRoleRequest
|
||||
|
||||
if err := c.BindJSON(&input); err != nil {
|
||||
newErrorResponse(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
username := c.Param("username")
|
||||
if username == "" {
|
||||
newErrorResponse(c, http.StatusBadRequest, "Ошибка в строке запроса")
|
||||
return
|
||||
}
|
||||
role, err := h.services.ChangeUserRole(username, input.Role)
|
||||
|
||||
if err != nil {
|
||||
newErrorResponse(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"newRole": role,
|
||||
})
|
||||
}
|
||||
35
internal/repository/postgres.go
Normal file
35
internal/repository/postgres.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
const (
|
||||
usersTable = "users"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port string
|
||||
Username string
|
||||
Password string
|
||||
DBName string
|
||||
SSLMode string
|
||||
}
|
||||
|
||||
func NewPostgresDB(cfg Config) (*sql.DB, error) {
|
||||
db, err := sql.Open("postgres",
|
||||
fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=%s",
|
||||
cfg.Username, cfg.Password, cfg.Host, cfg.DBName, cfg.SSLMode))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
22
internal/repository/repository.go
Normal file
22
internal/repository/repository.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"authorization/internal"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type UserResository interface {
|
||||
CreateUser(user internal.User) (int, error)
|
||||
GetUser(username, password string) (internal.User, error)
|
||||
UpdateUserRole(username string, userrole internal.UserRole) (string, error)
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
UserResository
|
||||
}
|
||||
|
||||
func NewRepository(db *sql.DB) *Repository {
|
||||
return &Repository{
|
||||
UserResository: NewUserPostgres(db),
|
||||
}
|
||||
}
|
||||
44
internal/repository/user_postgres.go
Normal file
44
internal/repository/user_postgres.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"authorization/internal"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type UserPostgres struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewUserPostgres(db *sql.DB) *UserPostgres {
|
||||
return &UserPostgres{db: db}
|
||||
}
|
||||
|
||||
func (r *UserPostgres) CreateUser(user internal.User) (int, error) {
|
||||
var id int
|
||||
query := fmt.Sprintf("INSERT INTO %s(name,username,password_hash,role) values ($1,$2,$3,$4) RETURNING id", usersTable)
|
||||
row := r.db.QueryRow(query, user.Name, user.Username, user.Password, internal.Student)
|
||||
if err := row.Scan(&id); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (r *UserPostgres) GetUser(username, password string) (internal.User, error) {
|
||||
var user internal.User
|
||||
query := fmt.Sprintf("SELECT * from %s where username = $1 AND password_hash=$2", usersTable)
|
||||
row := r.db.QueryRow(query, username, password)
|
||||
err := row.Scan(&user.Id, &user.Name, &user.Username, &user.Password, &user.UserRole)
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (r *UserPostgres) UpdateUserRole(username string, userrole internal.UserRole) (string, error) {
|
||||
query := fmt.Sprintf("UPDATE %s SET role = $1 WHERE username = $2 RETURNING role", usersTable)
|
||||
var newRole string
|
||||
err := r.db.QueryRow(query, userrole, username).Scan(&newRole)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to update user role: %v", err)
|
||||
}
|
||||
return newRole, nil
|
||||
}
|
||||
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