Skip to content

Instantly share code, notes, and snippets.

@serxoz
Forked from AaronGhent/gin-miniapi.go
Created February 10, 2021 08:39
Show Gist options
  • Save serxoz/b2a92afe1c097a76a48c0aed31c35fcc to your computer and use it in GitHub Desktop.
Save serxoz/b2a92afe1c097a76a48c0aed31c35fcc to your computer and use it in GitHub Desktop.
golang gin - mini rest api jwt + proxy
package main
## Install
# sudo apt install golang-go
# echo GOPATH=`pwd`
# export GOPATH=`pwd`
# go get -u -v github.com/appleboy/gin-jwt
# go get -u -v github.com/gin-contrib/cors
# go get -u -v github.com/gin-gonic/gin
# go get -u -v github.com/derekparker/delve/cmd/dlv
# go get -u -v github.com/aarzilli/gdlv
## Run
# go run src/main/main.go
# or
# bin/gdlv run src/main/main.go
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httputil"
"strings"
"time"
ginJwt "github.com/appleboy/gin-jwt"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/dgrijalva/jwt-go.v3"
"github.com/gin-gonic/gin"
"gopkg.in/go-playground/validator.v8"
)
const PROXY_EXTERNAL_API = "http://localhost:8008"
const JWT_KEY = "secure_as_hell_key"
func ValidationErrorToText(err *validator.FieldError) string {
var msg = fmt.Sprintf("%s is not valid", err.Field)
/*
type FieldError struct {
FieldNamespace string // TestJSON.Test.Id
NameNamespace string // Test.Id
Field string // Id
Name string // Id
Tag string // eq
ActualTag string // eq
Kind reflect.Kind //int
Type reflect.Type // int
Param string // 12
Value interface{} // 369
}
*/
switch err.Tag {
case "required":
msg = fmt.Sprintf("%s is required", err.Field)
case "max":
msg = fmt.Sprintf("%s cannot be longer than %s", err.Field, err.Param)
case "min":
msg = fmt.Sprintf("%s must be longer than %s", err.Field, err.Param)
case "email":
msg = fmt.Sprintf("Invalid email format")
case "len":
msg = fmt.Sprintf("%s must be %s characters long", err.Field, err.Param)
case "eq":
msg = fmt.Sprintf("%s must be equal to %s", err.Field, err.Param)
}
return msg
}
func ErrorHandler(ctx *gin.Context) {
if len(ctx.Errors) < 0 {
return
}
multiError := false
errors := make(map[string]string)
for _, fail := range ctx.Errors {
switch fail.Type {
case gin.ErrorTypePublic:
errors["error"] = fail.Error()
case gin.ErrorTypeBind:
switch etype := fail.Err.(type) {
case *json.UnmarshalTypeError:
msg := fmt.Sprintf("UnmarshalTypeError: %v.%v(%v) Value(%v) Offset(%v)", etype.Struct, etype.Field, etype.Type, etype.Value, etype.Offset)
errors["error"] = msg
case *json.InvalidUnmarshalError:
msg := fmt.Sprintf("InvalidUnmarshalError: Type(%v)", etype.Type)
errors["error"] = msg
case validator.ValidationErrors:
multiError = true
for _, fieldError := range fail.Err.(validator.ValidationErrors) {
errors[fieldError.NameNamespace] = ValidationErrorToText(fieldError)
}
default:
errors["error"] = fail.Err.Error()
}
default:
errors["error"] = fail.Error()
}
}
if multiError {
RenderErrorMultiple(errors, http.StatusBadRequest, ctx)
return
} else if len(errors) <= 1 {
for _, reason := range errors {
RenderErrorSingle(reason, http.StatusBadRequest, ctx)
return
}
}
RenderErrorSingle("Server Error: Something went wrong (╯°□°)╯︵ ┻━┻", http.StatusInternalServerError, ctx)
}
func RenderErrorSingle(message string, status int, ctx *gin.Context) {
meta := gin.H{
"status_code": status,
"error": true,
}
body := gin.H{
"data": nil,
"errors": message,
"meta": meta,
}
RenderResponse(&body, status, ctx)
}
func RenderErrorMultiple(errors interface{}, status int, ctx *gin.Context) {
meta := gin.H{
"status_code": status,
"error": true,
}
body := gin.H{
"data": nil,
"errors": errors,
"meta": meta,
}
RenderResponse(&body, status, ctx)
}
func RenderData(data interface{}, ctx *gin.Context) {
meta := gin.H{
"status_code": http.StatusOK,
"error": false,
}
body := gin.H{
"data": data,
"errors": false,
"meta": meta,
}
RenderResponse(&body, http.StatusOK, ctx)
}
func RenderResponse(body *gin.H, status int, ctx *gin.Context) {
ctx.IndentedJSON(status, body)
}
func LogIncoming(ctx *gin.Context) {
requestDump, err := httputil.DumpRequest(ctx.Request, true)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(requestDump))
}
/*
router.POST("/upload", func(c *gin.Context) {
// single file
file, _ := c.FormFile("file")
log.Println(file.Filename)
// Upload the file to specific dst.
// c.SaveUploadedFile(file, dst)
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
*/
func ExternalApiRequest() gin.HandlerFunc {
proxy := strings.Split(PROXY_EXTERNAL_API, "://")
return func(c *gin.Context) {
director := func(req *http.Request) {
r := c.Request
req = r
req.URL.Scheme = proxy[0]
req.URL.Host = proxy[1]
// for loop em from gin.Context
// req.Header["my-header"] = []string{r.Header.Get("my-header")}
}
proxy := &httputil.ReverseProxy{Director: director}
proxy.ServeHTTP(c.Writer, c.Request)
}
}
func test(ctx *gin.Context) {
LogIncoming(ctx)
var incoming TestRequestJSON
if err := ctx.BindJSON(&incoming); err != nil {
util.ErrorHandler(ctx)
return
}
util.RenderData(gin.H{
"incoming": incoming,
}, ctx)
}
func login(ctx *gin.Context) {
LogIncoming(ctx)
var loginVals Login
if ctx.ShouldBindWith(&loginVals, binding.JSON) != nil {
util.RenderErrorSingle("Missing Username or Password", http.StatusBadRequest, ctx)
return
}
if (loginVals.Username != "aaron") && (loginVals.Username != "test") {
util.RenderErrorSingle("Bad Username or Password", http.StatusUnauthorized, ctx)
return
}
// Create the token
token := jwt.New(jwt.GetSigningMethod("HS256"))
claims := token.Claims.(jwt.MapClaims)
/* // add extra things to the jwt claims
for key, value := range ginJwt.PayloadFunc(loginVals.Username) {
claims[key] = value
}*/
expire := time.Now().Add(time.Hour)
claims["id"] = loginVals.Username
claims["exp"] = expire.Unix()
claims["orig_iat"] = time.Now().Unix()
tokenString, err := token.SignedString([]byte(JWT_KEY))
if err != nil {
util.RenderErrorSingle("Create JWT Token faild", http.StatusUnauthorized, ctx)
return
}
util.RenderData(gin.H{
"login": loginVals.Username,
"exp": claims["exp"],
"iat": claims["orig_iat"],
"token": tokenString,
}, ctx)
}
func hello(ctx *gin.Context) {
util.RenderData(gin.H{"message": "hello world"}, ctx)
}
func main() {
router := gin.New()
router.Use(gin.Logger())
router.Use(gin.Recovery())
authMiddleware := &ginJwt.GinJWTMiddleware{
Realm: "thingy api",
Key: []byte(JWT_KEY),
Timeout: time.Hour,
MaxRefresh: time.Hour,
Authenticator: func(userId string, password string, c *gin.Context) (string, bool) {
if (userId == "aaron") || (userId == "test") {
return userId, true
}
return userId, false
},
Unauthorized: func(ctx *gin.Context, code int, message string) {
util.RenderErrorSingle(message, code, ctx)
},
TokenLookup: "header:Authorization",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
}
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "PUT", "POST", "DELETE"},
AllowHeaders: []string{"Origin", "Authorization", "Content-Type", "Content-Length", "Accept", "Accept-Encoding", "X-HttpRequest"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return true
},
MaxAge: 12 * time.Hour,
}))
router.NoRoute(func(ctx *gin.Context) {
util.RenderErrorSingle("Not Found", 404, ctx)
})
router.NoMethod(func(ctx *gin.Context) {
util.RenderErrorSingle("Methhod Not Allowed", 405, ctx)
})
router.POST("/v1/user/login", login)
auth := router.Group("/v1/")
auth.Use(authMiddleware.MiddlewareFunc())
{
auth.GET("hello", hello)
auth.GET("ext", ExternalApiRequest())
auth.GET("user/login_refresh", authMiddleware.RefreshHandler)
auth.POST("test", test)
}
http.ListenAndServe(":4239", router)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment