diff options
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 129 |
1 files changed, 126 insertions, 3 deletions
@@ -25,6 +25,9 @@ import ( jwt "github.com/golang-jwt/jwt/v4" "golang.org/x/crypto/bcrypt" "golang.org/x/term" + + httpSwagger "github.com/swaggo/http-swagger" + _ "riedstra.dev/mitch/paste/docs" ) // Default to the system umask @@ -69,6 +72,20 @@ type Response struct { Data any `json:",omitempty"` } +// @title Simple Pastebin API +// @version 1.0 +// @description Simple pastebin backed by your filesystem +// @contact.name Mitchell Riedstra +// @contact.url https://riedstra.dev +// @contact.email mitch@riedstra.dev +// @license.name ISC +// @BasePath /api +// +// @securityDefinitions.basic BasicAuth +// @securitydefinitions.apikey +// @in header +// @name Authorization +// @tokenURL /v1/login func main() { var ( listen = ":6130" @@ -297,9 +314,14 @@ func (a *App) Handler() http.Handler { "/api/v0/view/", a.HandleViewPlain()), + "/api/v1/getToken": tokenHandler(a.users, a.jwtKey, a.sessionHours), "/api/v1/login": loginHandler(a.users, a.jwtKey, a.sessionHours), "/api/v1/logout": logoutHandler(), + "/swagger/": httpSwagger.Handler( + httpSwagger.URL("/swagger/doc.json"), + ), + "/_app/": http.FileServer(http.FS(a.static)), "/": a.HandleIndex(a.static), } @@ -392,8 +414,8 @@ func genTokenKey() string { // encoding the response status code in the header, JSON, and a friendly // message as well as any other exported members of the Response struct. func sendJSON(er Response) { - er.w.WriteHeader(er.Code) er.w.Header().Add("Content-type", "application/json") + er.w.WriteHeader(er.Code) enc := json.NewEncoder(er.w) enc.SetIndent("", " ") _ = enc.Encode(er) @@ -403,8 +425,8 @@ func sendJSON(er Response) { // output verbatim unless the rdr is not nil, in which case the rdr // is used instead func sendPlain(r Response, rdr io.Reader) { + r.w.Header().Add("Content-type", "text/plain") r.w.WriteHeader(r.Code) - r.w.Header().Add("Content-type", "application/json") if rdr != nil { _, _ = io.Copy(r.w, rdr) } else { @@ -532,6 +554,66 @@ func (users UsersMap) AuthHandler(next http.Handler, jwtKey string, }) } +// Credentials +// +// @Description User's credentials +type Credentials struct { + Username string `json:"Username"` + Password string `json:"Password"` +} + +// tokenHandler +// +// @Summary Get an API key with valid credentials +// @Description Returns an API key that's valid for a pre-determined amount of hours +// @Tags v1 +// @Produce json +// @Accept json +// @Param request body Credentials true "User Credentials" +// @Router /v1/getToken [post] +func tokenHandler(users UsersMap, jwtKey string, sessionHours int, +) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + sendJSON(Response{w, http.StatusBadRequest, + "Invalid type. POST only", nil}) + return + } + + dec := json.NewDecoder(r.Body) + in := &Credentials{} + + err := dec.Decode(in) + if err != nil { + sendJSON(Response{w, http.StatusBadRequest, "Invalid request", nil}) + return + } + + if !users.IsValidLogin(in.Username, in.Password) { + sendJSON(Response{w, http.StatusUnauthorized, + "Invalid username or password", nil}) + return + } + expires := time.Now().Add(time.Hour * time.Duration(sessionHours)) + + claims := &jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expires), + ID: in.Username, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims) + ss, err := token.SignedString([]byte(jwtKey)) + if err != nil { + sendJSON(Response{w, http.StatusInternalServerError, + "Invalid username or password", nil}) + return + } + + sendJSON(Response{w, http.StatusOK, "Ok", + struct{ Token string } { ss }}) + }) +} + func loginHandler(users UsersMap, jwtKey string, sessionHours int, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -665,6 +747,14 @@ func getPastePathFromRawURL(storage, u string) string { filepath.Clean(u), "/", "")) } +// HandleViewJSON +// +// @Summary View a paste for a given ID +// @Description Fetches the contents of a paste if given an ID +// @Tags v1 +// @Produce json +// @Param id path string true "Paste ID" +// @Router /v1/view/{id} [get] func (a *App) HandleViewJSON() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pth := getPastePathFromRawURL(a.storage, r.URL.Path) @@ -688,6 +778,14 @@ func (a *App) HandleViewJSON() http.Handler { }) } +// HandleViewPlain +// +// @Summary View a paste for a given ID +// @Description Fetches the contents of a paste if given an ID +// @Tags v0 +// @Produce text/plain +// @Param id path string true "Paste ID" +// @Router /v0/view/{id} [get] func (a *App) HandleViewPlain() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pth := getPastePathFromRawURL(a.storage, r.URL.Path) @@ -703,6 +801,16 @@ func (a *App) HandleViewPlain() http.Handler { }) } +// HandleDel +// +// @Summary Deletes a paste for a given ID +// @Description Remove a paste from the filesystem +// @Tags v0 +// @Produce text/plain +// @Param id path string true "Paste ID" +// @Router /v0/del/{id} [delete] +// @securityDefinitions.basic BasicAuth +// @securityDefinitions.apikey ApiKeyAuth func (a *App) HandleDel() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pth := getPastePathFromRawURL(a.storage, r.URL.Path) @@ -724,9 +832,24 @@ func (a *App) HandleDel() http.Handler { }) } +// HandleDelJSON +// +// @Summary Deletes a paste for a given ID +// @Description Remove a paste from the filesystem +// @Tags v1 +// @Produce application/json +// @Param id path string true "Paste ID" +// @Router /v1/del/{id} [delete] +// @securityDefinitions.basic BasicAuth +// @name Authorization func (a *App) HandleDelJSON() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - logger.Println("Made it to delete...") + if r.Method != "DELETE" { + sendJSON(Response{w, http.StatusMethodNotAllowed, + "Method not allowed", nil}) + return + } + pth := getPastePathFromRawURL(a.storage, r.URL.Path) logger.Println("Deleting path: ", pth) err := os.Remove(pth) |
