package gpaste import ( "encoding/json" "io" "net/http" "strings" "git.t-juice.club/torjus/gpaste/files" "git.t-juice.club/torjus/gpaste/users" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/google/uuid" "go.uber.org/zap" ) type HTTPServer struct { Files files.FileStore Users users.UserStore Auth *AuthService config *ServerConfig Logger *zap.SugaredLogger AccessLogger *zap.SugaredLogger http.Server } func NewHTTPServer(cfg *ServerConfig) *HTTPServer { srv := &HTTPServer{ config: cfg, Logger: zap.NewNop().Sugar(), AccessLogger: zap.NewNop().Sugar(), } srv.Files = files.NewMemoryFileStore() srv.Users = users.NewMemoryUserStore() srv.Auth = NewAuthService(srv.Users, []byte(srv.config.SigningSecret)) // Create initial user // TODO: Do properly user := &users.User{Username: "admin"} user.SetPassword("admin") srv.Users.Store(user) r := chi.NewRouter() r.Use(middleware.RealIP) r.Use(middleware.RequestID) r.Use(srv.MiddlewareAccessLogger) r.Use(srv.MiddlewareAuthentication) r.Get("/", srv.HandlerIndex) r.Post("/api/file", srv.HandlerAPIFilePost) r.Get("/api/file/{id}", srv.HandlerAPIFileGet) r.Post("/api/login", srv.HandlerAPILogin) r.Post("/api/user", srv.HandlerAPIUserCreate) srv.Handler = r return srv } func (s *HTTPServer) HandlerIndex(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("index")) } func (s *HTTPServer) HandlerAPIFilePost(w http.ResponseWriter, r *http.Request) { f := &files.File{ ID: uuid.Must(uuid.NewRandom()).String(), Body: r.Body, } reqID := middleware.GetReqID(r.Context()) // Check if multipart form ct := r.Header.Get("Content-Type") if strings.Contains(ct, "multipart/form-data") { s.processMultiPartFormUpload(w, r) return } err := s.Files.Store(f) if err != nil { w.WriteHeader(http.StatusInternalServerError) s.Logger.Warnw("Error storing file.", "req_id", reqID, "error", err, "id", f.ID, "remote_addr", r.RemoteAddr) return } s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "remote_addr", r.RemoteAddr) var resp = struct { Message string `json:"message"` ID string `json:"id"` URL string `json:"url"` }{ Message: "OK", ID: f.ID, URL: "TODO", } w.WriteHeader(http.StatusAccepted) encoder := json.NewEncoder(w) if err := encoder.Encode(&resp); err != nil { s.Logger.Warnw("Error encoding response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) } } func (s *HTTPServer) HandlerAPIFileGet(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") if id == "" { w.WriteHeader(http.StatusBadRequest) return } f, err := s.Files.Get(id) if err != nil { // TODO: LOG w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) if _, err := io.Copy(w, f.Body); err != nil { reqID := middleware.GetReqID(r.Context()) s.Logger.Warnw("Error writing file to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) } } func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.Request) { reqID := middleware.GetReqID(r.Context()) type resp struct { Message string `json:"message"` ID string `json:"id"` URL string `json:"url"` } var responses []resp if err := r.ParseMultipartForm(1024 * 1024 * 10); err != nil { s.Logger.Warnw("Error parsing multipart form.", "req_id", reqID, "err", err) } for k := range r.MultipartForm.File { ff, fh, err := r.FormFile(k) if err != nil { s.Logger.Warnw("Error reading file from multipart form.", "req_id", reqID, "error", err) return } f := &files.File{ ID: uuid.Must(uuid.NewRandom()).String(), OriginalFilename: fh.Filename, Body: ff, } if err := s.Files.Store(f); err != nil { w.WriteHeader(http.StatusInternalServerError) s.Logger.Warnw("Error storing file.", "req_id", reqID, "error", err, "id", f.ID, "remote_addr", r.RemoteAddr) return } s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "filename", f.OriginalFilename, "remote_addr", r.RemoteAddr) responses = append(responses, resp{Message: "OK", ID: f.ID, URL: "TODO"}) } w.WriteHeader(http.StatusAccepted) encoder := json.NewEncoder(w) if err := encoder.Encode(&responses); err != nil { s.Logger.Warnw("Error encoding response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) } } func (s *HTTPServer) HandlerAPILogin(w http.ResponseWriter, r *http.Request) { reqID := middleware.GetReqID(r.Context()) expectedRequest := struct { Username string `json:"username"` Password string `json:"password"` }{} decoder := json.NewDecoder(r.Body) defer r.Body.Close() if err := decoder.Decode(&expectedRequest); err != nil { w.WriteHeader(http.StatusBadRequest) return } token, err := s.Auth.Login(expectedRequest.Username, expectedRequest.Password) if err != nil { w.WriteHeader(http.StatusUnauthorized) return } response := struct { Token string `json:"token"` }{ Token: token, } s.Logger.Infow("User logged in.", "req_id", reqID, "username", expectedRequest.Username) encoder := json.NewEncoder(w) if err := encoder.Encode(&response); err != nil { s.Logger.Infow("Error encoding json response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) } } type RequestAPIUserCreate struct { Username string `json:"username"` Password string `json:"password"` } func (s *HTTPServer) HandlerAPIUserCreate(w http.ResponseWriter, r *http.Request) { reqID := middleware.GetReqID(r.Context()) defer r.Body.Close() level, err := AuthLevelFromRequest(r) if err != nil || level < AuthLevelAdmin { w.WriteHeader(http.StatusUnauthorized) return } var req RequestAPIUserCreate decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&req); err != nil { s.Logger.Debugw("Error parsing request.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) w.WriteHeader(http.StatusBadRequest) return } // TODO: Ensure user does not already exist user := &users.User{Username: req.Username} if err := user.SetPassword(req.Password); err != nil { s.Logger.Warnw("Error setting user password.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) w.WriteHeader(http.StatusBadRequest) return } if err := s.Users.Store(user); err != nil { s.Logger.Warnw("Error setting user password.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) w.WriteHeader(http.StatusInternalServerError) return } s.Logger.Infow("Created user.", "req_id", reqID, "remote_addr", r.RemoteAddr, "username", req.Username) }