Loading app/murena-api/murena-api.go +54 −25 Original line number Diff line number Diff line Loading @@ -20,7 +20,8 @@ var log = logger.GetLogger() // --- Types --- // ------------- type UserCanDoSTTInterface interface { UserCanDoSTT(reqID, username string) bool InvokeV1(reqID, username string) bool InvokeV2(reqID, accessToken string, username string) bool } type UserCanDoSTTProd struct{} Loading @@ -28,11 +29,24 @@ type UserCanDoSTTProd struct{} // -------------- // --- Public --- // -------------- func (UserCanDoSTTProd) InvokeV2(reqID string, accessToken string, username string) bool { path := fmt.Sprintf("/ocs/v2.php/cloud/users/%s/groups?format=json", username) request, err := initHttpRequestV2("GET", path, accessToken) return checkIfUserIsPremium(reqID, request, err) } func (UserCanDoSTTProd) UserCanDoSTT(reqID string, username string) bool { baseLog := fmt.Sprintf("[%s] [UserCanDoSTT]", reqID) func (UserCanDoSTTProd) InvokeV1(reqID string, username string) bool { path := fmt.Sprintf("/ocs/v2.php/cloud/users/%s/groups?format=json", username) request, err := initHttpRequest("GET", path) request, err := initHttpRequestV1("GET", path) return checkIfUserIsPremium(reqID, request, err) } // --------------- // --- Private --- // --------------- func checkIfUserIsPremium(reqID string, request *http.Request, err error) bool { baseLog := fmt.Sprintf("[%s] [checkIfUserIsPremium]", reqID) if(err != nil) { log.Error().Msgf("[%s] %v", reqID, err) return false Loading Loading @@ -80,36 +94,23 @@ func (UserCanDoSTTProd) UserCanDoSTT(reqID string, username string) bool { return userIsPremium } // --------------- // --- Private --- // --------------- func initHttpRequest(method string, path string) (*http.Request, error) { func initHttpRequestGeneric(method string, path string, authHeaderFunc func() (string, error)) (*http.Request, error) { host := os.Getenv("MURENA_API_HOST") host = strings.TrimSuffix(host, "/") // remove last caracter if it's a / if(host == "") { errorMessage := "Env MURENA_API_HOST is net set !" errorMessage := "Env MURENA_API_HOST is not set !" log.Fatal().Msg(errorMessage) return nil, errors.New(errorMessage) } adminUsername := os.Getenv("MURENA_API_ADMIN_USERNAME") if(adminUsername == "") { errorMessage := "Env MURENA_API_ADMIN_USERNAME is net set !" log.Fatal().Msg(errorMessage) return nil, errors.New(errorMessage) } url := fmt.Sprintf("%s%s", host, path) adminPassword := os.Getenv("MURENA_API_ADMIN_PASSWORD") if(adminPassword == "") { errorMessage := "Env MURENA_API_ADMIN_PASSWORD is net set !" log.Fatal().Msg(errorMessage) return nil, errors.New(errorMessage) authHeader, err := authHeaderFunc() if err != nil { log.Error().Msgf("Error generating Authorization header: %v", err) return nil, err } url := fmt.Sprintf("%s%s", host, path) auth := fmt.Sprintf("%s:%s", adminUsername, adminPassword) authHeader := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth))) req, err := http.NewRequest(method, url, nil) if err != nil { errorMessage := "Error : Fail to create http request" Loading @@ -122,3 +123,31 @@ func initHttpRequest(method string, path string) (*http.Request, error) { return req, nil } func initHttpRequestV2(method string, path string, accessToken string) (*http.Request, error) { return initHttpRequestGeneric(method, path, func() (string, error) { return fmt.Sprintf("Bearer %s", accessToken), nil }) } func initHttpRequestV1(method string, path string) (*http.Request, error) { return initHttpRequestGeneric(method, path, func() (string, error) { adminUsername := os.Getenv("MURENA_API_ADMIN_USERNAME") if(adminUsername == "") { errorMessage := "Env MURENA_API_ADMIN_USERNAME is not set !" log.Fatal().Msg(errorMessage) return "", errors.New(errorMessage) } adminPassword := os.Getenv("MURENA_API_ADMIN_PASSWORD") if(adminPassword == "") { errorMessage := "Env MURENA_API_ADMIN_PASSWORD is not set !" log.Fatal().Msg(errorMessage) return "", errors.New(errorMessage) } auth := fmt.Sprintf("%s:%s", adminUsername, adminPassword) return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth))), nil }) } No newline at end of file app/quic-router/quic-router.go +99 −23 Original line number Diff line number Diff line Loading @@ -57,12 +57,12 @@ func initQuicServer(router *gin.Engine) { crtPath := os.Getenv("CRT_PATH") if(crtPath == "") { log.Fatal().Msg("Env CRT_PATH is net set !") log.Fatal().Msg("Env CRT_PATH is not set !") return } keyPath := os.Getenv("KEY_PATH") if(keyPath == "") { log.Fatal().Msg("Env KEY_PATH is net set !") log.Fatal().Msg("Env KEY_PATH is not set !") return } Loading Loading @@ -91,38 +91,114 @@ func initRouter( WebsocketCloseConnection: websocketCloseConnection, } router.POST("/upload", handler.HandleUpload) router.POST("/upload", handler.AuthMiddlewareV1(), handler.HandleUpload) router.POST("/api/v2/upload", handler.AuthMiddlewareV2(), handler.HandleUpload) return router } func (h *UploadHandler) HandleUpload(context *gin.Context) { // ---------------------- // --- Middleware Auth -- // ---------------------- func (h *UploadHandler) AuthMiddlewareV2() gin.HandlerFunc { return func(c *gin.Context) { reqID := fmt.Sprintf("req_%d", time.Now().UnixNano()) baseLog := fmt.Sprintf("[%s] [POST /api/v2/upload]", reqID) log.Info().Msgf("%s New incoming upload, check authentication", baseLog) // Access Token // 1. Retrieve the token from the Authorization header authHeader := c.GetHeader("Authorization") if authHeader == "" { log.Error().Msgf("%s Missing Authorization header", baseLog) c.AbortWithStatus(401) return } // 2. Check the format: "Bearer <token>" const prefix = "Bearer " if !strings.HasPrefix(authHeader, prefix) { log.Error().Msgf("%s Invalid Authorization header format", baseLog) c.AbortWithStatus(401) return } // 3. Extract the token accessToken := strings.TrimPrefix(authHeader, prefix) accessToken = strings.TrimSpace(accessToken) // Lang lang := c.DefaultQuery("lang", "en") log.Debug().Msgf("%s Requested lang : %s", baseLog, lang) // Username username := c.DefaultQuery("username", "") log.Debug().Msgf("%s Requested username : %s", baseLog, username) if strings.HasSuffix(username, "@murena.io") { username = strings.TrimSuffix(username, "@murena.io") } log.Debug().Msgf("%s Formated username for Murena API request : %s", baseLog, username) granted := h.MurenaUserCanDoSTT.InvokeV2(reqID, accessToken, username) if(!granted) { log.Info().Msgf("%s User is not granted to call this API", baseLog) c.AbortWithStatus(403) return } c.Set("reqID", reqID) c.Set("lang", lang) c.Set("username", username) c.Set("baseLog", baseLog) c.Next() } } func (h *UploadHandler) AuthMiddlewareV1() gin.HandlerFunc { return func(c *gin.Context) { reqID := fmt.Sprintf("req_%d", time.Now().UnixNano()) baseLog := fmt.Sprintf("[%s] [POST /upload]", reqID) log.Info().Msgf("%s New incoming upload", baseLog) log.Info().Msgf("%s New incoming upload, check authentication", baseLog) lang := context.DefaultQuery("lang", "en") // Lang lang := c.DefaultQuery("lang", "en") log.Debug().Msgf("%s Requested lang : %s", baseLog, lang) username := context.DefaultQuery("username", "") // Username username := c.DefaultQuery("username", "") log.Debug().Msgf("%s Requested username : %s", baseLog, username) if strings.HasSuffix(username, "@murena.io") { username = strings.TrimSuffix(username, "@murena.io") } log.Debug().Msgf("%s Formated username for Murena API request : %s", baseLog, username) granted := h.MurenaUserCanDoSTT.UserCanDoSTT(reqID, username) granted := h.MurenaUserCanDoSTT.InvokeV1(reqID, username) if(!granted) { log.Info().Msgf("%s User is not granted to call this API", baseLog) context.AbortWithStatus(403) c.AbortWithStatus(403) return } // Ici tu peux générer un ID de session unique pour chaque client clientID := fmt.Sprintf("client_%d", time.Now().UnixNano()) // ID de session unique c.Set("reqID", reqID) c.Set("lang", lang) c.Set("username", username) c.Set("baseLog", baseLog) c.Next() } } // ----------------------- // --- Upload Handlers --- // ----------------------- func (h *UploadHandler) HandleUpload(context *gin.Context) { reqID := context.GetString("reqID") baseLog := context.GetString("baseLog") lang := context.GetString("lang") clientID := fmt.Sprintf("client_%d", time.Now().UnixNano()) stopRead := make(chan bool) // Créer une connexion WebSocket pour ce client go h.WebsocketStartConnection.StartConnection(reqID, lang, clientID, context, stopRead) defer func() { h.WebsocketCloseConnection.CloseConnection(reqID, clientID, stopRead); Loading test/mocks/murena-api-mock.go +1 −1 Original line number Diff line number Diff line Loading @@ -8,6 +8,6 @@ type UserCanDoSTTMock struct {} // -------------- // --- Public --- // -------------- func (UserCanDoSTTMock) UserCanDoSTT(reqID string, username string) bool { func (UserCanDoSTTMock) UserCanDoSTT(reqID string, accessToken string, username string) bool { return username == "murena" } No newline at end of file Loading
app/murena-api/murena-api.go +54 −25 Original line number Diff line number Diff line Loading @@ -20,7 +20,8 @@ var log = logger.GetLogger() // --- Types --- // ------------- type UserCanDoSTTInterface interface { UserCanDoSTT(reqID, username string) bool InvokeV1(reqID, username string) bool InvokeV2(reqID, accessToken string, username string) bool } type UserCanDoSTTProd struct{} Loading @@ -28,11 +29,24 @@ type UserCanDoSTTProd struct{} // -------------- // --- Public --- // -------------- func (UserCanDoSTTProd) InvokeV2(reqID string, accessToken string, username string) bool { path := fmt.Sprintf("/ocs/v2.php/cloud/users/%s/groups?format=json", username) request, err := initHttpRequestV2("GET", path, accessToken) return checkIfUserIsPremium(reqID, request, err) } func (UserCanDoSTTProd) UserCanDoSTT(reqID string, username string) bool { baseLog := fmt.Sprintf("[%s] [UserCanDoSTT]", reqID) func (UserCanDoSTTProd) InvokeV1(reqID string, username string) bool { path := fmt.Sprintf("/ocs/v2.php/cloud/users/%s/groups?format=json", username) request, err := initHttpRequest("GET", path) request, err := initHttpRequestV1("GET", path) return checkIfUserIsPremium(reqID, request, err) } // --------------- // --- Private --- // --------------- func checkIfUserIsPremium(reqID string, request *http.Request, err error) bool { baseLog := fmt.Sprintf("[%s] [checkIfUserIsPremium]", reqID) if(err != nil) { log.Error().Msgf("[%s] %v", reqID, err) return false Loading Loading @@ -80,36 +94,23 @@ func (UserCanDoSTTProd) UserCanDoSTT(reqID string, username string) bool { return userIsPremium } // --------------- // --- Private --- // --------------- func initHttpRequest(method string, path string) (*http.Request, error) { func initHttpRequestGeneric(method string, path string, authHeaderFunc func() (string, error)) (*http.Request, error) { host := os.Getenv("MURENA_API_HOST") host = strings.TrimSuffix(host, "/") // remove last caracter if it's a / if(host == "") { errorMessage := "Env MURENA_API_HOST is net set !" errorMessage := "Env MURENA_API_HOST is not set !" log.Fatal().Msg(errorMessage) return nil, errors.New(errorMessage) } adminUsername := os.Getenv("MURENA_API_ADMIN_USERNAME") if(adminUsername == "") { errorMessage := "Env MURENA_API_ADMIN_USERNAME is net set !" log.Fatal().Msg(errorMessage) return nil, errors.New(errorMessage) } url := fmt.Sprintf("%s%s", host, path) adminPassword := os.Getenv("MURENA_API_ADMIN_PASSWORD") if(adminPassword == "") { errorMessage := "Env MURENA_API_ADMIN_PASSWORD is net set !" log.Fatal().Msg(errorMessage) return nil, errors.New(errorMessage) authHeader, err := authHeaderFunc() if err != nil { log.Error().Msgf("Error generating Authorization header: %v", err) return nil, err } url := fmt.Sprintf("%s%s", host, path) auth := fmt.Sprintf("%s:%s", adminUsername, adminPassword) authHeader := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth))) req, err := http.NewRequest(method, url, nil) if err != nil { errorMessage := "Error : Fail to create http request" Loading @@ -122,3 +123,31 @@ func initHttpRequest(method string, path string) (*http.Request, error) { return req, nil } func initHttpRequestV2(method string, path string, accessToken string) (*http.Request, error) { return initHttpRequestGeneric(method, path, func() (string, error) { return fmt.Sprintf("Bearer %s", accessToken), nil }) } func initHttpRequestV1(method string, path string) (*http.Request, error) { return initHttpRequestGeneric(method, path, func() (string, error) { adminUsername := os.Getenv("MURENA_API_ADMIN_USERNAME") if(adminUsername == "") { errorMessage := "Env MURENA_API_ADMIN_USERNAME is not set !" log.Fatal().Msg(errorMessage) return "", errors.New(errorMessage) } adminPassword := os.Getenv("MURENA_API_ADMIN_PASSWORD") if(adminPassword == "") { errorMessage := "Env MURENA_API_ADMIN_PASSWORD is not set !" log.Fatal().Msg(errorMessage) return "", errors.New(errorMessage) } auth := fmt.Sprintf("%s:%s", adminUsername, adminPassword) return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth))), nil }) } No newline at end of file
app/quic-router/quic-router.go +99 −23 Original line number Diff line number Diff line Loading @@ -57,12 +57,12 @@ func initQuicServer(router *gin.Engine) { crtPath := os.Getenv("CRT_PATH") if(crtPath == "") { log.Fatal().Msg("Env CRT_PATH is net set !") log.Fatal().Msg("Env CRT_PATH is not set !") return } keyPath := os.Getenv("KEY_PATH") if(keyPath == "") { log.Fatal().Msg("Env KEY_PATH is net set !") log.Fatal().Msg("Env KEY_PATH is not set !") return } Loading Loading @@ -91,38 +91,114 @@ func initRouter( WebsocketCloseConnection: websocketCloseConnection, } router.POST("/upload", handler.HandleUpload) router.POST("/upload", handler.AuthMiddlewareV1(), handler.HandleUpload) router.POST("/api/v2/upload", handler.AuthMiddlewareV2(), handler.HandleUpload) return router } func (h *UploadHandler) HandleUpload(context *gin.Context) { // ---------------------- // --- Middleware Auth -- // ---------------------- func (h *UploadHandler) AuthMiddlewareV2() gin.HandlerFunc { return func(c *gin.Context) { reqID := fmt.Sprintf("req_%d", time.Now().UnixNano()) baseLog := fmt.Sprintf("[%s] [POST /api/v2/upload]", reqID) log.Info().Msgf("%s New incoming upload, check authentication", baseLog) // Access Token // 1. Retrieve the token from the Authorization header authHeader := c.GetHeader("Authorization") if authHeader == "" { log.Error().Msgf("%s Missing Authorization header", baseLog) c.AbortWithStatus(401) return } // 2. Check the format: "Bearer <token>" const prefix = "Bearer " if !strings.HasPrefix(authHeader, prefix) { log.Error().Msgf("%s Invalid Authorization header format", baseLog) c.AbortWithStatus(401) return } // 3. Extract the token accessToken := strings.TrimPrefix(authHeader, prefix) accessToken = strings.TrimSpace(accessToken) // Lang lang := c.DefaultQuery("lang", "en") log.Debug().Msgf("%s Requested lang : %s", baseLog, lang) // Username username := c.DefaultQuery("username", "") log.Debug().Msgf("%s Requested username : %s", baseLog, username) if strings.HasSuffix(username, "@murena.io") { username = strings.TrimSuffix(username, "@murena.io") } log.Debug().Msgf("%s Formated username for Murena API request : %s", baseLog, username) granted := h.MurenaUserCanDoSTT.InvokeV2(reqID, accessToken, username) if(!granted) { log.Info().Msgf("%s User is not granted to call this API", baseLog) c.AbortWithStatus(403) return } c.Set("reqID", reqID) c.Set("lang", lang) c.Set("username", username) c.Set("baseLog", baseLog) c.Next() } } func (h *UploadHandler) AuthMiddlewareV1() gin.HandlerFunc { return func(c *gin.Context) { reqID := fmt.Sprintf("req_%d", time.Now().UnixNano()) baseLog := fmt.Sprintf("[%s] [POST /upload]", reqID) log.Info().Msgf("%s New incoming upload", baseLog) log.Info().Msgf("%s New incoming upload, check authentication", baseLog) lang := context.DefaultQuery("lang", "en") // Lang lang := c.DefaultQuery("lang", "en") log.Debug().Msgf("%s Requested lang : %s", baseLog, lang) username := context.DefaultQuery("username", "") // Username username := c.DefaultQuery("username", "") log.Debug().Msgf("%s Requested username : %s", baseLog, username) if strings.HasSuffix(username, "@murena.io") { username = strings.TrimSuffix(username, "@murena.io") } log.Debug().Msgf("%s Formated username for Murena API request : %s", baseLog, username) granted := h.MurenaUserCanDoSTT.UserCanDoSTT(reqID, username) granted := h.MurenaUserCanDoSTT.InvokeV1(reqID, username) if(!granted) { log.Info().Msgf("%s User is not granted to call this API", baseLog) context.AbortWithStatus(403) c.AbortWithStatus(403) return } // Ici tu peux générer un ID de session unique pour chaque client clientID := fmt.Sprintf("client_%d", time.Now().UnixNano()) // ID de session unique c.Set("reqID", reqID) c.Set("lang", lang) c.Set("username", username) c.Set("baseLog", baseLog) c.Next() } } // ----------------------- // --- Upload Handlers --- // ----------------------- func (h *UploadHandler) HandleUpload(context *gin.Context) { reqID := context.GetString("reqID") baseLog := context.GetString("baseLog") lang := context.GetString("lang") clientID := fmt.Sprintf("client_%d", time.Now().UnixNano()) stopRead := make(chan bool) // Créer une connexion WebSocket pour ce client go h.WebsocketStartConnection.StartConnection(reqID, lang, clientID, context, stopRead) defer func() { h.WebsocketCloseConnection.CloseConnection(reqID, clientID, stopRead); Loading
test/mocks/murena-api-mock.go +1 −1 Original line number Diff line number Diff line Loading @@ -8,6 +8,6 @@ type UserCanDoSTTMock struct {} // -------------- // --- Public --- // -------------- func (UserCanDoSTTMock) UserCanDoSTT(reqID string, username string) bool { func (UserCanDoSTTMock) UserCanDoSTT(reqID string, accessToken string, username string) bool { return username == "murena" } No newline at end of file