Initial commit: Эфир мессенджер

This commit is contained in:
2026-04-06 14:57:36 +03:00
commit ff93679b6d
50 changed files with 5642 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
package middleware
import (
"context"
"messenger/internal/api/responses"
"messenger/internal/models"
"messenger/internal/service"
"net/http"
"strings"
)
type contextKey string
const UserContextKey contextKey = "user"
func JWTAuth(authService *service.AuthService) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Получаем токен из заголовка Authorization
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
responses.Unauthorized(w, "missing authorization header")
return
}
// Проверяем формат Bearer token
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
responses.Unauthorized(w, "invalid authorization header format")
return
}
token := parts[1]
// Валидируем токен
user, err := authService.ValidateToken(token)
if err != nil {
responses.Unauthorized(w, "invalid or expired token")
return
}
// Сохраняем пользователя в контексте
ctx := context.WithValue(r.Context(), UserContextKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func GetUserFromContext(ctx context.Context) *models.User {
user, ok := ctx.Value(UserContextKey).(*models.User)
if !ok {
return nil
}
return user
}
func RequireGlobalAdmin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := GetUserFromContext(r.Context())
if user == nil || !user.IsGlobalAdmin() {
responses.Forbidden(w, "global admin access required")
return
}
next.ServeHTTP(w, r)
})
}

View File

@@ -0,0 +1,38 @@
package middleware
import (
"net/http"
//"strings"
)
func CORS(allowedOrigins []string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
// Проверяем разрешен ли origin
allowed := false
for _, o := range allowedOrigins {
if o == "*" || o == origin {
allowed = true
break
}
}
if allowed {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
}

View File

@@ -0,0 +1,36 @@
package middleware
import (
"log/slog"
"net/http"
"time"
)
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Оборачиваем ResponseWriter для захвата статуса
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(wrapped, r)
slog.Info("HTTP request",
"method", r.Method,
"path", r.URL.Path,
"status", wrapped.statusCode,
"duration", time.Since(start),
"remote_addr", r.RemoteAddr,
)
})
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}

View File

@@ -0,0 +1,23 @@
package middleware
import (
"log/slog"
"net/http"
"runtime/debug"
)
func Recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
slog.Error("panic recovered",
"error", err,
"stack", string(debug.Stack()),
"path", r.URL.Path,
)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}