diff --git a/README.md b/README.md index 72c75ec..8f570d1 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,21 @@ curl http://localhost:8080/set/bucket001/foo # List all list curl http://localhost:8080/list/bucket001/key1?start=0&end=10 ``` + + +## Auth + +1. Enable Auth: +``` +nutshttp.EnableAuth = true +``` +2. Create Token: +```bash +curl http://127.0.0.1:8080/auth/thisisacert +``` +`thisisacert` replace with your pwd or username + +3. Use Token: + +- Add token to HEADERS: + > Authorization : Bearer `` diff --git a/auth.go b/auth.go new file mode 100644 index 0000000..4f9e77a --- /dev/null +++ b/auth.go @@ -0,0 +1,125 @@ +package nutshttp + +import ( + "errors" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt" +) + +type CheckFunc func(string) bool + +type Option struct { + ExpireDuration time.Duration + Issuer string + SigningMethod jwt.SigningMethod + checkFunc CheckFunc +} + +//var checkFunc CheckFunc +var option Option + +func defaultOptAndCheckFunc() Option { + return Option{ + ExpireDuration: time.Hour * 4, + Issuer: "nutsdb.nutsdb-http", + SigningMethod: jwt.SigningMethodHS256, + checkFunc: func(s string) bool { + return true + }, + } +} + +type TokenClaims struct { + Cert []byte `json:"cert"` + jwt.StandardClaims +} + +var secret []byte + +func GenerateToken(cert string) (string, error) { + claims := TokenClaims{ + []byte(cert), + jwt.StandardClaims{ + ExpiresAt: time.Now().Add(option.ExpireDuration).Unix(), + Issuer: option.Issuer, + }} + token := jwt.NewWithClaims(option.SigningMethod, claims) + signedToken, err := token.SignedString(secret) + if err != nil { + return "", err + } + return signedToken, nil +} + +func CheckToken(tokenStr string) (*TokenClaims, error) { + token, err := jwt.ParseWithClaims(tokenStr, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + return secret, nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(*TokenClaims); ok && token.Valid { + return claims, nil + } + return nil, errors.New("invalid token") +} + +func handler(c *gin.Context) { + if &option == nil { + panic(errors.New("invalid auth option")) + } + var cert string + cert = c.Param("cert") + b := option.checkFunc(cert) + if b { + token, err := GenerateToken(cert) + if err != nil { + WriteError(c, ErrInternalServerError) + return + } + WriteSucc(c, token) + return + } + WriteError(c, ErrRefuseIssueToken) +} + +func (s *NutsHTTPServer) DefaultInitAuth() { + option = defaultOptAndCheckFunc() + sr := s.r.Group("/auth") + sr.GET("/:cert", handler) + s.r.Use(JWTAuthMiddleware()) +} + +func SetSecret(s string) { + secret = []byte(s) +} + +const Bearer = "Bearer" + +func JWTAuthMiddleware() func(c *gin.Context) { + return func(c *gin.Context) { + authHeader := c.Request.Header.Get("Authorization") + if len(authHeader) == 0 { + WriteError(c, ErrAuthInvalid) + c.Abort() + return + } + parts := strings.SplitN(authHeader, " ", 2) + if !(len(parts) == 2 && parts[0] == Bearer) { + WriteError(c, ErrAuthInvalid) + c.Abort() + return + } + mc, err := CheckToken(parts[1]) + if err != nil { + WriteError(c, ErrAuthInvalid) + c.Abort() + return + } + c.Set("cert", mc.Cert) + c.Next() + } +} diff --git a/examples/hello.go b/examples/hello.go index 6e52042..4716a88 100644 --- a/examples/hello.go +++ b/examples/hello.go @@ -2,7 +2,6 @@ package main import ( "log" - "nutshttp" "github.com/xujiajun/nutsdb" @@ -16,7 +15,9 @@ func main() { log.Fatal(err) } defer db.Close() - + // Enable auth + nutshttp.EnableAuth = true + nutshttp.SetSecret("TbI7O6yEdEYa") go func() { if err := nutshttp.Enable(db); err != nil { panic(err) diff --git a/go.mod b/go.mod index 78ad918..554e014 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.17 require ( github.com/gin-gonic/gin v1.7.7 + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/xujiajun/nutsdb v0.8.0 ) diff --git a/go.sum b/go.sum index 45a5a82..de4886f 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= diff --git a/http.go b/http.go index 0bfad07..335af1d 100644 --- a/http.go +++ b/http.go @@ -4,6 +4,8 @@ import ( "github.com/xujiajun/nutsdb" ) +var EnableAuth bool + func Enable(db *nutsdb.DB) error { s := NewNutsHTTPServer(db) diff --git a/response.go b/response.go index 1866bc9..5cfd05e 100644 --- a/response.go +++ b/response.go @@ -14,9 +14,9 @@ type APIMessage struct { var ( APIOK = APIMessage{Code: 200, Message: "OK"} - ErrBadRequest = APIMessage{Code: 400, Message: "Bad Request"} - ErrNotFound = APIMessage{404, "Not Found"} ErrInternalServerError = APIMessage{500, "Internal Server Error"} + ErrRefuseIssueToken = APIMessage{40000, "Server refused to issue token"} + ErrAuthInvalid = APIMessage{40003, "Auth invalid"} ErrKeyNotFoundInBucket = APIMessage{40001, "Key Not Found In Bucket"} ErrPrefixScan = APIMessage{40002, "Prefix Scans Not Found"} ErrPrefixSearchScan = APIMessage{40003, "Prefix Search Scans Not Found"} diff --git a/server.go b/server.go index 9514b5e..ee45738 100644 --- a/server.go +++ b/server.go @@ -32,6 +32,9 @@ func (s *NutsHTTPServer) Run(addr string) error { } func (s *NutsHTTPServer) initRouter() { + if EnableAuth { + s.DefaultInitAuth() + } s.initSetRouter() s.initListRouter()