mirror of
https://github.com/elyby/chrly.git
synced 2025-05-31 14:11:51 +05:30
Rework security module, replace JWT library, invalidate JWT tokens signed for Chrly v4, generate RSA key in runtime when not provided via configuration
This commit is contained in:
@@ -86,6 +86,8 @@ type Authenticator interface {
|
||||
Authenticate(req *http.Request) error
|
||||
}
|
||||
|
||||
// The current middleware implementation doesn't check the scope assigned to the token.
|
||||
// For now there is only one scope and at this moment I don't want to spend time on it
|
||||
func CreateAuthenticationMiddleware(checker Authenticator) mux.MiddlewareFunc {
|
||||
return func(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
@@ -137,12 +139,3 @@ func apiForbidden(resp http.ResponseWriter, reason string) {
|
||||
})
|
||||
_, _ = resp.Write(result)
|
||||
}
|
||||
|
||||
func apiNotFound(resp http.ResponseWriter, reason string) {
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
resp.Header().Set("Content-Type", "application/json")
|
||||
result, _ := json.Marshal([]interface{}{
|
||||
reason,
|
||||
})
|
||||
_, _ = resp.Write(result)
|
||||
}
|
||||
|
@@ -1,78 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SermoDigital/jose/crypto"
|
||||
"github.com/SermoDigital/jose/jws"
|
||||
)
|
||||
|
||||
var hashAlg = crypto.SigningMethodHS256
|
||||
|
||||
const scopesClaim = "scopes"
|
||||
|
||||
type Scope string
|
||||
|
||||
var (
|
||||
SkinScope = Scope("skin")
|
||||
)
|
||||
|
||||
type JwtAuth struct {
|
||||
Emitter
|
||||
Key []byte
|
||||
}
|
||||
|
||||
func (t *JwtAuth) NewToken(scopes ...Scope) ([]byte, error) {
|
||||
if len(t.Key) == 0 {
|
||||
return nil, errors.New("signing key not available")
|
||||
}
|
||||
|
||||
claims := jws.Claims{}
|
||||
claims.Set(scopesClaim, scopes)
|
||||
claims.SetIssuedAt(time.Now())
|
||||
encoder := jws.NewJWT(claims, hashAlg)
|
||||
token, err := encoder.Serialize(t.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (t *JwtAuth) Authenticate(req *http.Request) error {
|
||||
if len(t.Key) == 0 {
|
||||
return t.emitErr(errors.New("Signing key not set"))
|
||||
}
|
||||
|
||||
bearerToken := req.Header.Get("Authorization")
|
||||
if bearerToken == "" {
|
||||
return t.emitErr(errors.New("Authentication header not presented"))
|
||||
}
|
||||
|
||||
if !strings.EqualFold(bearerToken[0:7], "BEARER ") {
|
||||
return t.emitErr(errors.New("Cannot recognize JWT token in passed value"))
|
||||
}
|
||||
|
||||
tokenStr := bearerToken[7:]
|
||||
token, err := jws.ParseJWT([]byte(tokenStr))
|
||||
if err != nil {
|
||||
return t.emitErr(errors.New("Cannot parse passed JWT token"))
|
||||
}
|
||||
|
||||
err = token.Validate(t.Key, hashAlg)
|
||||
if err != nil {
|
||||
return t.emitErr(errors.New("JWT token have invalid signature. It may be corrupted or expired"))
|
||||
}
|
||||
|
||||
t.Emit("authentication:success")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *JwtAuth) emitErr(err error) error {
|
||||
t.Emit("authentication:error", err)
|
||||
return err
|
||||
}
|
@@ -1,127 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNTE2NjU4MTkzIiwic2NvcGVzIjoic2tpbiJ9.agbBS0qdyYMBaVfTZJAZcTTRgW1Y0kZty4H3N2JHBO8"
|
||||
|
||||
func TestJwtAuth_NewToken(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
jwt := &JwtAuth{Key: []byte("secret")}
|
||||
token, err := jwt.NewToken(SkinScope)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, token)
|
||||
})
|
||||
|
||||
t.Run("key not provided", func(t *testing.T) {
|
||||
jwt := &JwtAuth{}
|
||||
token, err := jwt.NewToken(SkinScope)
|
||||
assert.Error(t, err, "signing key not available")
|
||||
assert.Nil(t, token)
|
||||
})
|
||||
}
|
||||
|
||||
func TestJwtAuth_Authenticate(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
emitter := &emitterMock{}
|
||||
emitter.On("Emit", "authentication:success")
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
req.Header.Add("Authorization", "Bearer "+jwt)
|
||||
jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
assert.Nil(t, err)
|
||||
|
||||
emitter.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("request without auth header", func(t *testing.T) {
|
||||
emitter := &emitterMock{}
|
||||
emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool {
|
||||
assert.Error(t, err, "Authentication header not presented")
|
||||
return true
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
assert.Error(t, err, "Authentication header not presented")
|
||||
|
||||
emitter.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("no bearer token prefix", func(t *testing.T) {
|
||||
emitter := &emitterMock{}
|
||||
emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool {
|
||||
assert.Error(t, err, "Cannot recognize JWT token in passed value")
|
||||
return true
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
req.Header.Add("Authorization", "this is not jwt")
|
||||
jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
assert.Error(t, err, "Cannot recognize JWT token in passed value")
|
||||
|
||||
emitter.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("bearer token but not jwt", func(t *testing.T) {
|
||||
emitter := &emitterMock{}
|
||||
emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool {
|
||||
assert.Error(t, err, "Cannot parse passed JWT token")
|
||||
return true
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
req.Header.Add("Authorization", "Bearer thisIs.Not.Jwt")
|
||||
jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
assert.Error(t, err, "Cannot parse passed JWT token")
|
||||
|
||||
emitter.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("when secret is not set", func(t *testing.T) {
|
||||
emitter := &emitterMock{}
|
||||
emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool {
|
||||
assert.Error(t, err, "Signing key not set")
|
||||
return true
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
req.Header.Add("Authorization", "Bearer "+jwt)
|
||||
jwt := &JwtAuth{Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
assert.Error(t, err, "Signing key not set")
|
||||
|
||||
emitter.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("invalid signature", func(t *testing.T) {
|
||||
emitter := &emitterMock{}
|
||||
emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool {
|
||||
assert.Error(t, err, "JWT token have invalid signature. It may be corrupted or expired")
|
||||
return true
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
req.Header.Add("Authorization", "Bearer "+jwt)
|
||||
jwt := &JwtAuth{Key: []byte("this is another secret"), Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
assert.Error(t, err, "JWT token have invalid signature. It may be corrupted or expired")
|
||||
|
||||
emitter.AssertExpectations(t)
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user