modified: go.mod

modified:   go.sum
	new file:   internal/config/oauth.go
	new file:   internal/handler/auth.go
	new file:   internal/utils/errors.go
	new file:   internal/utils/json.go
	modified:   internal/utils/password.go
Add utils, add auth handler, auth configs
This commit is contained in:
2025-09-29 01:28:22 +05:00
parent 83559faa4d
commit bf296373bd
7 changed files with 316 additions and 6 deletions
@@ -0,0 +1,39 @@
// utils/errors.go
package utils
import (
"fmt"
"net/http"
)
// APIError представляет ошибку API
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (e *APIError) Error() string {
return fmt.Sprintf("API Error %d: %s", e.Code, e.Message)
}
// ErrorResponse представляет стандартный ответ с ошибкой
type ErrorResponse struct {
Error bool `json:"error"`
Message string `json:"message"`
Code int `json:"code"`
}
// ValidationErrorResponse представляет ответ с ошибками валидации
type ValidationErrorResponse struct {
Error bool `json:"error"`
Message string `json:"message"`
Code int `json:"code"`
Errors map[string]string `json:"errors,omitempty"`
}
// Predefined errors
var (
ErrInvalidJSON = &APIError{Code: http.StatusBadRequest, Message: "Invalid JSON"}
ErrEmptyRequestBody = &APIError{Code: http.StatusBadRequest, Message: "Request body is empty"}
ErrRequestBodyTooLarge = &APIError{Code: http.StatusRequestEntityTooLarge, Message: "Request body too large"}
)
+115
View File
@@ -0,0 +1,115 @@
// utils/json.go
package utils
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
// DecodeJSON декодирует JSON из тела запроса с валидацией
func DecodeJSON(r *http.Request, v interface{}) error {
// Ограничиваем размер тела запроса (например, 1MB)
maxBytes := int64(1_048_576) // 1MB
r.Body = http.MaxBytesReader(nil, r.Body, maxBytes)
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() // Запрещаем неизвестные поля
err := decoder.Decode(v)
if err != nil {
var syntaxError *json.SyntaxError
var unmarshalTypeError *json.UnmarshalTypeError
var invalidUnmarshalError *json.InvalidUnmarshalError
switch {
case err == io.EOF:
return &APIError{
Code: http.StatusBadRequest,
Message: "Request body is empty",
}
case err.Error() == "http: request body too large":
return &APIError{
Code: http.StatusRequestEntityTooLarge,
Message: fmt.Sprintf("Request body must not be larger than %d bytes", maxBytes),
}
case strings.HasPrefix(err.Error(), "json: unknown field"):
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
return &APIError{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("Unknown field in JSON: %s", fieldName),
}
case errors.As(err, &syntaxError):
return &APIError{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("Malformed JSON at position %d", syntaxError.Offset),
}
case errors.As(err, &unmarshalTypeError):
return &APIError{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("Invalid value for field '%s'. Expected type %s", unmarshalTypeError.Field, unmarshalTypeError.Type),
}
case errors.As(err, &invalidUnmarshalError):
return &APIError{
Code: http.StatusInternalServerError,
Message: "Internal server error",
}
default:
return &APIError{
Code: http.StatusBadRequest,
Message: "Invalid JSON",
}
}
}
// Проверяем, что нет лишних данных после JSON
if err = decoder.Decode(&struct{}{}); err != io.EOF {
return &APIError{
Code: http.StatusBadRequest,
Message: "Request body must contain only single JSON object",
}
}
return nil
}
// WriteJSON записывает JSON ответ
func WriteJSON(w http.ResponseWriter, status int, data interface{}) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if data == nil {
return nil
}
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(true) // Экранируем HTML для безопасности
return encoder.Encode(data)
}
// WriteError записывает ошибку в формате JSON
func WriteError(w http.ResponseWriter, status int, message string) {
errorResponse := ErrorResponse{
Error: true,
Message: message,
Code: status,
}
WriteJSON(w, status, errorResponse)
}
// WriteValidationError записывает ошибки валидации
func WriteValidationError(w http.ResponseWriter, errors map[string]string) {
errorResponse := ValidationErrorResponse{
Error: true,
Message: "Validation failed",
Code: http.StatusBadRequest,
Errors: errors,
}
WriteJSON(w, http.StatusBadRequest, errorResponse)
}
@@ -1,14 +1,29 @@
// utils/password.go
package utils
import "golang.org/x/crypto/bcrypt"
import (
"crypto/rand"
"encoding/base64"
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// GenerateRandomPassword генерирует случайный пароль для OAuth пользователей
func GenerateRandomPassword() string {
bytes := make([]byte, 32) // 256 бит
_, err := rand.Read(bytes)
if err != nil {
// Fallback - используем временный пароль
return "temp_oauth_password_123"
}
return base64.URLEncoding.EncodeToString(bytes)
}