From 17a671a41727fbb202e4bdd7ba2fb3837bafc5b1 Mon Sep 17 00:00:00 2001 From: Nell Date: Tue, 11 Nov 2025 02:33:05 +0100 Subject: [PATCH] init --- domain/client.go | 18 ++++ domain/client_manager.go | 15 +++ go.mod | 3 + go.sum | 10 ++ network/http/context/context.go | 51 ++++++++++ network/http/handler/handler.go | 13 +++ network/http/handlers/channel.go | 25 ----- network/http/handlers/main.go | 31 ------ network/http/handlers/server.go | 1 - network/http/middleware/auth.go | 1 + .../{middleware.go => middleware/cors.go} | 16 +-- network/http/server.go | 4 +- network/http/web/api/category.go | 46 +++++++++ network/http/web/api/channel.go | 51 ++++++++++ network/http/web/api/message.go | 46 +++++++++ network/http/web/api/server.go | 46 +++++++++ network/http/web/auth.go | 72 ++++++++++++++ network/http/web/main.go | 48 +++++++++ network/http/web/ws.go | 38 ++++++++ network/websocket/hub.go | 1 + services/jwt.go | 97 +++++++++++++++++++ 21 files changed, 560 insertions(+), 73 deletions(-) create mode 100644 domain/client.go create mode 100644 domain/client_manager.go create mode 100644 network/http/context/context.go create mode 100644 network/http/handler/handler.go delete mode 100644 network/http/handlers/channel.go delete mode 100644 network/http/handlers/main.go delete mode 100644 network/http/handlers/server.go create mode 100644 network/http/middleware/auth.go rename network/http/{middleware.go => middleware/cors.go} (67%) create mode 100644 network/http/web/api/category.go create mode 100644 network/http/web/api/channel.go create mode 100644 network/http/web/api/message.go create mode 100644 network/http/web/api/server.go create mode 100644 network/http/web/auth.go create mode 100644 network/http/web/main.go create mode 100644 network/http/web/ws.go create mode 100644 network/websocket/hub.go create mode 100644 services/jwt.go diff --git a/domain/client.go b/domain/client.go new file mode 100644 index 0000000..5f4d9a4 --- /dev/null +++ b/domain/client.go @@ -0,0 +1,18 @@ +package domain + +import ( + "go_oxspeak_server/models" + "sync" +) + +type Client struct { + User models.User + + mu sync.RWMutex +} + +func newClient(user models.User) *Client { + return &Client{ + User: user, + } +} diff --git a/domain/client_manager.go b/domain/client_manager.go new file mode 100644 index 0000000..445a869 --- /dev/null +++ b/domain/client_manager.go @@ -0,0 +1,15 @@ +package domain + +import ( + "github.com/puzpuzpuz/xsync/v4" +) + +type ClientManager struct { + clients *xsync.Map[string, Client] +} + +func NewClientManager() *ClientManager { + return &ClientManager{ + clients: xsync.NewMap[string, Client](), + } +} diff --git a/go.mod b/go.mod index 323d32f..fd66d18 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,8 @@ require ( github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.6 // indirect @@ -40,6 +42,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/puzpuzpuz/xsync/v4 v4.2.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.55.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index 75b2ac1..c87894e 100644 --- a/go.sum +++ b/go.sum @@ -30,9 +30,13 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -63,6 +67,12 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY= +github.com/puzpuzpuz/xsync v1.5.2/go.mod h1:K98BYhX3k1dQ2M63t1YNVDanbwUPmBCAhNmVrrxfiGg= +github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0= +github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= diff --git a/network/http/context/context.go b/network/http/context/context.go new file mode 100644 index 0000000..43a1d3f --- /dev/null +++ b/network/http/context/context.go @@ -0,0 +1,51 @@ +package context + +import ( + "go_oxspeak_server/models" + + "github.com/gin-gonic/gin" +) + +// Context wrapper avec des helpers +// NE contient PAS de dépendances globales (DB, Config, etc.) +type Context struct { + *gin.Context +} + +func NewContext(c *gin.Context) *Context { + return &Context{Context: c} +} + +// Helpers pour accéder aux données de REQUÊTE + +func (c *Context) GetCurrentUser() (*models.User, bool) { + value, exists := c.Get("currentUser") + if !exists { + return nil, false + } + user, ok := value.(*models.User) + return user, ok +} + +func (c *Context) MustGetCurrentUser() *models.User { + user, ok := c.GetCurrentUser() + if !ok { + c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) + panic("no authenticated user") + } + return user +} + +func (c *Context) GetRequestID() string { + value, exists := c.Get("requestID") + if !exists { + return "" + } + id, _ := value.(string) + return id +} + +func (c *Context) IsAuthenticated() bool { + _, exists := c.GetCurrentUser() + return exists +} diff --git a/network/http/handler/handler.go b/network/http/handler/handler.go new file mode 100644 index 0000000..f052d2e --- /dev/null +++ b/network/http/handler/handler.go @@ -0,0 +1,13 @@ +package handler + +import "gorm.io/gorm" + +type Handler struct { + DB *gorm.DB +} + +func NewHandler(db *gorm.DB) *Handler { + return &Handler{ + DB: db, + } +} diff --git a/network/http/handlers/channel.go b/network/http/handlers/channel.go deleted file mode 100644 index 27c1e93..0000000 --- a/network/http/handlers/channel.go +++ /dev/null @@ -1,25 +0,0 @@ -package handlers - -import ( - "go_oxspeak_server/models" - - "github.com/gin-gonic/gin" -) - -type ChannelHandler struct { - *Handler -} - -func AddChannelRoutes(rg *gin.RouterGroup, h *Handler) { - channel := rg.Group("/channel") - - handler := &ChannelHandler{h} - channel.GET("/:id", handler.getChannels) - -} - -func (h *Handler) getChannels(c *gin.Context) { - var users []models.User - h.DB.Find(&users) - -} diff --git a/network/http/handlers/main.go b/network/http/handlers/main.go deleted file mode 100644 index c46e875..0000000 --- a/network/http/handlers/main.go +++ /dev/null @@ -1,31 +0,0 @@ -package handlers - -import ( - "go_oxspeak_server/database" - - "github.com/gin-gonic/gin" - "gorm.io/gorm" -) - -type Handler struct { - DB *gorm.DB -} - -func CreateRouter() *gin.Engine { - router := gin.Default() - - handler := &Handler{DB: database.DB} - - api := router.Group("/api") - { - AddChannelRoutes(api, handler) - } - - router.GET("/health", handler.healthcheck) - - return router -} - -func (h *Handler) healthcheck(c *gin.Context) { - c.JSON(200, gin.H{"status": "ok"}) -} diff --git a/network/http/handlers/server.go b/network/http/handlers/server.go deleted file mode 100644 index 5ac8282..0000000 --- a/network/http/handlers/server.go +++ /dev/null @@ -1 +0,0 @@ -package handlers diff --git a/network/http/middleware/auth.go b/network/http/middleware/auth.go new file mode 100644 index 0000000..c870d7c --- /dev/null +++ b/network/http/middleware/auth.go @@ -0,0 +1 @@ +package middleware diff --git a/network/http/middleware.go b/network/http/middleware/cors.go similarity index 67% rename from network/http/middleware.go rename to network/http/middleware/cors.go index 13e116b..2232a36 100644 --- a/network/http/middleware.go +++ b/network/http/middleware/cors.go @@ -1,10 +1,6 @@ -package http +package middleware -import ( - "log" - - "github.com/gin-gonic/gin" -) +import "github.com/gin-gonic/gin" // CORSMiddleware configure les headers CORS func CORSMiddleware() gin.HandlerFunc { @@ -22,11 +18,3 @@ func CORSMiddleware() gin.HandlerFunc { c.Next() } } - -// LoggingMiddleware personnalisé (optionnel, Gin en a un par défaut) -func LoggingMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - log.Printf("[HTTP] %s %s", c.Request.Method, c.Request.URL.Path) - c.Next() - } -} diff --git a/network/http/server.go b/network/http/server.go index ff9fc97..61ec494 100644 --- a/network/http/server.go +++ b/network/http/server.go @@ -1,7 +1,7 @@ package http import ( - "go_oxspeak_server/network/http/handlers" + "go_oxspeak_server/network/http/web" "github.com/gin-gonic/gin" ) @@ -12,7 +12,7 @@ type Server struct { } func NewServer(addr string) *Server { - router := handlers.CreateRouter() + router := web.CreateRouter() s := &Server{ router: router, diff --git a/network/http/web/api/category.go b/network/http/web/api/category.go new file mode 100644 index 0000000..939b534 --- /dev/null +++ b/network/http/web/api/category.go @@ -0,0 +1,46 @@ +package api + +import ( + "go_oxspeak_server/models" + "go_oxspeak_server/network/http/handler" + + "github.com/gin-gonic/gin" +) + +type CategoryHandler struct { + *handler.Handler +} + +func NewCategoryHandler(h *handler.Handler) *CategoryHandler { + return &CategoryHandler{h} +} + +func (h *CategoryHandler) RegisterRoutes(rg *gin.RouterGroup) { + category := rg.Group("/category") + category.GET("/", h.getCategories) + category.GET("/:id/", h.getCategory) + category.POST("/", h.addCategory) + category.PUT("/:id/", h.updateCategory) + category.DELETE("/:id/", h.deleteCategory) +} + +func (h *CategoryHandler) getCategories(c *gin.Context) { + var categories []models.Category + h.DB.Find(&categories) +} + +func (h *CategoryHandler) getCategory(c *gin.Context) { + var category models.Category + h.DB.Find(&category) +} + +func (h *CategoryHandler) addCategory(c *gin.Context) { + +} + +func (h *CategoryHandler) updateCategory(c *gin.Context) { + +} + +func (h *CategoryHandler) deleteCategory(c *gin.Context) { +} diff --git a/network/http/web/api/channel.go b/network/http/web/api/channel.go new file mode 100644 index 0000000..eaa5dd7 --- /dev/null +++ b/network/http/web/api/channel.go @@ -0,0 +1,51 @@ +package api + +import ( + "go_oxspeak_server/models" + "go_oxspeak_server/network/http/handler" + + "github.com/gin-gonic/gin" +) + +type ChannelHandler struct { + *handler.Handler +} + +func NewChannelHandler(h *handler.Handler) *ChannelHandler { + return &ChannelHandler{h} +} + +func (h *ChannelHandler) RegisterRoutes(rg *gin.RouterGroup) { + channel := rg.Group("/channel") + channel.GET("/", h.getChannels) + channel.GET("/:id/", h.getChannel) + channel.POST("/", h.addChannel) + channel.PUT("/:id/", h.updateChannel) + channel.DELETE("/:id/", h.deleteChannel) + +} + +func (h *ChannelHandler) getChannels(c *gin.Context) { + var users []models.User + h.DB.Find(&users) + +} + +func (h *ChannelHandler) getChannel(c *gin.Context) { + var user models.User + h.DB.Find(&user) +} + +func (h *ChannelHandler) addChannel(c *gin.Context) { + +} + +func (h *ChannelHandler) updateChannel(c *gin.Context) { + var user models.User + h.DB.Find(&user) +} + +func (h *ChannelHandler) deleteChannel(c *gin.Context) { + var user models.User + h.DB.Find(&user) +} diff --git a/network/http/web/api/message.go b/network/http/web/api/message.go new file mode 100644 index 0000000..cb53658 --- /dev/null +++ b/network/http/web/api/message.go @@ -0,0 +1,46 @@ +package api + +import ( + "go_oxspeak_server/models" + "go_oxspeak_server/network/http/handler" + + "github.com/gin-gonic/gin" +) + +type MessageHandler struct { + *handler.Handler +} + +func NewMessageHandler(h *handler.Handler) *MessageHandler { + return &MessageHandler{h} +} + +func (h *MessageHandler) RegisterRoutes(rg *gin.RouterGroup) { + message := rg.Group("/message") + message.GET("/", h.getMessages) + message.GET("/:id/", h.getMessage) + message.POST("/", h.addMessage) + message.PUT("/:id/", h.updateMessage) + message.DELETE("/:id/", h.deleteMessage) +} + +func (h *MessageHandler) getMessages(c *gin.Context) { + var messages []models.Message + h.DB.Find(&messages) +} + +func (h *MessageHandler) getMessage(c *gin.Context) { + var message models.Message + h.DB.Find(&message) +} + +func (h *MessageHandler) addMessage(c *gin.Context) { + +} + +func (h *MessageHandler) updateMessage(c *gin.Context) { + +} + +func (h *MessageHandler) deleteMessage(c *gin.Context) { +} diff --git a/network/http/web/api/server.go b/network/http/web/api/server.go new file mode 100644 index 0000000..2be02c5 --- /dev/null +++ b/network/http/web/api/server.go @@ -0,0 +1,46 @@ +package api + +import ( + "go_oxspeak_server/models" + "go_oxspeak_server/network/http/handler" + + "github.com/gin-gonic/gin" +) + +type ServerHandler struct { + *handler.Handler +} + +func NewServerHandler(h *handler.Handler) *ServerHandler { + return &ServerHandler{h} +} + +func (h *ServerHandler) RegisterRoutes(rg *gin.RouterGroup) { + server := rg.Group("/server") + server.GET("/", h.getServers) + server.GET("/:id/", h.getServer) + server.POST("/", h.addServer) + server.PUT("/:id/", h.updateServer) + server.DELETE("/:id/", h.deleteServer) +} + +func (h *ServerHandler) getServers(c *gin.Context) { + var servers []models.Server + h.DB.Find(&servers) +} + +func (h *ServerHandler) getServer(c *gin.Context) { + var server models.Server + h.DB.Find(&server) +} + +func (h *ServerHandler) addServer(c *gin.Context) { + +} + +func (h *ServerHandler) updateServer(c *gin.Context) { + +} + +func (h *ServerHandler) deleteServer(c *gin.Context) { +} diff --git a/network/http/web/auth.go b/network/http/web/auth.go new file mode 100644 index 0000000..3d152ca --- /dev/null +++ b/network/http/web/auth.go @@ -0,0 +1,72 @@ +package web + +import ( + "go_oxspeak_server/models" + "go_oxspeak_server/network/http/handler" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "gorm.io/gorm" +) + +type AuthHandler struct { + *handler.Handler +} + +func NewAuthHandler(h *handler.Handler) *AuthHandler { + return &AuthHandler{h} +} + +func (h *AuthHandler) RegisterRoutes(rg *gin.RouterGroup) { + channel := rg.Group("/channel") + channel.GET("/login/", h.authenticate) + +} + +type AuthRequest struct { + PublicKey string `json:"pub_key" binding:"required"` +} + +type AuthResponse struct { + JWT string `json:"JWT"` +} + +func (h *AuthHandler) authenticate(c *gin.Context) { + var req AuthRequest + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var user models.User + result := h.DB.Where("public_key = ?", req.PublicKey).First(&user) + if result.Error != nil { + if result.Error == gorm.ErrRecordNotFound { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid public key"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) + } + + // Generate token + claims := jwt.MapClaims{ + "user_id": user.ID, + "expiration_date": time.Now().Add(time.Hour * 72).Unix(), + "creation_date": time.Now().Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + // TODO: Remplacer par votre clé secrète (utiliser une variable d'environnement) + secretKey := []byte("votre-cle-secrete-a-changer") + + jwtString, err := token.SignedString(secretKey) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la génération du JWT"}) + return + } + + c.JSON(http.StatusOK, AuthResponse{JWT: jwtString}) +} diff --git a/network/http/web/main.go b/network/http/web/main.go new file mode 100644 index 0000000..90d6969 --- /dev/null +++ b/network/http/web/main.go @@ -0,0 +1,48 @@ +package web + +import ( + "go_oxspeak_server/database" + "go_oxspeak_server/network/http/handler" + "go_oxspeak_server/network/http/middleware" + "go_oxspeak_server/network/http/web/api" + + "github.com/gin-gonic/gin" +) + +func CreateRouter() *gin.Engine { + router := gin.Default() + + // Register middleware + router.Use(middleware.CORSMiddleware()) + + // Create base handler + baseHandler := handler.NewHandler(database.DB) + + // Create specific handler + authHandler := NewAuthHandler(baseHandler) + serverHandler := api.NewServerHandler(baseHandler) + categoryHandler := api.NewCategoryHandler(baseHandler) + channelHandler := api.NewChannelHandler(baseHandler) + messageHandler := api.NewMessageHandler(baseHandler) + + authGroup := router.Group("/auth") + { + authHandler.RegisterRoutes(authGroup) + } + + apiGroup := router.Group("/api") + { + serverHandler.RegisterRoutes(apiGroup) + channelHandler.RegisterRoutes(apiGroup) + categoryHandler.RegisterRoutes(apiGroup) + messageHandler.RegisterRoutes(apiGroup) + } + + router.GET("/health", healthcheck) + + return router +} + +func healthcheck(c *gin.Context) { + c.JSON(200, gin.H{"status": "ok"}) +} diff --git a/network/http/web/ws.go b/network/http/web/ws.go new file mode 100644 index 0000000..208ff33 --- /dev/null +++ b/network/http/web/ws.go @@ -0,0 +1,38 @@ +package web + +import ( + "go_oxspeak_server/network/http/handler" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" +) + +type WSHandler struct { + *handler.Handler +} + +func NewWSHandler(h *handler.Handler) *WSHandler { + return &WSHandler{h} +} + +func (h *WSHandler) RegisterRoutes(rg *gin.RouterGroup) { + ws := rg.Group("/ws") + ws.GET("/", h.handleWS) +} + +var upgrader = websocket.Upgrader{} + +func (h *WSHandler) handleWS(c *gin.Context) { + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + return + } + defer conn.Close() + + for { + _, _, err := conn.ReadMessage() + if err != nil { + break + } + } +} diff --git a/network/websocket/hub.go b/network/websocket/hub.go new file mode 100644 index 0000000..708bc8c --- /dev/null +++ b/network/websocket/hub.go @@ -0,0 +1 @@ +package websocket diff --git a/services/jwt.go b/services/jwt.go new file mode 100644 index 0000000..e751c5e --- /dev/null +++ b/services/jwt.go @@ -0,0 +1,97 @@ +package services + +import ( + "errors" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" +) + +type JWTService struct { + secretKey []byte + tokenDuration time.Duration +} + +type TokenClaims struct { + UserID string `json:"user_id"` + PubKey string `json:"pub_key"` + jwt.RegisteredClaims +} + +func NewJWTService(secretKey string, tokenDuration time.Duration) *JWTService { + return &JWTService{ + secretKey: []byte(secretKey), + tokenDuration: tokenDuration, + } +} + +// GenerateToken crée un nouveau JWT pour un utilisateur +func (s *JWTService) GenerateToken(userID uuid.UUID, pubKey string) (string, error) { + now := time.Now() + + claims := TokenClaims{ + UserID: userID.String(), + PubKey: pubKey, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(now.Add(s.tokenDuration)), + IssuedAt: jwt.NewNumericDate(now), + NotBefore: jwt.NewNumericDate(now), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + tokenString, err := token.SignedString(s.secretKey) + if err != nil { + return "", err + } + + return tokenString, nil +} + +// ValidateToken vérifie la validité d'un JWT et retourne les claims +func (s *JWTService) ValidateToken(tokenString string) (*TokenClaims, error) { + token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + // Vérifier que la méthode de signature est bien HMAC + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.New("méthode de signature invalide") + } + return s.secretKey, nil + }) + + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(*TokenClaims); ok && token.Valid { + return claims, nil + } + + return nil, errors.New("token invalide") +} + +// RenewToken renouvelle un token existant (si valide et proche de l'expiration) +func (s *JWTService) RenewToken(tokenString string) (string, error) { + claims, err := s.ValidateToken(tokenString) + if err != nil { + return "", err + } + + // Créer un nouveau token avec les mêmes informations + userID, err := uuid.Parse(claims.UserID) + if err != nil { + return "", err + } + + return s.GenerateToken(userID, claims.PubKey) +} + +// ExtractUserID extrait l'ID utilisateur d'un token (sans validation complète) +func (s *JWTService) ExtractUserID(tokenString string) (string, error) { + claims, err := s.ValidateToken(tokenString) + if err != nil { + return "", err + } + return claims.UserID, nil +}