// ArchiveView shows a list of posts arranged by its date func ArchiveView(c *echo.Context) error { id := c.Param("id") var err error // Get archive data var archive models.Archive err = database.Current.Get(&archive, database.Queries.ArchiveBySignature, id) if err != nil { log.Println("Cannot get archive by signature", err) return echo.NewHTTPError(http.StatusInternalServerError) } // Get related posts var posts []models.AuthoredPost err = database.Current.Select(&posts, database.Queries.ArchiveAuthoredPosts, id) if err != nil { log.Println("Cannot select posts", err) return echo.NewHTTPError(http.StatusInternalServerError) } // Create our render context and fill base data ac := archiveContext{ Archive: archive, RelatedPosts: posts, } err = fillBlogContext(c, &ac.blogContext) if err != nil { log.Println("Cannot fill blog context", err) return echo.NewHTTPError(http.StatusInternalServerError) } return c.Render(200, "archive", ac) }
// putPlace изменяет определение уже существующего места. func putPlace(c *echo.Context) error { groupID := c.Get("GroupID").(string) placeID := c.Query("place-id") if !bson.IsObjectIdHex(placeID) { return echo.NewHTTPError(http.StatusNotFound) } var place places.Place // описание места err := c.Bind(&place) // разбираем описание места из запроса if err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if place.Circle == nil && place.Polygon == nil { return echo.NewHTTPError(http.StatusBadRequest) } place.ID = bson.ObjectIdHex(placeID) place.GroupID = groupID _, err = placesDB.Save(place) if err == mgo.ErrNotFound { return echo.NewHTTPError(http.StatusNotFound) } if err != nil { llog.Error("placesDB error: %v", err) return err } return c.NoContent(http.StatusOK) }
// auth является вспомогательной функцией, проверяющей и разбирающей токен с авторизационной // информацией в HTTP-заголовке. Разобранная информация сохраняется в контексте запроса. func auth(h echo.HandlerFunc) echo.HandlerFunc { return func(c *echo.Context) error { req := c.Request() // получаем доступ к HTTP-запросу if req.Header.Get(echo.Upgrade) == echo.WebSocket { // пропускаем запросы WebSocket return nil } data, err := tokenEngine.ParseRequest(req) // разбираем токен из запроса if err != nil { llog.Warn("Bad token: %v", err) return echo.NewHTTPError(http.StatusForbidden) } groupID := data["group"].(string) userID := data["id"].(string) if !bson.IsObjectIdHex(userID) { llog.Warn("Bad user Object ID: %v", userID) return echo.NewHTTPError(http.StatusForbidden) } userObjectId := bson.ObjectIdHex(userID) // проверяем, что пользователь есть и входит в эту группу exists, err := usersDB.Check(groupID, userObjectId) if err != nil { llog.Error("usersDB error: %v", err) return err } if !exists { llog.Debug("Auth not exist: %v (%v)", userID, groupID) return echo.NewHTTPError(http.StatusForbidden) } c.Set("GroupID", groupID) // сохраняем данные в контексте запроса c.Set("ID", userObjectId) llog.Debug("Auth: %v (%v)", userID, groupID) return h(c) // выполняем основной обработчик } }
func postDevicePairing(c *echo.Context) error { groupID := c.Get("GroupID").(string) deviceID := c.Param("device-id") _, _ = groupID, deviceID var pairingKey struct { Key string } err := c.Bind(&pairingKey) // читаем ключ из запроса if err != nil || len(pairingKey.Key) < 4 { return echo.NewHTTPError(http.StatusBadRequest) } var deviceIDResp string err = nce.Request(serviceNamePairingKey, pairingKey.Key, &deviceIDResp, natsRequestTimeout) if err != nil { llog.Error("NATS Pairing Key response error: %v", err) return err } if deviceIDResp == "" { return echo.NewHTTPError(http.StatusNotFound) } if deviceIDResp == deviceID { // TODO: реально связать в базе return echo.NewHTTPError(http.StatusOK) } if deviceID == "" { return c.JSON(http.StatusOK, map[string]string{"ID": deviceIDResp}) } return echo.NewHTTPError(http.StatusBadRequest) }
// Authentication middleware using Session Tokens, every request handled is checked // for the X-Session-Token Header, decodes the session and authenticates the user to // it's resources in the store. Returns [400] for a bad token, [401] for non valid // sessions, and proceeds with request otherwise. func Auth(app *App) echo.HandlerFunc { return func(c *echo.Context) error { //Check for session token header := c.Request().Header.Get("X-Session-Token") // If no token [401], should login if header == "" { return echo.NewHTTPError(http.StatusUnauthorized) } // Token cannot be decoded or mismatch token length [400] // Session Tokens are 32 bytes long (64 hexadecimal characters) token, err := hex.DecodeString(header) if err != nil || len(token) != 32 { return echo.NewHTTPError(http.StatusBadRequest) } // If Session Token is not found (maybe no longer valid due to logout) // client gets [401] should login again session := app.System.Get("session", c.Param("user")) if session == nil { return echo.NewHTTPError(http.StatusUnauthorized) } // Finally if tokens don't match [401] if string(token) != string(session) { return echo.NewHTTPError(http.StatusUnauthorized) } return nil } }
// ApiNextTaskHandler implements method for getting next task from the queue func (h *handler) ApiNextTaskHandler(c *echo.Context) error { enc := json.NewDecoder(c.Request().Body) var taskReq common.TaskRequest if err := enc.Decode(&taskReq); err != nil { c.Request().Body.Close() return echo.NewHTTPError(http.StatusBadRequest, "Wrong JSON. Expected gobench.common.TaskRequest") } c.Request().Body.Close() // checks existing test environment by authKey received by client ok, err := h.back.Model.TestEnvironment.Exist(taskReq.AuthKey) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError) } if !ok { return echo.NewHTTPError(http.StatusBadRequest, "Wrong authKey!") } // retrives single task taskRow, err := h.back.Model.Task.Next(taskReq.AuthKey) if err != nil { if err == model.ErrNotFound { return echo.NewHTTPError(http.StatusNoContent) } return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } task := common.TaskResponse{Id: taskRow.Id.Hex(), PackageName: taskRow.PackageName, Type: []string{"benchmark"}} return c.JSON(http.StatusOK, task) }
// CategoryView shows a list of posts arranged by its category func CategoryView(c *echo.Context) error { // Parse pagination attributes pn, ps := utils.GetPage(c) id, _ := strconv.Atoi(c.Param("id")) // Get category using id var category models.Category err := database.Current.Get(&category, database.Queries.CategoryByID, id) if err != nil { log.Println("Cannot select category:", err) return echo.NewHTTPError(http.StatusInternalServerError) } // Query the posts var posts []models.AuthoredPost err = database.Current.Select(&posts, database.Queries.AuthoredPostsByCategory, id, ps, ps*(pn-1)) if err != nil { log.Println("Cannot select posts:", err) return echo.NewHTTPError(http.StatusInternalServerError) } var count int err = database.Current.Get(&count, database.Queries.PostByCategoryCount, id) if err != nil { log.Println("Cannot count posts:", err) } pc := int(math.Floor(float64(count/ps))) + 1 // Generate preview for all posts for _, p := range posts { p.Preview = createPreview(p.TextContent) } // Create our context and fill base data cc := categoryContext{ Category: category, RelatedPosts: models.Paginated{ TotalObjects: count, PageCount: pc, PageNumber: pn, PageSize: ps, NextAvailable: pn < pc, Results: posts, }, } err = fillBlogContext(c, &cc.blogContext) if err != nil { log.Println("Cannot fill blog context", err) return echo.NewHTTPError(http.StatusInternalServerError) } // Render the results return c.Render(200, "category", cc) }
// Error forwards to echo.Error func (e *echoHandler) Error(c server.Context, code int, d ...interface{}) error { switch v := d[0].(type) { case error: return echo.NewHTTPError(code, v.Error()) case string: return echo.NewHTTPError(code, v) default: return echo.NewHTTPError(code) } }
func (s *FilesService) Upload(params url.Values) *Responder { var ( err error ) body := &bytes.Buffer{} writer := multipart.NewWriter(body) for k, values := range params { switch k { default: err = writer.WriteField(k, values[0]) if err != nil { return &Responder{ Error: echo.NewHTTPError(http.StatusBadRequest, err.Error()), } } case "file": for _, path := range values { file, err := os.Open(path) if err != nil { return &Responder{ Error: echo.NewHTTPError(http.StatusBadRequest, err.Error()), } } defer file.Close() part, err := writer.CreateFormFile("file", filepath.Base(path)) if err != nil { return &Responder{ Error: echo.NewHTTPError(http.StatusBadRequest, err.Error()), } } _, err = io.Copy(part, file) if err != nil { return &Responder{ Error: echo.NewHTTPError(http.StatusBadRequest, err.Error()), } } } } } defer writer.Close() return &Responder{ Body: body, Headers: map[string]string{ "Content-Type": writer.FormDataContentType(), }, Method: "MULTIPART", Params: params, Rpc: "upload", Service: s, } }
func HMACAuth(o *HmacOpts) echo.HandlerFunc { return func(c *echo.Context) error { auth, err := newAuth(c.Request(), o) if nil != err { return echo.NewHTTPError(http.StatusUnauthorized, err.Error()) } if err := auth.Authentic(c.Request()); nil != err { return echo.NewHTTPError(http.StatusUnauthorized, err.Error()) } c.Set(API_KEY, auth.ApiKey) return nil } }
// Login route (/login), credentials are given as Basic Authentication, so be sure to do it over SSL or private networking. // If authentication is successful, it returns [200] and a 32 byte X-Session-Token in a hex encoded string. // All authenticated requests should include the X-Session-Token header with this value, if the token is no longer // valid due to logout or expiration, a [401] usually means you have to call this login endpoint again and obtain a new token. func (app *App) Login(c *echo.Context) error { //Check for Basic Authentication Header user, pass, ok := c.Request().BasicAuth() if !ok { return echo.NewHTTPError(http.StatusBadRequest) } // If credentials don't match if !app.Authenticate(user, pass) { return echo.NewHTTPError(http.StatusUnauthorized) } // Return either a new Session Token or an existing one return c.String(http.StatusOK, app.NewSession(user)) }
func (s *yovpnServer) deleteEndpoint(c *echo.Context) error { id := c.Param("id") if len(id) == 0 { return echo.NewHTTPError(http.StatusBadRequest, "ID not provided!") } _, err := s.provisioner.DestroyEndpoint(id) if err != nil { return echo.NewHTTPError(http.StatusNotFound, "Endpoint not found!") } c.NoContent(http.StatusNoContent) return nil }
func (s *yovpnServer) getEndpoint(c *echo.Context) error { id := c.Param("id") if len(id) == 0 { return echo.NewHTTPError(http.StatusBadRequest, "ID not provided!") } endpoint, err := s.provisioner.GetEndpoint(id) if err != nil { return echo.NewHTTPError(http.StatusNotFound, "Endpoint not found!") } c.JSON(http.StatusOK, endpoint) return nil }
// ProfileView renders the profile data with the fancy portfolio template func ProfileView(c *echo.Context) error { id := c.Param("id") // ID in this case applies to username var err error var profile models.Profile err = database.Current.Get(&profile, database.Queries.ProfileByUsername, id) if err != nil { log.Println("Cannot get profile:", err) return echo.NewHTTPError(http.StatusInternalServerError) } var skills []models.ResumeSkill err = database.Current.Select(&skills, database.Queries.ResumeSkillsByProfileID, profile.ID) if err != nil { log.Println("Cannot get skills:", err) return echo.NewHTTPError(http.StatusInternalServerError) } var jobs []models.ResumeJob err = database.Current.Select(&jobs, database.Queries.ResumeJobsByProfileID, profile.ID) if err != nil { log.Println("Cannot select jobs:", err) return echo.NewHTTPError(http.StatusInternalServerError) } var education []models.ResumeEducation err = database.Current.Select(&education, database.Queries.ResumeEducationByProfileID, profile.ID) if err != nil { log.Println("Cannot select education:", err) return echo.NewHTTPError(http.StatusInternalServerError) } var projects []models.ResumeProject err = database.Current.Select(&projects, database.Queries.ResumeProjectsByProfileID, profile.ID) if err != nil { log.Println("Cannot select projects:", err) return echo.NewHTTPError(http.StatusInternalServerError) } pc := profileContext{ Profile: profile, Skills: skills, Jobs: jobs, Education: education, Projects: projects, } return c.Render(200, "profile", pc) }
// Index serves blog index view func Index(c *echo.Context) error { // Parse pagination attributes pn, ps := utils.GetPage(c) // Query the posts var posts []models.AuthoredPost err := database.Current.Select(&posts, database.Queries.AuthoredPosts, ps, ps*(pn-1)) if err != nil { log.Println("Cannot select posts:", err) return echo.NewHTTPError(http.StatusInternalServerError) } var count int err = database.Current.Get(&count, database.Queries.PostCount) if err != nil { log.Println("Cannot count posts:", err) return echo.NewHTTPError(http.StatusInternalServerError) } pc := int(math.Floor(float64(count/ps))) + 1 // Generate preview for all posts for _, p := range posts { p.Preview = createPreview(p.TextContent) } // Create our render context and fill base data ic := indexContext{ PostResults: models.Paginated{ TotalObjects: count, PageCount: pc, PageNumber: pn, PageSize: ps, NextAvailable: pn < pc, Results: posts, }, } err = fillBlogContext(c, &ic.blogContext) if err != nil { log.Println("Cannot fill blog context", err) return echo.NewHTTPError(http.StatusInternalServerError) } // Render the results return c.Render(200, "index", ic) }
// JobsNewPost process the new job form. // // Method POST // // Route /dash/jobs/new // // Restrictions Yes // // Template None func JobsNewPost(ctx *echo.Context) error { var flashMessages = flash.New() f := forms.New(utils.GetLang(ctx)) jf := f.JobForm()(ctx.Request()) if !jf.IsValid() { // TODO: improve flash message ? flashMessages.Err(msgInvalidorm) flashMessages.Save(ctx) ctx.Redirect(http.StatusFound, "/dash/jobs/new") return nil } if isLoged := ctx.Get("IsLoged"); isLoged != nil { person := ctx.Get("User").(*models.Person) if jerr := query.PersonCreateJob(person, jf.GetModel().(forms.JobForm)); jerr != nil { // TODO: improve flash message ? flashMessages.Err("some really bad fish happened") flashMessages.Save(ctx) ctx.Redirect(http.StatusFound, "/dash/jobs/new") return nil } // add flash message flashMessages.Success("new job was created successful") flashMessages.Save(ctx) ctx.Redirect(http.StatusFound, "/dash/") return nil } he := echo.NewHTTPError(http.StatusUnauthorized) ctx.Error(he) return nil }
func BasicAuth() echo.HandlerFunc { return func(c *echo.Context) error { // Skip WebSocket if (c.Request().Header.Get(echo.Upgrade)) == echo.WebSocket { return nil } auth := c.Request().Header.Get(echo.Authorization) l := len(Basic) if len(auth) > l+1 && auth[:l] == Basic { b, err := base64.StdEncoding.DecodeString(auth[l+1:]) if err == nil { cred := string(b) for i := 0; i < len(cred); i++ { if cred[i] == ':' { // Verify credentials for _, d := range conf.Domains { if cred[:i] == d.Name && bcrypt.CompareHashAndPassword([]byte(d.PassHash), []byte(cred[i+1:])) == nil { c.Set("domain", d) return nil } } } } } } c.Response().Header().Set(echo.WWWAuthenticate, Basic+" realm=Restricted") return echo.NewHTTPError(http.StatusUnauthorized) } }
// JWTAuth returns a JWT authentication middleware. // // For valid token it sets JWT claims in the context with key `_claims` and calls // the next handler. // For invalid Authorization header it sends "404 - Bad Request" response. // For invalid credentials, it sends "401 - Unauthorized" response. func JWTAuth(fn JWTValidateFunc) echo.HandlerFunc { return func(c *echo.Context) error { // Skip WebSocket if (c.Request().Header.Get(echo.Upgrade)) == echo.WebSocket { return nil } auth := c.Request().Header.Get("Authorization") l := len(Bearer) he := echo.NewHTTPError(http.StatusBadRequest) if len(auth) > l+1 && auth[:l] == Bearer { t, err := jwt.Parse(auth[l+1:], func(token *jwt.Token) (interface{}, error) { // Lookup key and verify method if kid := token.Header["kid"]; kid != nil { return fn(kid.(string), token.Method) } return fn("", token.Method) }) if err == nil && t.Valid { c.Set("_claims", t.Claims) return nil } else { he.SetCode(http.StatusUnauthorized) } } return he } }
func JWTAuth(key string) echo.HandlerFunc { return func(c *echo.Context) error { // Skip WebSocket if (c.Request().Header.Get(echo.Upgrade)) == echo.WebSocket { return nil } auth := c.Request().Header.Get("Authorization") l := len(Bearer) halt := echo.NewHTTPError(http.StatusUnauthorized) if len(auth) > l+1 && auth[:l] == Bearer { t, err := jwt.Parse(auth[l+1:], func(token *jwt.Token) (interface{}, error) { // Always check the signing method if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } // Return the key for validation return []byte(key), nil }) if err == nil && t.Valid { // Store token claims in echo.Context c.Set("claims", t.Claims) return nil } } return halt } }
// deletePlace удаляет определение места. func deletePlace(c *echo.Context) error { groupID := c.Get("GroupID").(string) placeID := c.Query("place-id") if !bson.IsObjectIdHex(placeID) { return echo.NewHTTPError(http.StatusNotFound) } err := placesDB.Delete(groupID, bson.ObjectIdHex(placeID)) if err == mgo.ErrNotFound { return echo.NewHTTPError(http.StatusNotFound) } if err != nil { llog.Error("placesDB error: %v", err) return err } return c.NoContent(http.StatusOK) }
func JWTAuth(key string) echo.HandlerFunc { return func(c *echo.Context) error { // If this is a WS upgrade request, skip Auth if (c.Request().Header.Get(echo.Upgrade)) == echo.WebSocket { return nil } auth := c.Request().Header.Get("Authorization") l := len(Bearer) unauthorized := echo.NewHTTPError(http.StatusUnauthorized) if len(auth) > l+1 && auth[:l] == Bearer { t, err := jwt.Parse(auth[l+1:], func(token *jwt.Token) (interface{}, error) { // Check the signing method // https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte(key), nil }) if err == nil && t.Valid { // Store claims in echo.Context c.Set("claims", t.Claims) return nil } } return unauthorized } }
// BasicAuth returns an HTTP basic authentication middleware. // // For valid credentials it calls the next handler. // For invalid Authorization header it sends "404 - Bad Request" response. // For invalid credentials, it sends "401 - Unauthorized" response. func BasicAuth(fn BasicValidateFunc) echo.HandlerFunc { return func(c *echo.Context) error { // Skip WebSocket if (c.Request().Header.Get(echo.Upgrade)) == echo.WebSocket { return nil } auth := c.Request().Header.Get(echo.Authorization) l := len(Basic) he := echo.NewHTTPError(http.StatusBadRequest) if len(auth) > l+1 && auth[:l] == Basic { b, err := base64.StdEncoding.DecodeString(auth[l+1:]) if err == nil { cred := string(b) for i := 0; i < len(cred); i++ { if cred[i] == ':' { // Verify credentials if fn(cred[:i], cred[i+1:]) { return nil } he.SetCode(http.StatusUnauthorized) } } } } return he } }
// Must ensures that any route is authorized to access the next handler // otherwise an error is returned. // // TODO custom not authorized handler? func Must() echo.HandlerFunc { return func(ctx *echo.Context) error { // If this is called somewhere on the middleware chain, if we find the user // we check the context if is set. if v := ctx.Get("IsLoged"); v != nil && v.(bool) == true { return nil } ss, err := store.Get(ctx.Request(), settings.App.Session.Name) if err != nil { // TODO: log this? } if v, ok := ss.Values["userID"]; ok { person, err := query.GetPersonByUserID(v.(int)) if err != nil { // TODO: log this? } if person != nil { // set in main context ctx.Set("IsLoged", true) ctx.Set("User", person) // for templates utils.SetData(ctx, "IsLoged", true) utils.SetData(ctx, "User", person) return nil } } return echo.NewHTTPError(http.StatusUnauthorized) } }
func main() { demoData := Data{ Id: 5, Name: "User name", Tags: []string{"people", "customer", "developer"}, } e := echo.New() e.SetDebug(true) e.Use(middleware.Logger()) e.Use(middleware.Recover()) s := stats.New() e.Use(standard.WrapMiddleware(s.Handler)) e.GET("/xml", func(c echo.Context) error { return c.XML(200, demoData) }) e.GET("/json", func(c echo.Context) error { return c.JSON(200, demoData) }) e.GET("/error", func(c echo.Context) error { return echo.NewHTTPError(500, "Error here") }) e.Run(standard.New(":8888")) }
// Logout route (/logout), destroys the X-Session-Token for a given user making it no longer valid. // To logout the route expects the user credentials as Basic Authentication header to destroy the session. // All subsecuent requests using the previous X-Session-Token will receive [401] and needs to login again. func (app *App) Logout(c *echo.Context) error { //Check for Basic Authentication Header user, pass, ok := c.Request().BasicAuth() if !ok { return echo.NewHTTPError(http.StatusBadRequest) } // If credentials don't match if !app.Authenticate(user, pass) { return echo.NewHTTPError(http.StatusUnauthorized) } // Destroy session err := app.DestroySession(user) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError) } return c.String(http.StatusOK, "") }
// JWTFromHeader is a `JWTExtractor` that extracts token from the `Authorization` request // header. func JWTFromHeader(c echo.Context) (string, error) { auth := c.Request().Header().Get(echo.HeaderAuthorization) l := len(bearer) if len(auth) > l+1 && auth[:l] == bearer { return auth[l+1:], nil } return "", echo.NewHTTPError(http.StatusBadRequest, "empty or invalid authorization header="+auth) }
func getPlatformName(c *echo.Context) (string, error) { if value, ok := c.Get("platformName").(string); ok { return value, nil } else { return "", echo.NewHTTPError(http.StatusExpectationFailed, "Unable to identify platform.") } }
// JWTWithConfig returns a JWT auth middleware with config. // See: `JWT()`. func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { // Defaults if config.Skipper == nil { config.Skipper = DefaultJWTConfig.Skipper } if config.SigningKey == nil { panic("jwt middleware requires signing key") } if config.SigningMethod == "" { config.SigningMethod = DefaultJWTConfig.SigningMethod } if config.ContextKey == "" { config.ContextKey = DefaultJWTConfig.ContextKey } if config.Claims == nil { config.Claims = jwt.MapClaims{} } if config.TokenLookup == "" { config.TokenLookup = DefaultJWTConfig.TokenLookup } // Initialize parts := strings.Split(config.TokenLookup, ":") extractor := jwtFromHeader(parts[1]) switch parts[0] { case "query": extractor = jwtFromQuery(parts[1]) case "cookie": extractor = jwtFromCookie(parts[1]) } return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if config.Skipper(c) { return next(c) } auth, err := extractor(c) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } token, err := jwt.ParseWithClaims(auth, config.Claims, func(t *jwt.Token) (interface{}, error) { // Check the signing method if t.Method.Alg() != config.SigningMethod { return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"]) } return config.SigningKey, nil }) if err == nil && token.Valid { // Store user information from token into context. c.Set(config.ContextKey, token) return next(c) } return echo.ErrUnauthorized } } }
func init() { Errors[sql.ErrNoRows] = func(err error, c *echo.Context) { if c.Request().Header.Get("Content-Type") == imt.Application.JSON { JSONErrorHandler(echo.NewHTTPError(404, "Record not found"), c) } else { c.Render(404, "404", err) } } }
func assertContentTypeJSON(r *http.Request) *echo.HTTPError { ct := r.Header.Get(echo.ContentType) if !strings.HasPrefix(ct, echo.ApplicationJSON) { return echo.NewHTTPError(http.StatusBadRequest, "request: allowed Content-Type is 'application/json' only") } return nil }