Skip to content

Commit

Permalink
Merge pull request #279 from linkernetworks/johnlin/verify-jwt
Browse files Browse the repository at this point in the history
[Task] add verify jwt handler
  • Loading branch information
John-Lin authored Aug 27, 2018
2 parents cf17bd6 + 35e9d00 commit 9a87329
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 39 deletions.
38 changes: 36 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- [Table of Contents](#table-of-contents)
- [User](#user)
- [Signup](#signup)
- [Verify Token](#verify-token)
- [Signin](#signin)
- [Create User](#create-user)
- [List User](#list-user)
Expand Down Expand Up @@ -100,6 +101,22 @@ Response Data:
}
```

### Verify Token

**GET /v1/user/verify/auth**

with a authorization JWT key

```
Authorization: Bearer <MY_TOKEN>
```

Response status code:

when jwt is valid return status code 303 redirect to /v1/user/<id>
when jwt is invalid return status code 401


### Signin

**POST /v1/users/signin**
Expand All @@ -125,7 +142,7 @@ Response Data:

### Create User

**POST /v1/user**
**POST /v1/users**

Example:

Expand Down Expand Up @@ -219,8 +236,25 @@ Response Data:

### Get User

TODO
**GET /v1/users/5b5b418c760aab15e771bde2**

Response

```json
{
"id": "5b5b418c760aab15e771bde2",
"loginCredential": {
"username": "[email protected]",
"password": "$2a$14$XO4OOUCaiTNQHm.ZTzHU5..WwtP2ec2Q2HPPQuMHP1WoXCjXiRrxa"
},
"displayName": "John Doe",
"role": "guest",
"firstName": "John",
"lastName": "Doe",
"phoneNumber": "0911111111",
"createdAt": "2018-07-28T00:00:12.632011379+08:00"
}
```

### Delete User

Expand Down
25 changes: 25 additions & 0 deletions src/server/backend/jwt.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package backend

import (
"regexp"
"time"

"github.com/dgrijalva/jwt-go"
"github.com/linkernetworks/logger"
)

// GenerateToken is for generating token
Expand All @@ -21,3 +23,26 @@ func GenerateToken(userID string, role string) (string, error) {
}
return token.SignedString([]byte(SecretKey))
}

// VerifyToken is for verifing the JWT
func VerifyToken(tokenData []byte) bool {
// trim possible whitespace from token
tokenData = regexp.MustCompile(`\s*$`).ReplaceAll(tokenData, []byte{})

// Parse the token
token, err := jwt.Parse(string(tokenData), func(t *jwt.Token) (interface{}, error) {
return []byte(SecretKey), nil
})
// Print an error if we can't parse for some reason
if err != nil {
logger.Infof("Couldn't parse token: %v", err)
return false
}

// Is token invalid?
if !token.Valid {
logger.Infof("Token is invalid")
return false
}
return true
}
20 changes: 20 additions & 0 deletions src/server/backend/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,23 @@ func TestGenerateToken(t *testing.T) {
assert.NotNil(t, tokenString)
assert.NoError(t, err)
}

func TestVerifyToken(t *testing.T) {
roles := "admin"
tokenString, err := GenerateToken("234243353535330", roles)
assert.NotNil(t, tokenString)
assert.NoError(t, err)

isValid := VerifyToken([]byte(tokenString))
assert.True(t, isValid)
}

func TestInValidVerifyToken(t *testing.T) {
tokenString := "fakeToken"
isValid := VerifyToken([]byte(tokenString))
assert.False(t, isValid)

tokenString2 := "eyJhbGciOiJIUzI1NiIaInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
isValid2 := VerifyToken([]byte(tokenString2))
assert.False(t, isValid2)
}
15 changes: 10 additions & 5 deletions src/server/handler_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,19 @@ func (suite *NetworkTestSuite) SetupSuite() {
sp := serviceprovider.NewForTesting(cf)

suite.sp = sp
//init session
// init session
suite.session = sp.Mongo.NewSession()
//init restful container
// init restful container
suite.wc = restful.NewContainer()
service := newNetworkService(sp)
suite.wc.Add(service)

token, _ := loginGetToken(suite.sp, suite.wc)
networkService := newNetworkService(suite.sp)
userService := newUserService(suite.sp)

suite.wc.Add(networkService)
suite.wc.Add(userService)

token, _ := loginGetToken(suite.wc)
suite.NotEmpty(token)
suite.JWTBearer = "Bearer " + token
}

Expand Down
15 changes: 10 additions & 5 deletions src/server/handler_pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@ func (suite *PodTestSuite) SetupSuite() {
sp := serviceprovider.NewForTesting(cf)

suite.sp = sp
//init session
// init session
suite.session = sp.Mongo.NewSession()
//init restful container
// init restful container
suite.wc = restful.NewContainer()
service := newPodService(suite.sp)
suite.wc.Add(service)

token, _ := loginGetToken(suite.sp, suite.wc)
podService := newPodService(suite.sp)
userService := newUserService(suite.sp)

suite.wc.Add(podService)
suite.wc.Add(userService)

token, _ := loginGetToken(suite.wc)
suite.NotEmpty(token)
suite.JWTBearer = "Bearer " + token
}

Expand Down
15 changes: 10 additions & 5 deletions src/server/handler_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@ func (suite *ServiceTestSuite) SetupSuite() {
sp := serviceprovider.NewForTesting(cf)

suite.sp = sp
//init session
// init session
suite.session = sp.Mongo.NewSession()
//init restful container
// init restful container
suite.wc = restful.NewContainer()
service := newServiceService(suite.sp)
suite.wc.Add(service)

token, _ := loginGetToken(suite.sp, suite.wc)
serviceService := newServiceService(suite.sp)
userService := newUserService(suite.sp)

suite.wc.Add(serviceService)
suite.wc.Add(userService)

token, _ := loginGetToken(suite.wc)
suite.NotEmpty(token)
suite.JWTBearer = "Bearer " + token
}

Expand Down
10 changes: 10 additions & 0 deletions src/server/handler_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ func signUpUserHandler(ctx *web.Context) {
resp.WriteHeaderAndEntity(http.StatusCreated, user)
}

func verifyTokenHandler(ctx *web.Context) {
_, req, resp := ctx.ServiceProvider, ctx.Request, ctx.Response
mgoID, ok := req.Attribute("UserID").(string)
if !ok {
response.Unauthorized(req.Request, resp.ResponseWriter, fmt.Errorf("Unauthorized: User ID is empty"))
return
}
http.Redirect(resp.ResponseWriter, req.Request, "/v1/users/"+mgoID, 303)
}

func signInUserHandler(ctx *web.Context) {
sp, req, resp := ctx.ServiceProvider, ctx.Request, ctx.Response

Expand Down
45 changes: 38 additions & 7 deletions src/server/handler_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,29 @@ func init() {

type UserTestSuite struct {
suite.Suite
sp *serviceprovider.Container
wc *restful.Container
session *mongo.Session
sp *serviceprovider.Container
wc *restful.Container
session *mongo.Session
JWTBearer string
}

func (suite *UserTestSuite) SetupSuite() {
cf := config.MustRead("../../config/testing.json")
sp := serviceprovider.NewForTesting(cf)

suite.sp = sp
//init session
// init session
suite.session = sp.Mongo.NewSession()
//init restful container
// init restful container
suite.wc = restful.NewContainer()
user := newUserService(suite.sp)
suite.wc.Add(user)

userService := newUserService(suite.sp)

suite.wc.Add(userService)

token, _ := loginGetToken(suite.wc)
suite.NotEmpty(token)
suite.JWTBearer = "Bearer " + token
}

func (suite *UserTestSuite) TearDownSuite() {}
Expand Down Expand Up @@ -86,6 +93,28 @@ func (suite *UserTestSuite) TestSignUpUser() {
suite.Equal("user", retUser.Role)
}

func (suite *UserTestSuite) TestVerifyToken() {
httpRequest, err := http.NewRequest("GET", "http://localhost:7890/v1/users/verify/auth", nil)
suite.NoError(err)

httpRequest.Header.Add("Content-Type", "application/json")
httpRequest.Header.Add("Authorization", suite.JWTBearer)
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusSeeOther, httpWriter)
}

func (suite *UserTestSuite) TestVerifyInvalidToken() {
httpRequest, err := http.NewRequest("GET", "http://localhost:7890/v1/users/verify/auth", nil)
suite.NoError(err)

httpRequest.Header.Add("Content-Type", "application/json")
httpRequest.Header.Add("Authorization", "InValidToken")
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusUnauthorized, httpWriter)
}

func (suite *UserTestSuite) TestSignUpFailedUser() {
sameUsername := namesgenerator.GetRandomName(0) + "@linkernetworks.com"
// given a user already in mongodb
Expand Down Expand Up @@ -333,6 +362,7 @@ func (suite *UserTestSuite) TestGetUser() {
httpRequest, err := http.NewRequest("GET", "http://localhost:7890/v1/users/"+user.ID.Hex(), nil)
suite.NoError(err)

httpRequest.Header.Add("Authorization", suite.JWTBearer)
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusOK, httpWriter)
Expand All @@ -349,6 +379,7 @@ func (suite *UserTestSuite) TestGetUserWithInvalidID() {
httpRequest, err := http.NewRequest("GET", "http://localhost:7890/v1/users/"+bson.NewObjectId().Hex(), nil)
suite.NoError(err)

httpRequest.Header.Add("Authorization", suite.JWTBearer)
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusNotFound, httpWriter)
Expand Down
16 changes: 10 additions & 6 deletions src/server/handler_volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,21 @@ func (suite *VolumeTestSuite) SetupSuite() {
sp := serviceprovider.NewForTesting(cf)

suite.sp = sp
//init session
// init session
suite.session = sp.Mongo.NewSession()
//init restful container
// init restful container
suite.wc = restful.NewContainer()
service := newVolumeService(suite.sp)
suite.wc.Add(service)
volumeService := newVolumeService(suite.sp)
userService := newUserService(suite.sp)

token, _ := loginGetToken(suite.sp, suite.wc)
suite.wc.Add(userService)
suite.wc.Add(volumeService)

token, _ := loginGetToken(suite.wc)
suite.NotEmpty(token)
suite.JWTBearer = "Bearer " + token

//init a Storage
// init a Storage
suite.storage = entity.Storage{
ID: bson.NewObjectId(),
Type: "nfs",
Expand Down
13 changes: 9 additions & 4 deletions src/server/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,18 @@ func newRegistryService(sp *serviceprovider.Container) *restful.WebService {
func newUserService(sp *serviceprovider.Container) *restful.WebService {
webService := new(restful.WebService)
webService.Path("/v1/users").Consumes(restful.MIME_JSON, restful.MIME_JSON).Produces(restful.MIME_JSON, restful.MIME_JSON)
webService.Route(webService.POST("/").To(handler.RESTfulServiceHandler(sp, createUserHandler)))
webService.Route(webService.DELETE("/{id}").To(handler.RESTfulServiceHandler(sp, deleteUserHandler)))
webService.Route(webService.GET("/").To(handler.RESTfulServiceHandler(sp, listUserHandler)))
webService.Route(webService.GET("/{id}").To(handler.RESTfulServiceHandler(sp, getUserHandler)))
// Authenticate handlers Sign Up / Sign In
webService.Route(webService.POST("/signup").To(handler.RESTfulServiceHandler(sp, signUpUserHandler)))
webService.Route(webService.POST("/signin").To(handler.RESTfulServiceHandler(sp, signInUserHandler)))

// TODO only root role can access
webService.Route(webService.GET("/").To(handler.RESTfulServiceHandler(sp, listUserHandler)))
webService.Route(webService.POST("/").To(handler.RESTfulServiceHandler(sp, createUserHandler)))
webService.Route(webService.DELETE("/{id}").To(handler.RESTfulServiceHandler(sp, deleteUserHandler)))

// user role can access
webService.Route(webService.GET("/{id}").Filter(validateTokenMiddleware).To(handler.RESTfulServiceHandler(sp, getUserHandler)))
webService.Route(webService.GET("/verify/auth").Filter(validateTokenMiddleware).To(handler.RESTfulServiceHandler(sp, verifyTokenHandler)))
return webService
}

Expand Down
6 changes: 1 addition & 5 deletions src/server/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@ import (
restful "github.com/emicklei/go-restful"
"github.com/linkernetworks/vortex/src/entity"
response "github.com/linkernetworks/vortex/src/net/http"
"github.com/linkernetworks/vortex/src/serviceprovider"
)

func loginGetToken(sp *serviceprovider.Container, wc *restful.Container) (string, error) {
service := newUserService(sp)
wc.Add(service)

func loginGetToken(wc *restful.Container) (string, error) {
var resp response.ActionResponse
userCred := entity.LoginCredential{
Username: "[email protected]",
Expand Down

0 comments on commit 9a87329

Please sign in to comment.