Added middlewares and started work with QUIC protocol.
This commit is contained in:
parent
530896a015
commit
1da3ce4075
@ -2,28 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"git.insit.tech/psa/rtsp_reader-writer/reader/internal/config"
|
||||
"git.insit.tech/psa/rtsp_reader-writer/reader/internal/handlers"
|
||||
logger "git.insit.tech/psa/rtsp_reader-writer/reader/internal/log"
|
||||
"git.insit.tech/psa/rtsp_reader-writer/reader/internal/metrics"
|
||||
jwtware "github.com/gofiber/contrib/jwt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"git.insit.tech/psa/rtsp_reader-writer/reader/internal/web/handlers"
|
||||
"git.insit.tech/psa/rtsp_reader-writer/reader/internal/web/middlewares"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//port := 8080
|
||||
//
|
||||
//http.HandleFunc("GET /api/v1/download/", handlers.Download) // example request: {"date": "07-03-2025", "start_time": "16-43", "end_time": "16-44"}
|
||||
//http.HandleFunc("GET /api/v1/hls/", handlers.HLS)
|
||||
|
||||
//log.Println("Starting server on:")
|
||||
//log.Printf("Serving on HTTP port: %d\n", port)
|
||||
//
|
||||
//log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
|
||||
|
||||
go metrics.Metrics()
|
||||
logger.StartMainLogger(config.Local, "reader")
|
||||
|
||||
@ -38,34 +27,37 @@ func main() {
|
||||
// logger.Log.Error("failed to create flow", zap.Error(err))
|
||||
//}
|
||||
|
||||
app := fiber.New()
|
||||
config.Storage.Users = make(map[string]config.User)
|
||||
|
||||
authStorage := &handlers.AuthStorage{Users: map[string]handlers.User{}}
|
||||
authHandler := &handlers.AuthHandler{Storage: authStorage}
|
||||
userHandler := &handlers.UserHandler{Storage: authStorage}
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Группа обработчиков, которые доступны неавторизованным пользователям
|
||||
publicGroup := app.Group("")
|
||||
publicGroup.Post("/register", authHandler.Register)
|
||||
publicGroup.Post("/login", authHandler.Login)
|
||||
// Public routes.
|
||||
mux.HandleFunc("POST /register", handlers.Register)
|
||||
mux.HandleFunc("POST /login", handlers.Login)
|
||||
|
||||
// Группа обработчиков, которые требуют авторизации
|
||||
authorizedGroup := app.Group("")
|
||||
authorizedGroup.Use(jwtware.New(jwtware.Config{
|
||||
SigningKey: jwtware.SigningKey{
|
||||
Key: handlers.JwtSecretKey,
|
||||
},
|
||||
ContextKey: handlers.ContextKeyUser,
|
||||
}))
|
||||
authorizedGroup.Get("/profile", userHandler.Profile)
|
||||
// Authorized routes.
|
||||
//authorizedGroup := http.NewServeMux()
|
||||
//authorizedGroup.HandleFunc("GET /profile", userHandlerQUIC.Profile)
|
||||
|
||||
// API Flussonic Media Server adapted handlers.
|
||||
authorizedGroup.Get("/vods/:id/:res/:file", handlers.SingleVodsHandler)
|
||||
authorizedGroup.Delete("/vods/:id/:res/:file", handlers.DelSingleVodsHandler)
|
||||
authorizedGroup.Get("/vods/:id/files", handlers.ListFilesVodsHandler)
|
||||
authorizedGroup.Delete("/vods/:id", handlers.DelVodsHandler)
|
||||
authorizedGroup.Get("/vods/:id", handlers.ConfigVodsHandler)
|
||||
authorizedGroup.Get("/vods", handlers.ListVodsHandler)
|
||||
//mux.HandleFunc("GET /vods/:id/:res/:file", handlersQUIC.SingleVodsHandler)
|
||||
//mux.HandleFunc("DELETE /vods/:id/:res/:file", handlersQUIC.DelSingleVodsHandler)
|
||||
//mux.HandleFunc("GET /vods/:id/files", handlersQUIC.ListFilesVodsHandler)
|
||||
//mux.HandleFunc("DELETE /vods/:id", handlersQUIC.DelVodsHandler)
|
||||
//mux.HandleFunc("GET /vods/:id", handlersQUIC.ConfigVodsHandler)
|
||||
mux.Handle("GET /vods", middlewares.AuthMiddleware(handlers.ListVodsHandler()))
|
||||
|
||||
logrus.Fatal(app.Listen(":8100"))
|
||||
http.ListenAndServe(":8080", mux)
|
||||
|
||||
// HTTP/1.1 & HTTP/2
|
||||
go func() {
|
||||
//http.ListenAndServeTLS(":8080", certFile, keyFile, mux)
|
||||
|
||||
}()
|
||||
|
||||
//server := &http3.Server{
|
||||
// Addr: ":8080",
|
||||
// Handler: mux,
|
||||
// TLSConfig: quic.GetTLSConfig(),
|
||||
//}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ require (
|
||||
github.com/gofiber/contrib/jwt v1.1.0
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
go.uber.org/zap v1.27.0
|
||||
@ -25,7 +26,6 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafov/m3u8 v0.12.1 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
|
@ -1,7 +1,53 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ContextKeyUser = "user"
|
||||
ContextKeySession = "session_data"
|
||||
)
|
||||
|
||||
var (
|
||||
Local = "storage"
|
||||
LogsDirectory string
|
||||
DirData string
|
||||
|
||||
Storage AuthStorage
|
||||
|
||||
JwtSecretKey = []byte(uuid.NewString())
|
||||
SessionStore sync.Map
|
||||
SessionFile = "sessions.json"
|
||||
)
|
||||
|
||||
type (
|
||||
AuthStorage struct {
|
||||
Users map[string]User
|
||||
}
|
||||
|
||||
User struct {
|
||||
Email string
|
||||
Name string
|
||||
Password string
|
||||
}
|
||||
)
|
||||
|
||||
// ///////////////// в handlers?
|
||||
// Работа с файлом (дополнительная опция)
|
||||
type (
|
||||
FileSession struct {
|
||||
ID string `json:"id"`
|
||||
Data *SessionData `json:"data"`
|
||||
}
|
||||
|
||||
SessionData struct {
|
||||
Proto string `json:"proto"`
|
||||
FileName string `json:"file_name"`
|
||||
IP string `json:"ip"`
|
||||
Token string `json:"token"`
|
||||
LastCheck time.Time `json:"last_check"`
|
||||
}
|
||||
)
|
||||
|
279
reader/internal/web/handlers/handlers.go
Normal file
279
reader/internal/web/handlers/handlers.go
Normal file
@ -0,0 +1,279 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"git.insit.tech/psa/rtsp_reader-writer/reader/internal/config"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Структура HTTP-запроса на регистрацию пользователя
|
||||
type registerRequest struct {
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// Структура 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")
|
||||
)
|
||||
|
||||
// Структура HTTP-ответа с информацией о пользователе
|
||||
type ProfileResponse struct {
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type VideoRequest struct {
|
||||
Date string `json:"date"`
|
||||
StartTime string `json:"start_time"`
|
||||
EndTime string `json:"end_time"`
|
||||
}
|
||||
|
||||
type ListVodsResponse struct {
|
||||
EstimatedCount int `json:"estimated_count"` // Estimated total number of records for the query.
|
||||
Vods []string `json:"vods"` // List of vods.
|
||||
Files map[string][]string `json:"files"` // List files and folders of a specific VOD location.
|
||||
}
|
||||
|
||||
type ConfigVodsResponse struct {
|
||||
Prefix string `json:"prefix"` // The unique name of VOD location.
|
||||
AutoMbr bool `json:"auto_mbr"` // Turns on automatic creation of a multi-bitrate HLS playlist from several files with different bitrates.
|
||||
Disabled bool `json:"disabled"` // Whether this VOD location is disabled.
|
||||
Protocols struct { // Configuraton of play protocols.
|
||||
Hls bool `json:"hls"` // Whether to allow or deny an HLS stream playback.
|
||||
Cmaf bool `json:"cmaf"` // Whether to allow or deny an LL-HLS stream playback.
|
||||
Dash bool `json:"dash"` // Whether to allow or deny a DASH stream playback.
|
||||
Player bool `json:"player"` // Whether to allow or deny playback in embed.html.
|
||||
Mss bool `json:"mss"` // Whether to allow or deny an MSS stream playback.
|
||||
Rtmp bool `json:"rtmp"` // Whether to allow or deny an RTMP stream playback.
|
||||
Rtsp bool `json:"rtsp"` // Whether to allow or deny an RTSP stream playback.
|
||||
M4F bool `json:"m4f"` // Whether to allow or deny an M4F stream playback.
|
||||
M4S bool `json:"m4s"` // Whether to allow or deny an M4S stream playback.
|
||||
Mseld bool `json:"mseld"` // Whether to allow or deny an MSE-LD stream playback.
|
||||
Tshttp bool `json:"tshttp"` // Whether to allow or deny an MPEG-TS stream playback over HTTP(S).
|
||||
Webrtc bool `json:"webrtc"` // Whether to allow or deny an WebRTC stream playback.
|
||||
Srt bool `json:"srt"` // Whether to allow or deny an SRT stream playback.
|
||||
Shoutcast bool `json:"shoutcast"` // Whether to allow or deny a SHOUTcast/Icecast stream playback.
|
||||
Mp4 bool `json:"mp4"` // Whether to allow or deny an MP4 file download over HTTP(S).
|
||||
Jpeg bool `json:"jpeg"` // Whether to allow or deny delivering JPEG thumbnails over HTTP(S).
|
||||
Api bool `json:"api"` // Whether to allow or deny API requests.
|
||||
} `json:"protocols"`
|
||||
SegmentDuration int `json:"segment_duration"` // The time, in seconds, of the segment duration. Used for the protocols like HLS or DASH.
|
||||
AddAudioOnly bool `json:"add_audio_only"` // Whether to add an audio-only version of an HLS stream. Used to create App Store compliant HLS streams to deliver the content to Apple iOS devices. Add audio-only HLS playlist to variant MBR playlist for iOS compliant streaming.
|
||||
Provider string `json:"provider"` // Human-readable name of the content provider. Applicable to MPEG-TS.
|
||||
}
|
||||
|
||||
type SingleVodsResponse struct {
|
||||
Name string `json:"name"`
|
||||
Prefix string `json:"prefix"`
|
||||
Url string `json:"url"`
|
||||
Folder string `json:"folder"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
MediaInfo MediaSingleVodsResponse `json:"media_info"`
|
||||
}
|
||||
|
||||
type MediaSingleVodsResponse struct {
|
||||
Tracks map[string]string `json:"tracks"`
|
||||
Duration int `json:"duration"`
|
||||
Provider string `json:"provider"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// Обработчик HTTP-запросов на регистрацию пользователя
|
||||
func Register(w http.ResponseWriter, r *http.Request) {
|
||||
regReq := registerRequest{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(®Req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь с таким email еще не зарегистрирован
|
||||
if _, exists := config.Storage.Users[regReq.Email]; exists {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Сохраняем в память нового зарегистрированного пользователя
|
||||
config.Storage.Users[regReq.Email] = config.User{
|
||||
Email: regReq.Email,
|
||||
Name: regReq.Name,
|
||||
Password: regReq.Password,
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
// Обработчик HTTP-запросов на вход в аккаунт
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
regReq := loginRequest{}
|
||||
if err := json.NewDecoder(r.Body).Decode(®Req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Ищем пользователя в памяти приложения по электронной почте
|
||||
user, exists := config.Storage.Users[regReq.Email]
|
||||
// Если пользователь не найден, возвращаем ошибку
|
||||
if !exists {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Если пользователь найден, но у него другой пароль, возвращаем ошибку
|
||||
if user.Password != regReq.Password {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Генерируем JWT-токен для пользователя,
|
||||
// который он будет использовать в будущих HTTP-запросах
|
||||
|
||||
// Генерируем полезные данные, которые будут храниться в токене
|
||||
payload := jwt.MapClaims{
|
||||
"sub": user.Email,
|
||||
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
||||
}
|
||||
|
||||
// Создаем новый JWT-токен и подписываем его по алгоритму HS256
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
|
||||
|
||||
t, err := token.SignedString(config.JwtSecretKey)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("JWT token signing")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := json.Marshal(loginResponse{AccessToken: t})
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if _, err = w.Write(res); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
//w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// Inactive5Minutes checks if a folder has files that was not created or modified for last 5 minutes.
|
||||
func Inactive5Minutes(entries []os.DirEntry) (bool, error) {
|
||||
threshold := time.Now().Add(-5 * time.Minute)
|
||||
|
||||
disabled := true
|
||||
|
||||
for _, entry := range entries {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
return true, errors.New("Info error: " + err.Error())
|
||||
}
|
||||
|
||||
if info.ModTime().After(threshold) {
|
||||
disabled = false
|
||||
return disabled, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RecDurationMilliseconds calculates the difference between first existing rec file and the last one in milliseconds.
|
||||
func RecDurationMilliseconds(entries []os.DirEntry) (int, error) {
|
||||
// Find last file and first file; get timestamps.
|
||||
lastFile := entries[len(entries)-1].Name()
|
||||
lastTime := strings.Split(lastFile, "_")[0]
|
||||
|
||||
firstFile := entries[0].Name()
|
||||
firstTime := strings.Split(firstFile, "_")[0]
|
||||
|
||||
// Convert string timestamps to int.
|
||||
lastTimeInt, err := strconv.Atoi(lastTime)
|
||||
if err != nil {
|
||||
return 0, errors.New("convert last time error: " + err.Error())
|
||||
}
|
||||
|
||||
firstTimeInt, err := strconv.Atoi(firstTime)
|
||||
if err != nil {
|
||||
return 0, errors.New("convert first time error: " + err.Error())
|
||||
}
|
||||
|
||||
// Calculate the difference.
|
||||
difference := lastTimeInt - firstTimeInt
|
||||
|
||||
return difference * 1000, nil
|
||||
}
|
||||
|
||||
// ListVodsHandlerLogic implements main logic of handler ListVodsHandler.
|
||||
func ListVodsHandlerLogic() (ListVodsResponse, error) {
|
||||
// Read directory.
|
||||
entries, err := os.ReadDir(config.DirData)
|
||||
if err != nil {
|
||||
return ListVodsResponse{}, err
|
||||
}
|
||||
|
||||
// Filter only folders.
|
||||
var dirs []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
dirs = append(dirs, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the Response.
|
||||
VodsRes := ListVodsResponse{
|
||||
EstimatedCount: len(dirs),
|
||||
Vods: dirs,
|
||||
}
|
||||
|
||||
return VodsRes, nil
|
||||
}
|
||||
|
||||
// ListVodsHandler returns the list of VOD locations.
|
||||
//
|
||||
// This method allows to get the list of all VOD locations. VOD location is a virtual filepath used to place files for
|
||||
// VOD (Video on Demand) broadcasting.
|
||||
func ListVodsHandler() http.Handler {
|
||||
log.Println("before return ListVodsHandler")
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("hello fron ListVodsHandler")
|
||||
|
||||
// Prepare the Response.
|
||||
VodsRes, err := ListVodsHandlerLogic()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Write header and code response.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err = json.NewEncoder(w).Encode(VodsRes); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Println("hello fron ListVodsHandlerLogic: StatusOK")
|
||||
})
|
||||
}
|
464
reader/internal/web/middlewares/auth.go
Normal file
464
reader/internal/web/middlewares/auth.go
Normal file
@ -0,0 +1,464 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.insit.tech/psa/rtsp_reader-writer/reader/internal/config"
|
||||
jwtware "github.com/gofiber/contrib/jwt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// CheckJWT is custom middleware that checks if token is valid.
|
||||
func CheckJWT(c *fiber.Ctx) error {
|
||||
// Пропускаем JWT проверку, если есть session_id
|
||||
if c.Query("session_id") != "" {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// Выполняем JWT проверку только при наличии токена
|
||||
return jwtware.New(jwtware.Config{
|
||||
SigningKey: jwtware.SigningKey{
|
||||
Key: config.JwtSecretKey,
|
||||
},
|
||||
ContextKey: config.ContextKeyUser,
|
||||
TokenLookup: "query:token",
|
||||
})(c)
|
||||
}
|
||||
|
||||
// CheckJWT2 is custom middleware that checks if token is valid.
|
||||
func CheckJWT2(next http.Handler) http.Handler {
|
||||
log.Println("before return CheckJWT2")
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("hello from CheckJWT2")
|
||||
tokenStr := r.URL.Query().Get("token")
|
||||
if tokenStr == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
||||
return config.JwtSecretKey, nil
|
||||
})
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), config.ContextKeyUser, token)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
log.Println("CheckJWT2 finished")
|
||||
})
|
||||
}
|
||||
|
||||
// CheckSessionID middleware.
|
||||
func CheckSessionID(c *fiber.Ctx) error {
|
||||
// Если есть валидный JWT в контексте
|
||||
if c.Locals(config.ContextKeyUser) != nil {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// Проверка session_id
|
||||
sessionID := c.Query("session_id")
|
||||
if sessionID == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Auth required"})
|
||||
}
|
||||
|
||||
// Получаем данные из памяти
|
||||
data, err := getSessionData(sessionID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid session"})
|
||||
}
|
||||
|
||||
if !validateSessionParams(c, data) {
|
||||
deleteSession(sessionID)
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Session parameters mismatch"})
|
||||
}
|
||||
|
||||
if time.Since(data.LastCheck) > 3*time.Minute {
|
||||
if !checkTokenInDB(data.Token) {
|
||||
deleteSession(sessionID)
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Token expired"})
|
||||
}
|
||||
data.LastCheck = time.Now()
|
||||
saveSession(sessionID, data)
|
||||
}
|
||||
|
||||
c.Locals(config.ContextKeySession, data)
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// CheckSessionID2 middleware.
|
||||
func CheckSessionID2(next http.Handler) http.Handler {
|
||||
log.Println("before return CheckSessionID2")
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("hello from CheckSessionID2")
|
||||
if r.Context().Value(config.ContextKeyUser) != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := r.URL.Query().Get("session_id")
|
||||
if sessionID == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := getSessionData(sessionID)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if !validateSessionParam2(r, data) {
|
||||
deleteSession(sessionID)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if time.Since(data.LastCheck) > 3*time.Minute {
|
||||
if !checkTokenInDB(data.Token) {
|
||||
deleteSession(sessionID)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
data.LastCheck = time.Now()
|
||||
saveSession(sessionID, data)
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), config.ContextKeySession, data)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
log.Println("CheckSessionID2 finished")
|
||||
})
|
||||
}
|
||||
|
||||
// Работа с памятью
|
||||
func getSessionData(sessionID string) (*config.SessionData, error) {
|
||||
// Для работы с файлами (раскомментировать при необходимости):
|
||||
// return loadFromFile(sessionID)
|
||||
|
||||
val, ok := config.SessionStore.Load(sessionID)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session not found")
|
||||
}
|
||||
|
||||
data, ok := val.(*config.SessionData)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid session data format")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Остальные функции без изменений
|
||||
func validateSessionParams(c *fiber.Ctx, data *config.SessionData) bool {
|
||||
log.Printf("HTTP/1.1 && HTTP/2 Protocol: %s", c.Protocol())
|
||||
log.Printf("HTTP/1.1 && HTTP/2 File: %s", c.Params("file"))
|
||||
log.Printf("HTTP/1.1 && HTTP/2 IP: %s", c.IP())
|
||||
|
||||
log.Printf("DB Protocol: %s", data.Proto)
|
||||
log.Printf("DB File: %s", data.FileName)
|
||||
log.Printf("DB IP: %s", data.IP)
|
||||
|
||||
return c.Protocol() == data.Proto &&
|
||||
c.Params("file") == data.FileName &&
|
||||
c.IP() == data.IP
|
||||
}
|
||||
|
||||
// Остальные функции без изменений
|
||||
func validateSessionParam2(r *http.Request, data *config.SessionData) bool {
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
file := r.PathValue("file")
|
||||
|
||||
clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
// Если SplitHostPort не сработал, оставляем как есть
|
||||
clientIP = r.RemoteAddr
|
||||
}
|
||||
|
||||
log.Printf("HTTP/3 Protocol: %s", scheme)
|
||||
log.Printf("HTTP/3 File: %s", file)
|
||||
log.Printf("HTTP/3 IP: %s", clientIP)
|
||||
|
||||
log.Printf("DB Protocol: %s", data.Proto)
|
||||
log.Printf("DB File: %s", data.FileName)
|
||||
log.Printf("DB IP: %s", data.IP)
|
||||
|
||||
return scheme == data.Proto &&
|
||||
file == data.FileName &&
|
||||
clientIP == data.IP
|
||||
}
|
||||
|
||||
func checkTokenInDB(token string) bool {
|
||||
// Ваша реализация проверки токена
|
||||
return true // временная заглушка
|
||||
}
|
||||
|
||||
// Генерация session_id
|
||||
func generateSessionID(c *fiber.Ctx, token string) string {
|
||||
data := fmt.Sprintf("%s|%s|%s|%s",
|
||||
c.Protocol(),
|
||||
c.Params("file"),
|
||||
c.IP(),
|
||||
token,
|
||||
)
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// Генерация session_id
|
||||
func generateSessionID2(r *http.Request, token string) string {
|
||||
// Determine protocol.
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
// Determine filename.
|
||||
file := r.PathValue("file")
|
||||
|
||||
// Determine IP.
|
||||
clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
// Если SplitHostPort не сработал, оставляем как есть
|
||||
clientIP = r.RemoteAddr
|
||||
}
|
||||
|
||||
data := fmt.Sprintf("%s|%s|%s|%s",
|
||||
scheme,
|
||||
file,
|
||||
clientIP,
|
||||
token,
|
||||
)
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func saveSession(sessionID string, data *config.SessionData) {
|
||||
// Для работы с файлами (раскомментировать при необходимости):
|
||||
// saveToFile(sessionID, data)
|
||||
|
||||
config.SessionStore.Store(sessionID, data)
|
||||
}
|
||||
|
||||
func deleteSession(sessionID string) {
|
||||
// Для работы с файлами (раскомментировать при необходимости):
|
||||
// deleteFromFile(sessionID)
|
||||
|
||||
config.SessionStore.Delete(sessionID)
|
||||
}
|
||||
|
||||
func saveToFile(sessionID string, data *config.SessionData) error {
|
||||
file, _ := os.ReadFile(config.SessionFile)
|
||||
var sessions []config.FileSession
|
||||
json.Unmarshal(file, &sessions)
|
||||
|
||||
// Удаляем старую сессию если существует
|
||||
for i, s := range sessions {
|
||||
if s.ID == sessionID {
|
||||
sessions = append(sessions[:i], sessions[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sessions = append(sessions, config.FileSession{ID: sessionID, Data: data})
|
||||
bytes, _ := json.MarshalIndent(sessions, "", " ")
|
||||
return os.WriteFile(config.SessionFile, bytes, 0644)
|
||||
}
|
||||
|
||||
func loadFromFile(sessionID string) (*config.SessionData, error) {
|
||||
file, err := os.ReadFile(config.SessionFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sessions []config.FileSession
|
||||
json.Unmarshal(file, &sessions)
|
||||
|
||||
for _, s := range sessions {
|
||||
if s.ID == sessionID {
|
||||
return s.Data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("session not found")
|
||||
}
|
||||
|
||||
func deleteFromFile(sessionID string) error {
|
||||
file, _ := os.ReadFile(config.SessionFile)
|
||||
var sessions []config.FileSession
|
||||
json.Unmarshal(file, &sessions)
|
||||
|
||||
for i, s := range sessions {
|
||||
if s.ID == sessionID {
|
||||
sessions = append(sessions[:i], sessions[i+1:]...)
|
||||
bytes, _ := json.MarshalIndent(sessions, "", " ")
|
||||
return os.WriteFile(config.SessionFile, bytes, 0644)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSessionID is custom middleware that creates session ID by using token.
|
||||
func CreateSessionID(c *fiber.Ctx) error {
|
||||
if token := c.Query("token"); token != "" {
|
||||
// Get payload from token.
|
||||
jwtPayload, ok := jwtPayloadFromRequest(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
// Check if the owner of the token is registered.
|
||||
_, ok = config.Storage.Users[jwtPayload["sub"].(string)]
|
||||
if !ok {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
// Create session data.
|
||||
sessionData := &config.SessionData{
|
||||
Proto: c.Protocol(),
|
||||
FileName: c.Params("file"),
|
||||
IP: c.IP(),
|
||||
Token: token,
|
||||
LastCheck: time.Now(),
|
||||
}
|
||||
|
||||
// Генерируем session_id
|
||||
sessionID := generateSessionID(c, token)
|
||||
|
||||
// Сохраняем сессию
|
||||
saveSession(sessionID, sessionData)
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"session_id": sessionID,
|
||||
})
|
||||
}
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// CreateSessionID2 is custom middleware that creates session ID by using token.
|
||||
func CreateSessionID2(next http.Handler) http.Handler {
|
||||
log.Println("before return CreateSessionID2")
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("hello from CreateSessionID2")
|
||||
|
||||
if token := r.URL.Query().Get("token"); token != "" {
|
||||
// Get payload from token.
|
||||
jwtPayload, ok := jwtPayloadFromRequest2(r)
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the owner of the token is registered.
|
||||
_, ok = config.Storage.Users[jwtPayload["sub"].(string)]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("user not found"))
|
||||
return
|
||||
}
|
||||
|
||||
// Determine protocol.
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
// Determine filename.
|
||||
file := r.PathValue("file")
|
||||
|
||||
// Determine IP.
|
||||
clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
// Если SplitHostPort не сработал, оставляем как есть
|
||||
clientIP = r.RemoteAddr
|
||||
}
|
||||
|
||||
// Create session data.
|
||||
sessionData := &config.SessionData{
|
||||
Proto: scheme,
|
||||
FileName: file,
|
||||
IP: clientIP,
|
||||
Token: token,
|
||||
LastCheck: time.Now(),
|
||||
}
|
||||
|
||||
// Генерируем session_id
|
||||
sessionID := generateSessionID2(r, token)
|
||||
|
||||
// Сохраняем сессию
|
||||
saveSession(sessionID, sessionData)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(sessionID); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
log.Println("hello from CreateSessionID2: StatusOK")
|
||||
})
|
||||
}
|
||||
|
||||
func jwtPayloadFromRequest(c *fiber.Ctx) (jwt.MapClaims, bool) {
|
||||
jwtToken, ok := c.Context().Value(config.ContextKeyUser).(*jwt.Token)
|
||||
if !ok {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"jwt_token_context_value": c.Context().Value(config.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
|
||||
}
|
||||
|
||||
func jwtPayloadFromRequest2(r *http.Request) (jwt.MapClaims, bool) {
|
||||
jwtToken, ok := r.Context().Value(config.ContextKeyUser).(*jwt.Token)
|
||||
|
||||
if !ok {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"jwt_token_context_value": r.Context().Value(config.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
|
||||
}
|
||||
|
||||
// AuthMiddleware collects auth middlewares.
|
||||
func AuthMiddleware(h http.Handler) http.Handler {
|
||||
return CheckJWT2(CheckSessionID2(CreateSessionID2(h)))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user