aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
authorMitchell Riedstra <mitch@riedstra.dev>2023-01-05 21:10:26 -0500
committerMitchell Riedstra <mitch@riedstra.dev>2023-01-05 21:10:26 -0500
commit1e1cd261dd9c944f595c9023c8aa3e76aae751af (patch)
tree7b18c557dc38cb0dcc99f15a489298c1bd54f40f /main.go
parentc71b37eb23d4c8af7ab983de34c6da5be9363f3a (diff)
downloadpaste-1e1cd261dd9c944f595c9023c8aa3e76aae751af.tar.gz
paste-1e1cd261dd9c944f595c9023c8aa3e76aae751af.tar.xz
Start on swagger docsswagger
Diffstat (limited to 'main.go')
-rw-r--r--main.go129
1 files changed, 126 insertions, 3 deletions
diff --git a/main.go b/main.go
index d61be41..1def8f8 100644
--- a/main.go
+++ b/main.go
@@ -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)