package main import ( "crypto/subtle" "fmt" "log" "net/http" "text/template" "codeberg.org/vlbeaudoin/pave/v2" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) func RunServer() { e := echo.New() t := &Template{ templates: template.Must(template.ParseFS(templatesFS, "html/*.html")), } e.Renderer = t e.Pre(middleware.RemoveTrailingSlash()) groupStatic := e.Group("/public/*") groupStatic.Use(middleware.StaticWithConfig(middleware.StaticConfig{ Root: "/", Filesystem: http.FS(publicFS), //TODO //Browse: true, })) groupV1 := e.Group("/v1") groupV1.Use(middleware.AddTrailingSlash()) if cfg.Server.Api.Auth { if len(cfg.Server.Api.Key) < 10 { log.Fatal("server.api.auth is enabled, but server.api.key is too small (needs at least 10 characters)") } groupV1.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.Server.Api.Key)) == 1, nil })) log.Println("Key auth for /v1 activated") } groupAdmin := e.Group("/admin") groupAdmin.Use(middleware.AddTrailingSlash()) if cfg.Server.Admin.Auth { if len(cfg.Server.Admin.Username) < 5 { log.Fatal("server.admin.auth is enabled, but server.admin.username is too small (needs at least 5 characters)") } if len(cfg.Server.Admin.Password) < 10 { log.Fatal("server.admin.auth is enabled, but server.admin.password is too small (needs at least 10 characters)") } groupAdmin.Use(middleware.BasicAuth(func(username_entered, password_entered string, c echo.Context) (bool, error) { // Be careful to use constant time comparison to prevent timing attacks if subtle.ConstantTimeCompare([]byte(username_entered), []byte(cfg.Server.Admin.Username)) == 1 && subtle.ConstantTimeCompare([]byte(password_entered), []byte(cfg.Server.Admin.Password)) == 1 { return true, nil } return false, nil })) log.Println("Basic auth for /admin activated") } // API Routes mediaClient, err := NewMediaClientFromViper() if err != nil { log.Fatal("Error during NewMediaClientFromViper for API handlers") } p := pave.New() v1Handler := V1Handler{ Config: cfg, MediaClient: mediaClient, Pave: &p, } groupV1.GET("", v1Handler.ListRoutes) if err := pave.EchoRegister[ ExecuteSeedRequest, ExecuteSeedResponse](groupV1, &p, "/v1", http.MethodPost, "/seed", "Créer buckets manquants définis dans `server.documents.buckets`", "ExecuteSeed", v1Handler.ExecuteSeed); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ UpdateDocumentKeyRequest, UpdateDocumentKeyResponse](groupV1, &p, "/v1", http.MethodPut, "/bucket/:bucket/:document/key", "Renommer un document", "UpdateDocumentKey", v1Handler.UpdateDocumentKey); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ ReadSpecRequest, ReadSpecResponse](groupV1, &p, "/v1", http.MethodGet, "/spec", DescriptionV1SpecGET, "SpecRead", v1Handler.ReadSpec); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ ListBucketsRequest, ListBucketsResponse](groupV1, &p, "/v1", http.MethodGet, "/bucket", "List buckets", "ListBuckets", v1Handler.ListBuckets); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ ReadBucketRequest, ReadBucketResponse](groupV1, &p, "/v1", http.MethodGet, "/bucket/:bucket", "Read bucket content", "ReadBucket", v1Handler.ReadBucket); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ CreateDocumentsRequest, CreateDocumentsResponse](groupV1, &p, "/v1", http.MethodPost, "/bucket/:bucket/many", "Upload documents to specified bucket", "CreateDocuments", v1Handler.CreateDocuments); err != nil { log.Fatal(err) } if err := pave.EchoRegister[ CreateDocumentRequest, CreateDocumentResponse](groupV1, &p, "/v1", http.MethodPost, "/bucket/:bucket", "Upload document to specified bucket", "CreateDocument", v1Handler.CreateDocument); err != nil { log.Fatal(err) } // Do not move to pave, uses echo.Stream instead of echo.JSON groupV1.GET("/bucket/:bucket/:document", v1Handler.ReadDocument) if err := pave.EchoRegister[ DeleteDocumentRequest, DeleteDocumentResponse](groupV1, &p, "/v1", http.MethodDelete, "/bucket/:bucket/:document", "Delete document in specified bucket", "DeleteDocument", v1Handler.DeleteDocument); err != nil { log.Fatal(err) } // HTML Routes client := http.DefaultClient defer client.CloseIdleConnections() apiClient, err := NewAPIClientFromViper(client) if err != nil { log.Fatal(err) } webHandler := WebHandler{ ApiClient: apiClient, } e.GET("/", HandleIndex) //e.GET("/a-propos", HandleAPropos) //e.GET("/actualite", HandleActualite) //e.GET("/actualite/:article", HandleActualiteArticle) e.GET("/vie-etudiante", HandleVieEtudiante) e.GET("/vie-etudiante/:organisme", HandleVieEtudianteOrganisme) e.GET("/documentation", webHandler.HandleDocumentation) e.GET("/formulaires", HandleFormulaires) // Public Routes e.GET("/public/documentation/:bucket/:document", webHandler.HandlePublicDocumentation) // Admin Routes groupAdmin.GET("", HandleAdmin) groupAdmin.GET("/documents/upload", webHandler.HandleAdminDocumentsUpload) groupAdmin.POST("/documents/upload", webHandler.HandleAdminDocumentsUploadPOST) e.Logger.Fatal(e.Start( fmt.Sprintf(":%d", cfg.Server.Port))) }