Last active
February 10, 2021 08:39
-
-
Save AaronGhent/af4db30dfc3ca8a97134e6c3698a6572 to your computer and use it in GitHub Desktop.
golang gin - mini rest api jwt + proxy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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