Start refactoring
This commit is contained in:
246
server/server.go
246
server/server.go
@@ -1,166 +1,138 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"log"
|
||||
"net/http"
|
||||
"opensavecloudserver/authentication"
|
||||
"opensavecloudserver/config"
|
||||
"opensavecloudserver/database"
|
||||
"opensavecloudserver/upload"
|
||||
"opensavecloudserver/data/repository/game"
|
||||
"opensavecloudserver/data/repository/user"
|
||||
"opensavecloudserver/server/authentication"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
type (
|
||||
HTTPServer struct {
|
||||
Server *http.Server
|
||||
config config.Configuration
|
||||
deps DatasourceDependencies
|
||||
}
|
||||
|
||||
const (
|
||||
UserIdKey ContextKey = "userId"
|
||||
GameIdKey ContextKey = "gameId"
|
||||
DatasourceDependencies struct {
|
||||
UserRepository user.UserRepository
|
||||
GameRepository game.GameRepository
|
||||
Authenticator authentication.Authenticator
|
||||
}
|
||||
)
|
||||
|
||||
// Serve start the http server
|
||||
func Serve() {
|
||||
// NewServer start the http server
|
||||
func NewServer(config config.Configuration, deps DatasourceDependencies) *HTTPServer {
|
||||
s := &HTTPServer{config: config, deps: deps}
|
||||
router := chi.NewRouter()
|
||||
router.NotFound(func(writer http.ResponseWriter, request *http.Request) {
|
||||
notFound("This route does not exist", writer, request)
|
||||
})
|
||||
router.MethodNotAllowed(func(writer http.ResponseWriter, request *http.Request) {
|
||||
methodNotAllowed(writer, request)
|
||||
})
|
||||
router.Use(middleware.Logger)
|
||||
router.Use(recovery)
|
||||
router.Route("/api", func(rApi chi.Router) {
|
||||
rApi.Route("/v1", func(r chi.Router) {
|
||||
r.Post("/login", Login)
|
||||
r.Post("/check/token", CheckToken)
|
||||
if config.Features().AllowRegister {
|
||||
r.Post("/register", Register)
|
||||
router.Use(recoverMiddleware)
|
||||
if config.Server.Compress {
|
||||
router.Use(middleware.Compress(5, "application/json", "application/octet‑stream"))
|
||||
}
|
||||
if config.Server.PingEndPoint {
|
||||
router.Use(middleware.Heartbeat("/heartbeat"))
|
||||
}
|
||||
router.Route("/api", func(routerAPI chi.Router) {
|
||||
routerAPI.Route("/v1", func(r chi.Router) {
|
||||
// Unsupported V1 because it was shitty, sorry about that
|
||||
r.HandleFunc("/*", unsupportedAPIHandler)
|
||||
})
|
||||
routerAPI.Route("/v2", func(r chi.Router) {
|
||||
// Get information about the server
|
||||
r.Get("/version", s.Information)
|
||||
// Authentication routes
|
||||
// Login and get a token
|
||||
r.Post("/login", s.loginUserHandler)
|
||||
if config.Features.AllowRegister {
|
||||
// Register a user
|
||||
r.Post("/register", s.registerUserHandler)
|
||||
}
|
||||
r.Route("/system", func(systemRouter chi.Router) {
|
||||
systemRouter.Get("/information", Information)
|
||||
})
|
||||
r.Route("/admin", func(adminRouter chi.Router) {
|
||||
adminRouter.Use(adminMiddleware)
|
||||
adminRouter.Post("/user", AddUser)
|
||||
adminRouter.Post("/user/username", ChangeUsername)
|
||||
adminRouter.Post("/user/passwd/{id}", ChangeUserPassword)
|
||||
adminRouter.Delete("/user/{id}", RemoveUser)
|
||||
adminRouter.Get("/user/{id}", User)
|
||||
adminRouter.Get("/users", AllUsers)
|
||||
adminRouter.Get("/user/role/admin/{id}", SetAdmin)
|
||||
adminRouter.Get("/user/role/user/{id}", SetNotAdmin)
|
||||
})
|
||||
// Secured routes
|
||||
r.Group(func(secureRouter chi.Router) {
|
||||
secureRouter.Use(authMiddleware)
|
||||
secureRouter.Use(s.authMiddleware)
|
||||
// Logged user routes
|
||||
secureRouter.Route("/user", func(userRouter chi.Router) {
|
||||
userRouter.Get("/information", UserInformation)
|
||||
userRouter.Post("/passwd", ChangePassword)
|
||||
// Get information about the logged user
|
||||
userRouter.Get("/", s.currentUserHandler)
|
||||
// Change the password of the current user
|
||||
userRouter.Put("/password/update", s.updateCurrentUserPasswordHandler)
|
||||
})
|
||||
secureRouter.Route("/game", func(gameRouter chi.Router) {
|
||||
gameRouter.Post("/create", CreateGame)
|
||||
gameRouter.Get("/all", AllGamesInformation)
|
||||
gameRouter.Delete("/remove/{id}", RemoveGame)
|
||||
gameRouter.Get("/info/{id}", GameInfoByID)
|
||||
gameRouter.Post("/upload/init", AskForUpload)
|
||||
// Save files routes
|
||||
secureRouter.Route("/saves", func(gameRouter chi.Router) {
|
||||
// Create a save entry
|
||||
gameRouter.Post("/create", s.createSaveEntryHandler)
|
||||
// List all available saves
|
||||
gameRouter.Get("/", s.allUserSavesInformationHandler)
|
||||
// Remove a save
|
||||
gameRouter.Delete("/{id}", s.deleteSave)
|
||||
// Get the information about a save
|
||||
gameRouter.Get("/{id}", s.saveInformationHandler)
|
||||
// Data routes
|
||||
gameRouter.Group(func(uploadRouter chi.Router) {
|
||||
uploadRouter.Use(uploadMiddleware)
|
||||
uploadRouter.Post("/upload", UploadSave)
|
||||
uploadRouter.Get("/download", Download)
|
||||
uploadRouter.Use(s.uploadMiddleware)
|
||||
// Upload data
|
||||
uploadRouter.Put("/{id}/data", s.uploadDataHandler)
|
||||
// downloadDataHandler data
|
||||
uploadRouter.Get("/{id}/data", s.downloadDataHandler)
|
||||
})
|
||||
})
|
||||
secureRouter.Route("/admin", func(adminRouter chi.Router) {
|
||||
adminRouter.Use(s.adminMiddleware)
|
||||
// Create a user
|
||||
adminRouter.Post("/user/create", s.createUserHandler)
|
||||
// Update the username of a user
|
||||
adminRouter.Post("/user/{id}/username", s.updateUsernameHandler)
|
||||
// Update the password of a user
|
||||
adminRouter.Post("/user/{id}/password/update", s.updateUserPasswordHandler)
|
||||
// Remove a user
|
||||
adminRouter.Delete("/user/{id}", s.deleteUserHandler)
|
||||
// Get information about a user
|
||||
adminRouter.Get("/user/{id}", s.userHandler)
|
||||
// List all user registered on the server
|
||||
adminRouter.Get("/users", s.listAllServerUsersHandler)
|
||||
// Update role
|
||||
adminRouter.Put("/user/{id}/role", s.updateUserRoleHandler)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
log.Println("Server is listening...")
|
||||
err := http.ListenAndServe(fmt.Sprintf(":%d", config.Server().Port), router)
|
||||
s.Server = &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", config.Server.Port),
|
||||
Handler: router,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func unsupportedAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
e := httpError{
|
||||
Status: http.StatusGone,
|
||||
Error: "API Not supported anymore",
|
||||
Message: "This version of the server does not support the V1 version of the API.",
|
||||
Path: r.RequestURI,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
payload, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Println(err)
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusGone)
|
||||
_, err = w.Write(payload)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
// authMiddleware check the authentication token before accessing to the resource
|
||||
func authMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
header := r.Header.Get("Authorization")
|
||||
if len(header) > 7 {
|
||||
userId, err := authentication.ParseToken(header[7:])
|
||||
if err != nil {
|
||||
unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), UserIdKey, userId)
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
unauthorized(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// adminMiddleware check the role of the user before accessing to the resource
|
||||
func adminMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
header := r.Header.Get("Authorization")
|
||||
if len(header) > 7 {
|
||||
userId, err := authentication.ParseToken(header[7:])
|
||||
if err != nil {
|
||||
unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
user, err := database.UserById(userId)
|
||||
if err != nil {
|
||||
internalServerError(w, r)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
if !user.IsAdmin {
|
||||
forbidden(w, r)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), UserIdKey, userId)
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
unauthorized(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// uploadMiddleware check the upload key before allowing to upload a file
|
||||
func uploadMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
header := r.Header.Get("X-Upload-Key")
|
||||
if len(header) > 0 {
|
||||
if gameId, ok := upload.CheckUploadToken(header); ok {
|
||||
ctx := context.WithValue(r.Context(), GameIdKey, gameId)
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
unauthorized(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func userIdFromContext(ctx context.Context) (int, error) {
|
||||
if userId, ok := ctx.Value(UserIdKey).(int); ok {
|
||||
return userId, nil
|
||||
}
|
||||
return 0, errors.New("userId not found in context")
|
||||
}
|
||||
|
||||
func gameIdFromContext(ctx context.Context) (int, error) {
|
||||
if gameId, ok := ctx.Value(GameIdKey).(int); ok {
|
||||
return gameId, nil
|
||||
}
|
||||
return 0, errors.New("gameId not found in context")
|
||||
}
|
||||
|
||||
func recovery(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
internalServerError(w, r)
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user