package auth import ( "errors" "fmt" jwtware "github.com/gofiber/contrib/jwt" "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v5" "github.com/sirupsen/logrus" "time" ) const ( contextKeyUser = "user" ) func Auth() { app := fiber.New() authStorage := &AuthStorage{map[string]User{}} authHandler := &AuthHandler{storage: authStorage} userHandler := &UserHandler{storage: authStorage} // Группа обработчиков, которые доступны неавторизованным пользователям publicGroup := app.Group("") publicGroup.Post("/register", authHandler.Register) publicGroup.Post("/login", authHandler.Login) // Группа обработчиков, которые требуют авторизации authorizedGroup := app.Group("") authorizedGroup.Use(jwtware.New(jwtware.Config{ SigningKey: jwtware.SigningKey{ Key: jwtSecretKey, }, ContextKey: contextKeyUser, })) authorizedGroup.Get("/profile", userHandler.Profile) logrus.Fatal(app.Listen(":8000")) } type ( // Обработчик HTTP-запросов на регистрацию и аутентификацию пользователей AuthHandler struct { storage *AuthStorage } // Хранилище зарегистрированных пользователей // Данные хранятся в оперативной памяти AuthStorage struct { users map[string]User } // Структура данных с информацией о пользователе User struct { Email string Name string password string } ) // Структура HTTP-запроса на регистрацию пользователя type RegisterRequest struct { Email string `json:"email"` Name string `json:"name"` Password string `json:"password"` } // Обработчик HTTP-запросов на регистрацию пользователя func (h *AuthHandler) Register(c *fiber.Ctx) error { regReq := RegisterRequest{} if err := c.BodyParser(®Req); err != nil { return fmt.Errorf("body parser: %w", err) } // Проверяем, что пользователь с таким email еще не зарегистрирован if _, exists := h.storage.users[regReq.Email]; exists { return errors.New("the user already exists") } // Сохраняем в память нового зарегистрированного пользователя h.storage.users[regReq.Email] = User{ Email: regReq.Email, Name: regReq.Name, password: regReq.Password, } return c.SendStatus(fiber.StatusCreated) } // Структура HTTP-запроса на вход в аккаунт type LoginRequest struct { Email string `json:"email"` Password string `json:"password"` } // Структура HTTP-ответа на вход в аккаунт // В ответе содержится JWT-токен авторизованного пользователя type LoginResponse struct { AccessToken string `json:"access_token"` } var ( errBadCredentials = errors.New("email or password is incorrect") ) // Секретный ключ для подписи JWT-токена // Необходимо хранить в безопасном месте var jwtSecretKey = []byte("very-secret-key") // Обработчик HTTP-запросов на вход в аккаунт func (h *AuthHandler) Login(c *fiber.Ctx) error { regReq := LoginRequest{} if err := c.BodyParser(®Req); err != nil { return fmt.Errorf("body parser: %w", err) } // Ищем пользователя в памяти приложения по электронной почте user, exists := h.storage.users[regReq.Email] // Если пользователь не найден, возвращаем ошибку if !exists { return errBadCredentials } // Если пользователь найден, но у него другой пароль, возвращаем ошибку if user.password != regReq.Password { return errBadCredentials } // Генерируем JWT-токен для пользователя, // который он будет использовать в будущих HTTP-запросах // Генерируем полезные данные, которые будут храниться в токене payload := jwt.MapClaims{ "sub": user.Email, "exp": time.Now().Add(time.Hour * 72).Unix(), } // Создаем новый JWT-токен и подписываем его по алгоритму HS256 token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) t, err := token.SignedString(jwtSecretKey) if err != nil { logrus.WithError(err).Error("JWT token signing") return c.SendStatus(fiber.StatusInternalServerError) } return c.JSON(LoginResponse{AccessToken: t}) } // Обработчик HTTP-запросов, которые связаны с пользователем type UserHandler struct { storage *AuthStorage } // Структура HTTP-ответа с информацией о пользователе type ProfileResponse struct { Email string `json:"email"` Name string `json:"name"` } func jwtPayloadFromRequest(c *fiber.Ctx) (jwt.MapClaims, bool) { jwtToken, ok := c.Context().Value(contextKeyUser).(*jwt.Token) if !ok { logrus.WithFields(logrus.Fields{ "jwt_token_context_value": c.Context().Value(contextKeyUser), }).Error("wrong type of JWT token in context") return nil, false } payload, ok := jwtToken.Claims.(jwt.MapClaims) if !ok { logrus.WithFields(logrus.Fields{ "jwt_token_claims": jwtToken.Claims, }).Error("wrong type of JWT token claims") return nil, false } return payload, true } // Обработчик HTTP-запросов на получение информации о пользователе func (h *UserHandler) Profile(c *fiber.Ctx) error { jwtPayload, ok := jwtPayloadFromRequest(c) if !ok { return c.SendStatus(fiber.StatusUnauthorized) } userInfo, ok := h.storage.users[jwtPayload["sub"].(string)] if !ok { return errors.New("user not found") } return c.JSON(ProfileResponse{ Email: userInfo.Email, Name: userInfo.Name, }) }