From 06938a9f2b3e8e006b603a07dbc5ebd395fd7d87 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Tue, 26 May 2015 13:26:36 -0800 Subject: [PATCH] Update godeps --- Godeps/Godeps.json | 4 +- .../src/github.com/gorilla/mux/doc.go | 10 +- .../src/github.com/gorilla/mux/mux_test.go | 36 +++++ .../src/github.com/thermokarst/jwt/README.md | 141 +++++++++++------- .../thermokarst/jwt/examples/net-http.go | 60 +++++--- .../src/github.com/thermokarst/jwt/jwt.go | 71 ++++++--- .../github.com/thermokarst/jwt/jwt_test.go | 19 ++- 7 files changed, 238 insertions(+), 103 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 35cd0e0..7eb8f29 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -17,7 +17,7 @@ }, { "ImportPath": "github.com/gorilla/mux", - "Rev": "8a875a034c69b940914d83ea03d3f1299b4d094b" + "Rev": "94903de8c98a68d8b4483c529b26a5d146e386a2" }, { "ImportPath": "github.com/gorilla/schema", @@ -39,7 +39,7 @@ }, { "ImportPath": "github.com/thermokarst/jwt", - "Rev": "774185ba9ebde2a02689173b736b760a13a6b777" + "Rev": "66ca404d841ed908aa6cc9361107b0c363c94cd8" }, { "ImportPath": "golang.org/x/crypto/bcrypt", diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/doc.go b/Godeps/_workspace/src/github.com/gorilla/mux/doc.go index b2deed3..9a5e381 100644 --- a/Godeps/_workspace/src/github.com/gorilla/mux/doc.go +++ b/Godeps/_workspace/src/github.com/gorilla/mux/doc.go @@ -89,7 +89,7 @@ There are several other matchers that can be added. To match path prefixes: r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { return r.ProtoMajor == 0 - }) + }) ...and finally, it is possible to combine several matchers in a single route: @@ -164,8 +164,8 @@ This also works for host variables: // url.String() will be "http://news.domain.com/articles/technology/42" url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") + "category", "technology", + "id", "42") All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a @@ -193,7 +193,7 @@ as well: // "http://news.domain.com/articles/technology/42" url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") + "category", "technology", + "id", "42") */ package mux diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go b/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go index c37be8a..6b2c1d2 100644 --- a/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go +++ b/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go @@ -135,6 +135,42 @@ func TestHost(t *testing.T) { path: "", shouldMatch: false, }, + { + title: "Path route with single pattern with pipe, match", + route: new(Route).Path("/{category:a|b/c}"), + request: newRequest("GET", "http://localhost/a"), + vars: map[string]string{"category": "a"}, + host: "", + path: "/a", + shouldMatch: true, + }, + { + title: "Path route with single pattern with pipe, match", + route: new(Route).Path("/{category:a|b/c}"), + request: newRequest("GET", "http://localhost/b/c"), + vars: map[string]string{"category": "b/c"}, + host: "", + path: "/b/c", + shouldMatch: true, + }, + { + title: "Path route with multiple patterns with pipe, match", + route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"), + request: newRequest("GET", "http://localhost/a/product_name/1"), + vars: map[string]string{"category": "a", "product": "product_name", "id": "1"}, + host: "", + path: "/a/product_name/1", + shouldMatch: true, + }, + { + title: "Path route with multiple patterns with pipe, match", + route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"), + request: newRequest("GET", "http://localhost/b/c/product_name/1"), + vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"}, + host: "", + path: "/b/c/product_name/1", + shouldMatch: true, + }, } for _, test := range tests { testRoute(t, test) diff --git a/Godeps/_workspace/src/github.com/thermokarst/jwt/README.md b/Godeps/_workspace/src/github.com/thermokarst/jwt/README.md index fd13696..c97712c 100644 --- a/Godeps/_workspace/src/github.com/thermokarst/jwt/README.md +++ b/Godeps/_workspace/src/github.com/thermokarst/jwt/README.md @@ -9,72 +9,103 @@ your application: package main import ( - "errors" - "fmt" - "net/http" - "time" + "encoding/json" + "errors" + "fmt" + "net/http" + "time" - "github.com/thermokarst/jwt" + "github.com/thermokarst/jwt" ) func protectMe(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "secured") + fmt.Fprintf(w, "secured") +} + +func dontProtectMe(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "not secured") +} + +func auth(email string, password string) error { + // Hard-code a user + if email != "test" || password != "test" { + return errors.New("invalid credentials") + } + return nil +} + +func setClaims(id string) (map[string]interface{}, error) { + currentTime := time.Now() + return map[string]interface{}{ + "iat": currentTime.Unix(), + "exp": currentTime.Add(time.Minute * 60 * 24).Unix(), + }, nil +} + +func verifyClaims(claims []byte, r *http.Request) error { + currentTime := time.Now() + var c struct { + Iat int64 + Exp int64 + } + _ = json.Unmarshal(claims, &c) + if currentTime.After(time.Unix(c.Exp, 0)) { + return errors.New("this token has expired") + } + return nil } func main() { - var authFunc = func(email string, password string) error { - // Hard-code a user --- this could easily be a database call, etc. - if email != "test" || password != "test" { - return errors.New("invalid credentials") - } - return nil - } + config := &jwt.Config{ + Secret: "password", + Auth: auth, + Claims: setClaims, + } - var claimsFunc = func(userId string) (map[string]interface{}, error) { - currentTime := time.Now() - return map[string]interface{}{ - "iat": currentTime.Unix(), - "exp": currentTime.Add(time.Minute * 60 * 24).Unix(), - "sub": userId, - }, nil - } + j, err := jwt.New(config) + if err != nil { + panic(err) + } - var verifyClaimsFunc = func(claims []byte) error { - currentTime := time.Now() - var c struct { - Exp int64 - Iat int64 - Sub string - } - err := json.Unmarshal(claims, &c) - if err != nil { - return err - } - if currentTime.After(time.Unix(c.Exp, 0)) { - return errors.New("this token has expired!") - } - if c.Sub != "test" { - return errors.New("who are you??!") - } - return nil - } + protect := http.HandlerFunc(protectMe) + dontProtect := http.HandlerFunc(dontProtectMe) - config := &jwt.Config{ - Secret: "password", - Auth: authFunc, - Claims: claimsFunc, - } - j, err := jwt.New(config) - if err != nil { - panic(err) - } - protect := http.HandlerFunc(protectMe) - http.Handle("/authenticate", j.GenerateToken()) - http.Handle("/secure", j.Secure(protect, verifyClaimsFunc)) - http.ListenAndServe(":8080", nil) + http.Handle("/authenticate", j.GenerateToken()) + http.Handle("/secure", j.Secure(protect, verifyClaims)) + http.Handle("/insecure", dontProtect) + http.ListenAndServe(":8080", nil) } ``` +```shell +$ http GET :8080/secure + + HTTP/1.1 401 Unauthorized + Content-Length: 23 + Content-Type: text/plain; charset=utf-8 + Date: Fri, 08 May 2015 06:43:35 GMT + + please provide a token + +$ http POST :8080/authenticate email=test password=test + + HTTP/1.1 200 OK + Content-Length: 130 + Content-Type: text/plain; charset=utf-8 + Date: Fri, 08 May 2015 06:31:42 GMT + + eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzExNTI0ODAsImlhdCI6MTQzMTA2NjA4MH0=.UbJmLqOF4bTH/8+o6CrZfoi1Fu7zTDfCV0kwMQyzmos= + +$ http GET :8080/secure Authorization:"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzExNTI0ODAsImlhdCI6MTQzMTA2NjA4MH0=.UbJmLqOF4bTH/8+o6CrZfoi1Fu7zTDfCV0kwMQyzmos=" + + HTTP/1.1 200 OK + Content-Length: 7 + Content-Type: text/plain; charset=utf-8 + Date: Fri, 08 May 2015 06:38:30 GMT + + secured +``` + # Installation $ go get github.com/thermokarst/jwt @@ -98,6 +129,10 @@ config := &jwt.Config{ j, err := jwt.New(config) ``` +You can also customize the field names by specifying `IdentityField` and +`VerifyField` in the `Config` struct, if you want the credentials to be +something other than `"email"` and `"password"`. + Once the middleware is instantiated, create a route for users to generate a JWT at. diff --git a/Godeps/_workspace/src/github.com/thermokarst/jwt/examples/net-http.go b/Godeps/_workspace/src/github.com/thermokarst/jwt/examples/net-http.go index 9c1b37d..1caef55 100644 --- a/Godeps/_workspace/src/github.com/thermokarst/jwt/examples/net-http.go +++ b/Godeps/_workspace/src/github.com/thermokarst/jwt/examples/net-http.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "errors" "fmt" "net/http" @@ -17,41 +18,52 @@ func dontProtectMe(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "not secured") } +func auth(email string, password string) error { + // Hard-code a user + if email != "test" || password != "test" { + return errors.New("invalid credentials") + } + return nil +} + +func setClaims(id string) (map[string]interface{}, error) { + currentTime := time.Now() + return map[string]interface{}{ + "iat": currentTime.Unix(), + "exp": currentTime.Add(time.Minute * 60 * 24).Unix(), + }, nil +} + +func verifyClaims(claims []byte, r *http.Request) error { + currentTime := time.Now() + var c struct { + Iat int64 + Exp int64 + } + _ = json.Unmarshal(claims, &c) + if currentTime.After(time.Unix(c.Exp, 0)) { + return errors.New("this token has expired") + } + return nil +} + func main() { - var authFunc = func(email string, password string) error { - // Hard-code a user - if email != "test" || password != "test" { - return errors.New("invalid credentials") - } - return nil - } - - var claimsFunc = func(string) (map[string]interface{}, error) { - currentTime := time.Now() - return map[string]interface{}{ - "iat": currentTime.Unix(), - "exp": currentTime.Add(time.Minute * 60 * 24).Unix(), - }, nil - } - - var verifyClaimsFunc = func([]byte) error { - // We don't really care about the claims, just approve as-is - return nil - } - config := &jwt.Config{ Secret: "password", - Auth: authFunc, - Claims: claimsFunc, + Auth: auth, + Claims: setClaims, } + j, err := jwt.New(config) if err != nil { panic(err) } + protect := http.HandlerFunc(protectMe) dontProtect := http.HandlerFunc(dontProtectMe) + http.Handle("/authenticate", j.GenerateToken()) - http.Handle("/secure", j.Secure(protect, verifyClaimsFunc)) + http.Handle("/secure", j.Secure(protect, verifyClaims)) http.Handle("/insecure", dontProtect) http.ListenAndServe(":8080", nil) } diff --git a/Godeps/_workspace/src/github.com/thermokarst/jwt/jwt.go b/Godeps/_workspace/src/github.com/thermokarst/jwt/jwt.go index 8366378..d37be84 100644 --- a/Godeps/_workspace/src/github.com/thermokarst/jwt/jwt.go +++ b/Godeps/_workspace/src/github.com/thermokarst/jwt/jwt.go @@ -31,15 +31,9 @@ var ( ErrMalformedToken = errors.New("please provide a valid token") ErrInvalidSignature = errors.New("signature could not be verified") ErrParsingCredentials = errors.New("error parsing credentials") + ErrInvalidMethod = errors.New("invalid request method") ) -// Config is a container for setting up the JWT middleware. -type Config struct { - Secret string - Auth AuthFunc - Claims ClaimsFunc -} - // AuthFunc is a type for delegating user authentication to the client-code. type AuthFunc func(string, string) error @@ -47,15 +41,26 @@ type AuthFunc func(string, string) error type ClaimsFunc func(string) (map[string]interface{}, error) // VerifyClaimsFunc is a type for processing and validating JWT claims on one -// or more route's in the client-code. -type VerifyClaimsFunc func([]byte) error +// or more routes in the client-code. +type VerifyClaimsFunc func([]byte, *http.Request) error + +// Config is a container for setting up the JWT middleware. +type Config struct { + Secret string + Auth AuthFunc + Claims ClaimsFunc + IdentityField string + VerifyField string +} // Middleware is where we store all the specifics related to the client's // JWT needs. type Middleware struct { - secret string - auth AuthFunc - claims ClaimsFunc + secret string + auth AuthFunc + claims ClaimsFunc + identityField string + verifyField string } // New creates a new Middleware from a user-specified configuration. @@ -72,10 +77,18 @@ func New(c *Config) (*Middleware, error) { if c.Claims == nil { return nil, ErrMissingClaimsFunc } + if c.IdentityField == "" { + c.IdentityField = "email" + } + if c.VerifyField == "" { + c.VerifyField = "password" + } m := &Middleware{ - secret: c.Secret, - auth: c.Auth, - claims: c.Claims, + secret: c.Secret, + auth: c.Auth, + claims: c.Claims, + identityField: c.IdentityField, + verifyField: c.VerifyField, } return m, nil } @@ -142,7 +155,7 @@ func (m *Middleware) Secure(h http.Handler, v VerifyClaimsFunc) http.Handler { message: "decoding claims", } } - err = v(claimSet) + err = v(claimSet, r) if err != nil { return &jwtError{ status: http.StatusUnauthorized, @@ -163,6 +176,13 @@ func (m *Middleware) Secure(h http.Handler, v VerifyClaimsFunc) http.Handler { // the requester. func (m *Middleware) GenerateToken() http.Handler { generateHandler := func(w http.ResponseWriter, r *http.Request) *jwtError { + if r.Method != "POST" { + return &jwtError{ + status: http.StatusBadRequest, + err: ErrInvalidMethod, + message: "receiving request", + } + } var b map[string]string err := json.NewDecoder(r.Body).Decode(&b) if err != nil { @@ -172,7 +192,22 @@ func (m *Middleware) GenerateToken() http.Handler { message: "parsing authorization", } } - err = m.auth(b["email"], b["password"]) + // Check if required fields are in the body + if _, ok := b[m.identityField]; !ok { + return &jwtError{ + status: http.StatusBadRequest, + err: ErrParsingCredentials, + message: "parsing credentials, missing identity field", + } + } + if _, ok := b[m.verifyField]; !ok { + return &jwtError{ + status: http.StatusBadRequest, + err: ErrParsingCredentials, + message: "parsing credentials, missing verify field", + } + } + err = m.auth(b[m.identityField], b[m.verifyField]) if err != nil { return &jwtError{ status: http.StatusInternalServerError, @@ -192,7 +227,7 @@ func (m *Middleware) GenerateToken() http.Handler { } // Generate claims for user - claims, err := m.claims(b["email"]) + claims, err := m.claims(b[m.identityField]) if err != nil { return &jwtError{ status: http.StatusInternalServerError, diff --git a/Godeps/_workspace/src/github.com/thermokarst/jwt/jwt_test.go b/Godeps/_workspace/src/github.com/thermokarst/jwt/jwt_test.go index c2ad4ba..c5579b8 100644 --- a/Godeps/_workspace/src/github.com/thermokarst/jwt/jwt_test.go +++ b/Godeps/_workspace/src/github.com/thermokarst/jwt/jwt_test.go @@ -32,7 +32,7 @@ var claimsFunc = func(id string) (map[string]interface{}, error) { }, nil } -var verifyClaimsFunc = func(claims []byte) error { +var verifyClaimsFunc = func(claims []byte, r *http.Request) error { currentTime := time.Now() var c struct { Exp int64 @@ -100,6 +100,12 @@ func TestNewJWTMiddleware(t *testing.T) { if _, ok := claimsVal["iat"]; !ok { t.Errorf("wanted a claims set, got %v", claimsVal) } + if middleware.identityField != "email" { + t.Errorf("wanted email, got %v", middleware.identityField) + } + if middleware.verifyField != "password" { + t.Errorf("wanted password, got %v", middleware.verifyField) + } } func TestNewJWTMiddlewareNoConfig(t *testing.T) { @@ -215,3 +221,14 @@ func TestSecureHandlerGoodToken(t *testing.T) { t.Errorf("wanted %s, got %s", "test", body) } } + +func TestGenerateTokenHandlerNotPOST(t *testing.T) { + middleware := newMiddlewareOrFatal(t) + resp := httptest.NewRecorder() + req, _ := http.NewRequest("PUT", "http://example.com", nil) + middleware.GenerateToken().ServeHTTP(resp, req) + body := strings.TrimSpace(resp.Body.String()) + if body != ErrInvalidMethod.Error() { + t.Errorf("wanted %q, got %q", ErrInvalidMethod.Error(), body) + } +}