Added middlewares and started work with QUIC protocol.

This commit is contained in:
Сергей Петров 2025-04-18 16:20:35 +05:00
parent 530896a015
commit 1da3ce4075
5 changed files with 820 additions and 39 deletions

View File

@ -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(),
//}
}

View File

@ -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

View File

@ -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"`
}
)

View 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(&regReq); 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(&regReq); 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")
})
}

View 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)))
}