gpaste/api/http.go

303 lines
7.8 KiB
Go
Raw Permalink Normal View History

2022-01-20 02:44:33 +00:00
package api
2022-01-15 20:53:22 +00:00
import (
"encoding/json"
"io"
"net/http"
2022-01-24 18:03:20 +00:00
"path"
2022-01-24 15:29:34 +00:00
"strconv"
2022-01-16 20:29:42 +00:00
"strings"
2022-01-24 15:29:34 +00:00
"time"
2022-01-15 20:53:22 +00:00
2022-01-20 02:44:33 +00:00
"git.t-juice.club/torjus/gpaste"
2022-01-20 02:40:32 +00:00
"git.t-juice.club/torjus/gpaste/files"
2022-01-20 02:35:55 +00:00
"git.t-juice.club/torjus/gpaste/users"
2022-01-15 20:53:22 +00:00
"github.com/go-chi/chi/v5"
2022-01-19 02:23:54 +00:00
"github.com/go-chi/chi/v5/middleware"
2022-01-15 20:53:22 +00:00
"github.com/google/uuid"
2022-01-15 21:32:24 +00:00
"go.uber.org/zap"
2022-01-15 20:53:22 +00:00
)
2022-01-24 19:25:52 +00:00
const multipartMaxMemory = 1024 * 1024 * 100
2022-01-15 20:53:22 +00:00
type HTTPServer struct {
2022-01-20 02:40:32 +00:00
Files files.FileStore
2022-01-20 02:35:55 +00:00
Users users.UserStore
2022-01-20 02:44:33 +00:00
Auth *gpaste.AuthService
config *gpaste.ServerConfig
2022-01-19 02:23:54 +00:00
Logger *zap.SugaredLogger
AccessLogger *zap.SugaredLogger
2022-01-15 20:53:22 +00:00
http.Server
}
2022-01-20 02:44:33 +00:00
func NewHTTPServer(cfg *gpaste.ServerConfig) *HTTPServer {
2022-01-15 20:53:22 +00:00
srv := &HTTPServer{
2022-01-19 02:23:54 +00:00
config: cfg,
Logger: zap.NewNop().Sugar(),
AccessLogger: zap.NewNop().Sugar(),
2022-01-25 00:57:28 +00:00
Files: files.NewMemoryFileStore(),
Users: users.NewMemoryUserStore(),
2022-01-15 20:53:22 +00:00
}
2022-01-25 00:57:28 +00:00
signingSecret, _ := uuid.Must(uuid.NewRandom()).MarshalBinary()
srv.Auth = gpaste.NewAuthService(srv.Users, signingSecret)
2022-01-19 21:44:00 +00:00
2022-01-15 20:53:22 +00:00
r := chi.NewRouter()
2022-01-19 02:23:54 +00:00
r.Use(middleware.RealIP)
r.Use(middleware.RequestID)
r.Use(srv.MiddlewareAccessLogger)
2022-01-20 00:04:44 +00:00
r.Use(srv.MiddlewareAuthentication)
2022-01-15 20:53:22 +00:00
r.Get("/", srv.HandlerIndex)
r.Post("/api/file", srv.HandlerAPIFilePost)
r.Get("/api/file/{id}", srv.HandlerAPIFileGet)
2022-01-21 06:17:52 +00:00
r.Delete("/api/file/{id}", srv.HandlerAPIFileDelete)
2022-01-19 21:25:19 +00:00
r.Post("/api/login", srv.HandlerAPILogin)
2022-01-20 02:22:18 +00:00
r.Post("/api/user", srv.HandlerAPIUserCreate)
2022-01-15 20:53:22 +00:00
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) {
2022-01-19 02:23:54 +00:00
reqID := middleware.GetReqID(r.Context())
2022-01-15 20:53:22 +00:00
2022-01-16 20:29:42 +00:00
// Check if multipart form
ct := r.Header.Get("Content-Type")
if strings.Contains(ct, "multipart/form-data") {
s.processMultiPartFormUpload(w, r)
return
}
2022-01-24 15:29:34 +00:00
f := fileFromParams(r)
f.ID = uuid.NewString()
f.Body = r.Body
2022-01-19 21:25:19 +00:00
err := s.Files.Store(f)
2022-01-15 20:53:22 +00:00
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
2022-01-19 02:23:54 +00:00
s.Logger.Warnw("Error storing file.", "req_id", reqID, "error", err, "id", f.ID, "remote_addr", r.RemoteAddr)
2022-01-24 19:25:52 +00:00
2022-01-15 20:53:22 +00:00
return
}
2022-01-24 19:25:52 +00:00
2022-01-19 02:23:54 +00:00
s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "remote_addr", r.RemoteAddr)
2022-01-24 19:25:52 +00:00
2022-01-24 18:03:20 +00:00
fileURL := path.Join(s.config.URL, "/api/file", f.ID)
2022-01-24 18:15:43 +00:00
resp := &ResponseAPIFilePost{
2022-01-15 20:53:22 +00:00
Message: "OK",
2022-01-24 18:15:43 +00:00
Files: []ResponseAPIFilePostFiles{
2022-01-24 18:31:41 +00:00
{
2022-01-24 18:15:43 +00:00
ID: f.ID,
URL: fileURL,
},
},
2022-01-15 20:53:22 +00:00
}
2022-01-24 19:25:52 +00:00
2022-01-15 20:53:22 +00:00
w.WriteHeader(http.StatusAccepted)
2022-01-24 19:25:52 +00:00
2022-01-15 20:53:22 +00:00
encoder := json.NewEncoder(w)
if err := encoder.Encode(&resp); err != nil {
2022-01-19 02:23:54 +00:00
s.Logger.Warnw("Error encoding response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr)
2022-01-15 20:53:22 +00:00
}
}
func (s *HTTPServer) HandlerAPIFileGet(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if id == "" {
w.WriteHeader(http.StatusBadRequest)
return
}
2022-01-19 21:25:19 +00:00
f, err := s.Files.Get(id)
2022-01-15 20:53:22 +00:00
if err != nil {
// TODO: LOG
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
2022-01-24 19:25:52 +00:00
2022-01-15 20:53:22 +00:00
if _, err := io.Copy(w, f.Body); err != nil {
2022-01-19 02:23:54 +00:00
reqID := middleware.GetReqID(r.Context())
s.Logger.Warnw("Error writing file to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr)
2022-01-15 20:53:22 +00:00
}
}
2022-01-16 20:29:42 +00:00
2022-01-21 06:17:52 +00:00
func (s *HTTPServer) HandlerAPIFileDelete(w http.ResponseWriter, r *http.Request) {
// TODO: Require auth
id := chi.URLParam(r, "id")
if id == "" {
w.WriteHeader(http.StatusBadRequest)
return
}
2022-01-24 19:32:49 +00:00
if err := s.Files.Delete(id); err != nil {
2022-01-21 06:17:52 +00:00
w.WriteHeader(http.StatusBadRequest)
return
}
2022-01-24 19:25:52 +00:00
2022-01-21 06:17:52 +00:00
reqID := middleware.GetReqID(r.Context())
s.Logger.Infow("Deleted file", "id", id, "req_id", reqID)
}
2022-01-16 20:29:42 +00:00
func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.Request) {
2022-01-19 02:23:54 +00:00
reqID := middleware.GetReqID(r.Context())
2022-01-16 20:29:42 +00:00
2022-01-24 18:15:43 +00:00
var resp ResponseAPIFilePost
2022-01-16 20:29:42 +00:00
2022-01-24 19:25:52 +00:00
if err := r.ParseMultipartForm(multipartMaxMemory); err != nil {
2022-01-19 02:23:54 +00:00
s.Logger.Warnw("Error parsing multipart form.", "req_id", reqID, "err", err)
2022-01-16 20:29:42 +00:00
}
2022-01-24 19:25:52 +00:00
2022-01-16 20:29:42 +00:00
for k := range r.MultipartForm.File {
ff, fh, err := r.FormFile(k)
if err != nil {
2022-01-19 02:23:54 +00:00
s.Logger.Warnw("Error reading file from multipart form.", "req_id", reqID, "error", err)
2022-01-16 20:29:42 +00:00
return
}
2022-01-24 19:25:52 +00:00
2022-01-24 15:29:34 +00:00
f := fileFromParams(r)
f.ID = uuid.NewString()
f.OriginalFilename = fh.Filename
f.Body = ff
2022-01-16 20:29:42 +00:00
2022-01-19 21:25:19 +00:00
if err := s.Files.Store(f); err != nil {
2022-01-16 20:29:42 +00:00
w.WriteHeader(http.StatusInternalServerError)
2022-01-19 02:23:54 +00:00
s.Logger.Warnw("Error storing file.", "req_id", reqID, "error", err, "id", f.ID, "remote_addr", r.RemoteAddr)
2022-01-24 19:25:52 +00:00
2022-01-16 20:29:42 +00:00
return
}
2022-01-24 19:25:52 +00:00
2022-01-19 02:23:54 +00:00
s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "filename", f.OriginalFilename, "remote_addr", r.RemoteAddr)
2022-01-16 20:29:42 +00:00
2022-01-24 18:03:20 +00:00
fileURL := path.Join(s.config.URL, "/api/file", f.ID)
2022-01-24 18:15:43 +00:00
fileResponse := ResponseAPIFilePostFiles{ID: f.ID, URL: fileURL}
resp.Files = append(resp.Files, fileResponse)
2022-01-16 20:29:42 +00:00
}
w.WriteHeader(http.StatusAccepted)
encoder := json.NewEncoder(w)
2022-01-24 19:25:52 +00:00
2022-01-24 18:15:43 +00:00
if err := encoder.Encode(&resp); err != nil {
2022-01-19 02:23:54 +00:00
s.Logger.Warnw("Error encoding response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr)
2022-01-16 20:29:42 +00:00
}
}
2022-01-19 21:25:19 +00:00
func (s *HTTPServer) HandlerAPILogin(w http.ResponseWriter, r *http.Request) {
reqID := middleware.GetReqID(r.Context())
2022-01-24 19:25:52 +00:00
2022-01-20 16:50:56 +00:00
var expectedRequest RequestAPILogin
2022-01-24 19:25:52 +00:00
2022-01-19 21:25:19 +00:00
decoder := json.NewDecoder(r.Body)
defer r.Body.Close()
2022-01-24 19:25:52 +00:00
2022-01-19 21:25:19 +00:00
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
}
2022-01-20 16:50:56 +00:00
response := ResponseAPILogin{
2022-01-19 21:25:19 +00:00
Token: token,
}
2022-01-19 21:44:00 +00:00
s.Logger.Infow("User logged in.", "req_id", reqID, "username", expectedRequest.Username)
2022-01-19 21:25:19 +00:00
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)
}
}
2022-01-20 02:22:18 +00:00
func (s *HTTPServer) HandlerAPIUserCreate(w http.ResponseWriter, r *http.Request) {
reqID := middleware.GetReqID(r.Context())
defer r.Body.Close()
2022-01-20 16:50:56 +00:00
role, err := RoleFromRequest(r)
if err != nil || role != users.RoleAdmin {
2022-01-20 02:22:18 +00:00
w.WriteHeader(http.StatusUnauthorized)
return
}
var req RequestAPIUserCreate
2022-01-24 19:25:52 +00:00
2022-01-20 02:22:18 +00:00
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)
2022-01-24 19:25:52 +00:00
2022-01-20 02:22:18 +00:00
return
}
// TODO: Ensure user does not already exist
2022-01-22 09:19:18 +00:00
user := &users.User{Username: req.Username, Role: users.RoleUser}
2022-01-20 02:22:18 +00:00
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)
2022-01-24 19:25:52 +00:00
2022-01-20 02:22:18 +00:00
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)
2022-01-24 19:25:52 +00:00
2022-01-20 02:22:18 +00:00
return
}
2022-01-24 19:25:52 +00:00
2022-01-20 16:50:56 +00:00
w.WriteHeader(http.StatusAccepted)
2022-01-20 02:22:18 +00:00
s.Logger.Infow("Created user.", "req_id", reqID, "remote_addr", r.RemoteAddr, "username", req.Username)
}
2022-01-21 13:11:47 +00:00
func (s *HTTPServer) HandlerAPIUserList(w http.ResponseWriter, r *http.Request) {
reqID := middleware.GetReqID(r.Context())
l, err := s.Users.List()
if err != nil {
s.Logger.Warnw("Error listing users.", "req_id", reqID, "error", err)
w.WriteHeader(http.StatusInternalServerError)
2022-01-24 19:25:52 +00:00
2022-01-21 13:11:47 +00:00
return
}
encoder := json.NewEncoder(w)
if err := encoder.Encode(l); err != nil {
s.Logger.Warnw("Error encoding response.", "req_id", "error", err)
}
}
2022-01-24 15:29:34 +00:00
func fileFromParams(r *http.Request) *files.File {
const (
keyMaxViews = "max_views"
keyExpiresOn = "exp"
)
2022-01-24 19:25:52 +00:00
2022-01-24 15:29:34 +00:00
var f files.File
q := r.URL.Query()
if q.Has(keyMaxViews) {
2022-01-24 19:25:52 +00:00
views, err := strconv.ParseUint(q.Get(keyMaxViews), 10, 64) // nolint: gomnd
2022-01-24 15:29:34 +00:00
if err == nil {
f.MaxViews = uint(views)
}
}
if q.Has(keyExpiresOn) {
exp, err := time.Parse(time.RFC3339, q.Get(keyExpiresOn))
if err == nil {
f.ExpiresOn = exp
}
}
return &f
}