Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 87270aa6 authored by Nicolas Melin's avatar Nicolas Melin
Browse files

Merge branch '3698-use-access-token-to-authenticate-user' into 'main'

Use accessToken to authenticate user

Closes e/os/backlog#3698

See merge request e/infra/proxy-stt!2
parents 30c81bbd 40fb249e
Loading
Loading
Loading
Loading
+54 −25
Original line number Diff line number Diff line
@@ -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{}
@@ -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
@@ -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"
@@ -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
+99 −23
Original line number Diff line number Diff line
@@ -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
	}

@@ -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);
+1 −1
Original line number Diff line number Diff line
@@ -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