Refactor JWT implementation
This commit is contained in:
@@ -85,12 +85,8 @@ func (s server) Serve(ctx context.Context) {
|
||||
r.Use(LogResponse)
|
||||
}
|
||||
|
||||
println("using up DefaultJwtHandler", auth.DefaultJwtHandler != nil)
|
||||
|
||||
r.Use(
|
||||
auth.DefaultJwtHandler.HttpVerifier(),
|
||||
auth.DefaultJwtHandler.HttpAuthenticator(),
|
||||
)
|
||||
// Verifies JWT in headers, cookies, ...
|
||||
r.Use(auth.JWT().HttpVerifier())
|
||||
|
||||
for _, mountRoutes := range s.endpoints {
|
||||
mountRoutes(r)
|
||||
|
||||
@@ -38,3 +38,20 @@ func ErrUnauthorizedScope() error {
|
||||
errors.StackTrimAtFn("http.HandlerFunc.ServeHTTP"),
|
||||
)
|
||||
}
|
||||
|
||||
func ErrMalformedToken(details string) error {
|
||||
return errors.New(
|
||||
errors.KindUnauthorized,
|
||||
|
||||
"malformed token: "+details,
|
||||
|
||||
errors.Meta("type", "malformedToken"),
|
||||
|
||||
// translation namespace & key
|
||||
errors.Meta(locale.ErrorMetaNamespace{}, "internal"),
|
||||
errors.Meta(locale.ErrorMetaKey{}, "auth.errors.malformedToken"),
|
||||
|
||||
errors.StackSkip(1),
|
||||
errors.StackTrimAtFn("http.HandlerFunc.ServeHTTP"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
ExtraReqInfo struct {
|
||||
RemoteAddr string
|
||||
UserAgent string
|
||||
}
|
||||
)
|
||||
|
||||
func ExtraReqInfoMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ExtraReqInfo{}, ExtraReqInfo{
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
UserAgent: r.UserAgent(),
|
||||
})))
|
||||
})
|
||||
}
|
||||
|
||||
func GetExtraReqInfoFromContext(ctx context.Context) ExtraReqInfo {
|
||||
eti := ctx.Value(ExtraReqInfo{})
|
||||
if eti != nil {
|
||||
return eti.(ExtraReqInfo)
|
||||
} else {
|
||||
return ExtraReqInfo{}
|
||||
}
|
||||
}
|
||||
+9
-14
@@ -1,10 +1,5 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
Identifiable interface {
|
||||
Identity() uint64
|
||||
@@ -13,16 +8,16 @@ type (
|
||||
String() string
|
||||
}
|
||||
|
||||
TokenGenerator interface {
|
||||
Encode(i Identifiable, clientID uint64, scope ...string) (token []byte, err error)
|
||||
Generate(ctx context.Context, i Identifiable, clientID uint64, scope ...string) (token []byte, err error)
|
||||
}
|
||||
//TokenGenerator interface {
|
||||
// Encode(i Identifiable, clientID uint64, scope ...string) (token []byte, err error)
|
||||
// Generate(ctx context.Context, i Identifiable, clientID uint64, scope ...string) (token []byte, err error)
|
||||
//}
|
||||
|
||||
TokenHandler interface {
|
||||
TokenGenerator
|
||||
HttpVerifier() func(http.Handler) http.Handler
|
||||
HttpAuthenticator() func(http.Handler) http.Handler
|
||||
}
|
||||
//TokenHandler interface {
|
||||
// TokenGenerator
|
||||
// HttpVerifier() func(http.Handler) http.Handler
|
||||
// HttpAuthenticator() func(http.Handler) http.Handler
|
||||
//}
|
||||
|
||||
Signer interface {
|
||||
Sign(userID uint64, pp ...interface{}) string
|
||||
|
||||
+242
-182
@@ -2,67 +2,96 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/api"
|
||||
"github.com/cortezaproject/corteza-server/pkg/id"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza-server/pkg/payload"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"github.com/go-chi/jwtauth"
|
||||
"github.com/go-oauth2/oauth2/v4"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/jwk"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
"github.com/spf13/cast"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
tokenManager struct {
|
||||
signer interface {
|
||||
Sign(accessToken string, identity Identifiable, clientID uint64, scope ...string) (signed []byte, err error)
|
||||
}
|
||||
|
||||
MiddlewareValidator interface {
|
||||
HttpValidator(scope ...string) func(http.Handler) http.Handler
|
||||
}
|
||||
|
||||
oauth2manager interface {
|
||||
LoadAccessToken(ctx context.Context, access string) (ti oauth2.TokenInfo, err error)
|
||||
GenerateAccessToken(ctx context.Context, gt oauth2.GrantType, tgr *oauth2.TokenGenerateRequest) (oauth2.TokenInfo, error)
|
||||
}
|
||||
|
||||
jwtManager struct {
|
||||
// Expiration time in minutes
|
||||
expiry time.Duration
|
||||
|
||||
signAlgo jwa.SignatureAlgorithm
|
||||
signKey jwk.Key
|
||||
|
||||
log *zap.Logger
|
||||
|
||||
oa2m oauth2manager
|
||||
|
||||
issuerClaim string
|
||||
}
|
||||
|
||||
// @todo remove
|
||||
tokenStore interface {
|
||||
LookupUserByID(ctx context.Context, id uint64) (*types.User, error)
|
||||
LookupAuthOa2tokenByAccess(ctx context.Context, access string) (*types.AuthOa2token, error)
|
||||
SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter) (types.RoleMemberSet, types.RoleMemberFilter, error)
|
||||
|
||||
CreateAuthOa2token(ctx context.Context, rr ...*types.AuthOa2token) error
|
||||
DeleteAuthOA2TokenByUserID(ctx context.Context, _userID uint64) error
|
||||
|
||||
UpsertAuthConfirmedClient(ctx context.Context, rr ...*types.AuthConfirmedClient) error
|
||||
}
|
||||
|
||||
ExtraReqInfo struct {
|
||||
RemoteAddr string
|
||||
UserAgent string
|
||||
}
|
||||
// @todo remove
|
||||
//tokenLookup interface {
|
||||
// LookupAuthOa2tokenByID(ctx context.Context, id uint64) (*types.AuthOa2token, error)
|
||||
//}
|
||||
//
|
||||
//tokenStoreWithLookup interface {
|
||||
// tokenStore
|
||||
// tokenLookup
|
||||
//}
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultJwtHandler TokenHandler
|
||||
DefaultJwtStore tokenStore
|
||||
defaultJWTManager *jwtManager
|
||||
//DefaultJwtStore tokenStoreWithLookup
|
||||
)
|
||||
|
||||
func SetupDefault(secret string, expiry time.Duration) (err error) {
|
||||
// JWT returns d
|
||||
func JWT() *jwtManager {
|
||||
return defaultJWTManager
|
||||
}
|
||||
|
||||
func SetupDefault(oa2m oauth2manager, secret string, expiry time.Duration) (err error) {
|
||||
// Use JWT secret for hmac signer for now
|
||||
DefaultSigner = HmacSigner(secret)
|
||||
DefaultJwtHandler, err = TokenManager(secret, expiry)
|
||||
defaultJWTManager, err = NewJWTManager(oa2m, jwa.HS512, secret, expiry)
|
||||
return
|
||||
}
|
||||
|
||||
// TokenManager returns token management facility
|
||||
// NewJWTManager initializes and returns new instance of JWT manager
|
||||
// @todo should be extended to accept different kinds of algorythms, private-keys etc.
|
||||
func TokenManager(secret string, expiry time.Duration) (tm *tokenManager, err error) {
|
||||
tm = &tokenManager{
|
||||
expiry: expiry,
|
||||
signAlgo: jwa.HS512,
|
||||
func NewJWTManager(oa2m oauth2manager, algo jwa.SignatureAlgorithm, secret string, expiry time.Duration) (tm *jwtManager, err error) {
|
||||
tm = &jwtManager{
|
||||
expiry: expiry,
|
||||
signAlgo: algo,
|
||||
issuerClaim: "cortezaproject.org",
|
||||
log: logger.Default(),
|
||||
oa2m: oa2m,
|
||||
}
|
||||
|
||||
if len(secret) == 0 {
|
||||
@@ -74,72 +103,40 @@ func TokenManager(secret string, expiry time.Duration) (tm *tokenManager, err er
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
//
|
||||
//var (
|
||||
// // tuukn = jwt.New()
|
||||
// // signed []byte
|
||||
// //)
|
||||
// //
|
||||
// //if err = tuukn.Set(jwt.ExpirationKey, expiry); err != nil {
|
||||
// // return
|
||||
// //}
|
||||
// //
|
||||
// signed, err = jwt.Sign(tuukn, jwa.HS512, []byte(secret))
|
||||
//
|
||||
// tkn = &tokenManager{
|
||||
// expiry: expiry,
|
||||
// tokenAuth: jwtauth.New(jwt.SigningMethodHS512.Alg(), []byte(secret), nil),
|
||||
// secret: []byte(secret),
|
||||
// }, nil
|
||||
//
|
||||
//return tkn, nil
|
||||
}
|
||||
|
||||
// SetJWTStore set store for JWT
|
||||
// @todo find better way to initiate store,
|
||||
// it mainly used for generating and storing accessToken for impersonate and corredor, Ref: j.Generate()
|
||||
func SetJWTStore(store tokenStore) {
|
||||
DefaultJwtStore = store
|
||||
}
|
||||
|
||||
// Authenticate the token from the given string and return parsed token or error
|
||||
func (tm *tokenManager) Authenticate(token string) (pToken jwt.Token, err error) {
|
||||
if pToken, err = jwt.Parse([]byte(token), jwt.WithVerify(tm.signAlgo, tm.signKey)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = jwt.Validate(pToken); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//// Encode identity into a
|
||||
//func (tm *tokenManager) Encode(identity Identifiable, scope ...string) ([]byte, error) {
|
||||
// var (
|
||||
// // when possible, extend this with the client
|
||||
// clientID uint64 = 0
|
||||
// )
|
||||
//// @todo remove
|
||||
////// SetJWTStore set store for JWT
|
||||
////// @todo find better way to initiate store,
|
||||
////// it mainly used for generating and storing accessToken for impersonate and corredor, Ref: j.Generate()
|
||||
////func SetJWTStore(store tokenStoreWithLookup) {
|
||||
//// DefaultJwtStore = store
|
||||
////}
|
||||
//
|
||||
// if len(scope) == 0 {
|
||||
// // for backward compatibility we default
|
||||
// // unset scope to profile & api
|
||||
// scope = []string{"profile", "api"}
|
||||
//// Authenticate the token from the given string and return parsed token or error
|
||||
//func (m *jwtManager) Authenticate(s string) (pToken jwt.Token, err error) {
|
||||
// if pToken, err = jwt.Parse([]byte(s), jwt.WithVerify(m.signAlgo, m.signKey)); err != nil {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// return tm.Encode(identity, clientID, scope...)
|
||||
// if err = jwt.Validate(pToken); err != nil {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// return
|
||||
//}
|
||||
|
||||
// Encode give identity, clientID & scope into JWT access token (that can be use for API requests)
|
||||
// Sign takes security information and returns signed JWT
|
||||
//
|
||||
// @todo this follows implementation in auth/oauth2/jwt_access.go
|
||||
// and should be refactored accordingly (move both into the same location/pkg => here)
|
||||
func (tm *tokenManager) Encode(identity Identifiable, clientID uint64, scope ...string) (_ []byte, err error) {
|
||||
// Access token is expected to be issued by OAuth2 token manager
|
||||
// without it, we can only do static (JWT itself) validation
|
||||
//f
|
||||
// Identity holds user ID and all roles that go into this security context
|
||||
// Client ID represents the auth client that was used
|
||||
func (m *jwtManager) Sign(accessToken string, identity Identifiable, clientID uint64, scope ...string) (signed []byte, err error) {
|
||||
var (
|
||||
roles string
|
||||
token = jwt.New()
|
||||
roles = ""
|
||||
)
|
||||
|
||||
if len(scope) == 0 {
|
||||
@@ -149,12 +146,12 @@ func (tm *tokenManager) Encode(identity Identifiable, clientID uint64, scope ...
|
||||
}
|
||||
|
||||
for _, r := range identity.Roles() {
|
||||
roles += fmt.Sprintf(" %d", r)
|
||||
roles += strconv.FormatUint(r, 10)
|
||||
}
|
||||
|
||||
// previous implementation had special a "salt" claim that ensured JWT uniquness
|
||||
// we're using more standard approach with JWT ID now.
|
||||
if err = token.Set(jwt.JwtIDKey, fmt.Sprintf("%d", id.Next())); err != nil {
|
||||
// this is the key part
|
||||
// here we put access token to the JWT ID claim
|
||||
if err = token.Set(jwt.JwtIDKey, accessToken); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -162,11 +159,19 @@ func (tm *tokenManager) Encode(identity Identifiable, clientID uint64, scope ...
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set(jwt.ExpirationKey, time.Now().Add(tm.expiry).Unix()); err != nil {
|
||||
if err = token.Set(jwt.ExpirationKey, time.Now().Add(m.expiry).Unix()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set(jwt.AudienceKey, fmt.Sprintf("%d", clientID)); err != nil {
|
||||
if err = token.Set(jwt.IssuerKey, m.issuerClaim); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set(jwt.IssuedAtKey, time.Now().Unix()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set("clientID", strconv.FormatUint(clientID, 10)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -178,57 +183,87 @@ func (tm *tokenManager) Encode(identity Identifiable, clientID uint64, scope ...
|
||||
return
|
||||
}
|
||||
|
||||
return jwt.Sign(token, tm.signAlgo, tm.signKey)
|
||||
if signed, err = jwt.Sign(token, m.signAlgo, m.signKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//claims := jwt.MapClaims{
|
||||
// "sub": identity.String(),
|
||||
// "exp": time.Now().Add(tm.expiry).Unix(),
|
||||
// "aud": fmt.Sprintf("%d", clientID),
|
||||
// "scope": strings.Join(scope, " "),
|
||||
// "roles": strings.TrimSpace(roles),
|
||||
//}
|
||||
//
|
||||
//newToken := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
|
||||
//newToken.Header["salt"] = string(rand.Bytes(32))
|
||||
//access, _ := newToken.SignedString(tm.secret)
|
||||
//return access
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
// HttpVerifier returns a HTTP handler that verifies JWT and stores it into context
|
||||
func (tm *tokenManager) HttpVerifier() func(http.Handler) http.Handler {
|
||||
////jwt.WithHTTPClient()
|
||||
//return func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// token, err := jwt.ParseRequest(req)
|
||||
// if err != nil {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// next.ServeHTTP(w, req)
|
||||
// })
|
||||
//}
|
||||
func (m *jwtManager) Generate(ctx context.Context, i Identifiable, clientID uint64, scope ...string) (signed []byte, err error) {
|
||||
var (
|
||||
ti oauth2.TokenInfo
|
||||
)
|
||||
|
||||
return jwtauth.Verifier(jwtauth.New(tm.signAlgo.String(), tm.signKey, nil))
|
||||
ti, err = m.oa2m.GenerateAccessToken(ctx, oauth2.Implicit, &oauth2.TokenGenerateRequest{
|
||||
ClientID: strconv.FormatUint(clientID, 10),
|
||||
UserID: i.String(),
|
||||
Scope: strings.Join(scope, " "),
|
||||
Refresh: "cli?",
|
||||
AccessTokenExp: m.expiry,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return m.Sign(ti.GetAccess(), i, 0, scope...)
|
||||
}
|
||||
|
||||
// HttpAuthenticator converts JWT claims into identity and stores it into context
|
||||
func (tm *tokenManager) HttpAuthenticator() func(http.Handler) http.Handler {
|
||||
func ValidateContext(ctx context.Context, oa2m oauth2manager, scope ...string) (err error) {
|
||||
var (
|
||||
token jwt.Token
|
||||
)
|
||||
|
||||
if token, _, err = jwtauth.FromContext(ctx); err != nil {
|
||||
return ErrUnauthorized()
|
||||
}
|
||||
|
||||
return Validate(ctx, token, oa2m, scope...)
|
||||
}
|
||||
|
||||
func Validate(ctx context.Context, token jwt.Token, oa2m oauth2manager, scope ...string) (err error) {
|
||||
if !CheckJwtScope(token, scope...) {
|
||||
return ErrUnauthorizedScope()
|
||||
}
|
||||
|
||||
// Extract the JWT id from the token (string) and convert it to uint64
|
||||
// to be compatible with the lookup function
|
||||
if len(token.JwtID()) < 10 {
|
||||
return ErrMalformedToken("missing or malformed JWT ID")
|
||||
}
|
||||
|
||||
// @todo we could use a simple caching mechanism here
|
||||
// 1. if lookup is successful, add a JWT ID to the list
|
||||
// 2. add short exp time (that should not last longer than token's exp time)
|
||||
// 3. check against the list first; if JWT ID is not present there check in storage
|
||||
//
|
||||
if _, err = oa2m.LoadAccessToken(ctx, token.JwtID()); err != nil {
|
||||
return ErrUnauthorized()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HttpVerifier http middleware handler will verify a JWT string from a http request.
|
||||
func (m *jwtManager) HttpVerifier() func(http.Handler) http.Handler {
|
||||
return jwtauth.Verifier(jwtauth.New(m.signAlgo.String(), m.signKey, nil))
|
||||
}
|
||||
|
||||
func (m *jwtManager) HttpValidator(scope ...string) func(http.Handler) http.Handler {
|
||||
if len(scope) == 0 {
|
||||
// ensure that scope is not empty
|
||||
scope = []string{"api"}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
tkn, _, err := jwtauth.FromContext(ctx)
|
||||
|
||||
// When token is present, expect no errors and valid claims!
|
||||
if tkn != nil {
|
||||
if err != nil {
|
||||
// But if token is present, there shouldn't be an error
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx = SetIdentityToContext(ctx, IdentityFromToken(tkn))
|
||||
r = r.WithContext(ctx)
|
||||
if err := ValidateContext(r.Context(), m.oa2m, scope...); err != nil {
|
||||
errors.ProperlyServeHTTP(w, r, err, false)
|
||||
return
|
||||
} else {
|
||||
token, _, _ := jwtauth.FromContext(r.Context())
|
||||
r = r.WithContext(SetIdentityToContext(r.Context(), IdentityFromToken(token)))
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
@@ -236,62 +271,87 @@ func (tm *tokenManager) HttpAuthenticator() func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// Generates JWT and stores alongside with client-confirmation entry,
|
||||
func (tm *tokenManager) Generate(ctx context.Context, i Identifiable, clientID uint64, scope ...string) (token []byte, err error) {
|
||||
var (
|
||||
eti = GetExtraReqInfoFromContext(ctx)
|
||||
oa2t = &types.AuthOa2token{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now().Round(time.Second),
|
||||
RemoteAddr: eti.RemoteAddr,
|
||||
UserAgent: eti.UserAgent,
|
||||
ClientID: clientID,
|
||||
}
|
||||
//// HttpAuthenticator converts JWT claims into identity and stores it into context
|
||||
//func (m *jwtManager) HttpAuthenticator(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx := r.Context()
|
||||
//
|
||||
// tkn, _, err := jwtauth.FromContext(ctx)
|
||||
//
|
||||
// // Requests w/o token should not yield an error
|
||||
// // there are parts of the system that can be access without it
|
||||
// // and/or handle such situation internally
|
||||
// if err != nil && !errors.Is(err, jwtauth.ErrNoTokenFound) {
|
||||
// api.Send(w, r, err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // If token is present extract identity
|
||||
// if tkn != nil {
|
||||
// ctx = SetIdentityToContext(ctx, IdentityFromToken(tkn))
|
||||
// r = r.WithContext(ctx)
|
||||
//
|
||||
// // @todo verify JWT ID (access-token!!
|
||||
// tkn.JwtID()
|
||||
//
|
||||
// }
|
||||
//
|
||||
// next.ServeHTTP(w, r)
|
||||
// })
|
||||
//}
|
||||
|
||||
acc = &types.AuthConfirmedClient{
|
||||
ConfirmedAt: oa2t.CreatedAt,
|
||||
ClientID: clientID,
|
||||
}
|
||||
)
|
||||
//
|
||||
//// Generate makes a new token and stores it in the database
|
||||
//func (tm *tokenManager) Generate(ctx context.Context, i Identifiable, clientID uint64, scope ...string) (token []byte, err error) {
|
||||
// var (
|
||||
// // eti = GetExtraReqInfoFromContext(ctx)
|
||||
// // oa2t = &types.AuthOa2token{
|
||||
// // ID: id.Next(),
|
||||
// // CreatedAt: time.Now().Round(time.Second),
|
||||
// // RemoteAddr: eti.RemoteAddr,
|
||||
// // UserAgent: eti.UserAgent,
|
||||
// // ClientID: clientID,
|
||||
// // }
|
||||
// //
|
||||
// // acc = &types.AuthConfirmedClient{
|
||||
// // ConfirmedAt: oa2t.CreatedAt,
|
||||
// // ClientID: clientID,
|
||||
// // }
|
||||
// oa2t *types.AuthOa2token
|
||||
// acc *types.AuthConfirmedClient
|
||||
//
|
||||
// jwtID = id.Next()
|
||||
// )
|
||||
//
|
||||
// if oa2t, acc, err = MakeAuthStructs(ctx, jwtID, i.Identity(), clientID, nil, tm.expiry); err != nil {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if token, err = tm.make(jwtID, i, clientID, scope...); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// oa2t.Access = string(token)
|
||||
//
|
||||
// // use the same expiration as on token
|
||||
// //oa2t.ExpiresAt = oa2t.CreatedAt.Add(tm.expiry)
|
||||
//
|
||||
// //if oa2t.Data, err = json.Marshal(oa2t); err != nil {
|
||||
// // return
|
||||
// //}
|
||||
//
|
||||
// //if oa2t.UserID, _ = ExtractFromSubClaim(i.String()); oa2t.UserID == 0 {
|
||||
// // // UserID stores collection of IDs: user's ID and set of all roles' user is member of
|
||||
// // return nil, fmt.Errorf("could not parse user ID from token")
|
||||
// //}
|
||||
// //
|
||||
// //// copy user id to auth client confirmation
|
||||
// //acc.UserID = oa2t.UserID
|
||||
//
|
||||
// return token, StoreAuthToken(ctx, DefaultJwtStore, oa2t, acc)
|
||||
//}
|
||||
|
||||
if token, err = tm.Encode(i, clientID, scope...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
oa2t.Access = string(token)
|
||||
|
||||
// use the same expiration as on token
|
||||
oa2t.ExpiresAt = oa2t.CreatedAt.Add(tm.expiry)
|
||||
|
||||
if oa2t.Data, err = json.Marshal(oa2t); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if oa2t.UserID, _ = ExtractFromSubClaim(i.String()); oa2t.UserID == 0 {
|
||||
// UserID stores collection of IDs: user's ID and set of all roles' user is member of
|
||||
return nil, fmt.Errorf("could not parse user ID from token")
|
||||
}
|
||||
|
||||
// copy user id to auth client confirmation
|
||||
acc.UserID = oa2t.UserID
|
||||
|
||||
if err = DefaultJwtStore.UpsertAuthConfirmedClient(ctx, acc); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return token, DefaultJwtStore.CreateAuthOa2token(ctx, oa2t)
|
||||
}
|
||||
|
||||
func GetExtraReqInfoFromContext(ctx context.Context) ExtraReqInfo {
|
||||
eti := ctx.Value(ExtraReqInfo{})
|
||||
if eti != nil {
|
||||
return eti.(ExtraReqInfo)
|
||||
} else {
|
||||
return ExtraReqInfo{}
|
||||
}
|
||||
}
|
||||
|
||||
// ClaimsToIdentity decodes sub & roles claims into identity
|
||||
// IdentityFromToken decodes sub & roles claims into identity
|
||||
func IdentityFromToken(token jwt.Token) *identity {
|
||||
var (
|
||||
roles, _ = token.Get("roles")
|
||||
|
||||
+54
-50
@@ -1,53 +1,57 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
//import (
|
||||
// "net/http"
|
||||
//)
|
||||
//
|
||||
//func MiddlewareValidOnly(next http.Handler) http.Handler {
|
||||
// return AccessTokenCheck("api")(next)
|
||||
//}
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/go-chi/jwtauth"
|
||||
)
|
||||
|
||||
func MiddlewareValidOnly(next http.Handler) http.Handler {
|
||||
return AccessTokenCheck("api")(next)
|
||||
}
|
||||
|
||||
func AccessTokenCheck(scope ...string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = r.Context()
|
||||
|
||||
token, _, err := jwtauth.FromContext(ctx)
|
||||
spew.Dump(token, err)
|
||||
|
||||
if err != nil {
|
||||
errors.ProperlyServeHTTP(w, r, ErrUnauthorized(), false)
|
||||
return
|
||||
}
|
||||
|
||||
if !CheckJwtScope(token, scope...) {
|
||||
errors.ProperlyServeHTTP(w, r, ErrUnauthorizedScope(), false)
|
||||
}
|
||||
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
// @todo we need to check if token is in store!!
|
||||
//
|
||||
//// verify JWT from store
|
||||
//_, err = DefaultJwtStore.LookupAuthOa2tokenByAccess(ctx, tkn.Raw)
|
||||
//if err != nil {
|
||||
// errors.ProperlyServeHTTP(w, r, ErrUnauthorized(), false)
|
||||
// return
|
||||
//}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
//func AccessTokenCheck(s store.AuthOa2tokens, scope ...string) func(http.Handler) http.Handler {
|
||||
// return func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// if err := validateContextToken(r.Context(), s, scope); err != nil {
|
||||
// errors.ProperlyServeHTTP(w, r, err, false)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// next.ServeHTTP(w, r)
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func validateContextToken(ctx context.Context, s store.AuthOa2tokens, scope []string) (err error) {
|
||||
// var (
|
||||
// token jwt.Token
|
||||
// )
|
||||
//
|
||||
// if token, _, err = jwtauth.FromContext(ctx); err != nil {
|
||||
// return ErrUnauthorized()
|
||||
// }
|
||||
//
|
||||
// if !CheckJwtScope(token, scope...) {
|
||||
// return ErrUnauthorizedScope()
|
||||
// }
|
||||
//
|
||||
// // Extract the JWT id from the token (string) and convert it to uint64
|
||||
// // to be compatible with the lookup function
|
||||
// if len(token.JwtID()) < 10 {
|
||||
// return ErrMalformedToken("missing or malformed JWT ID")
|
||||
// }
|
||||
//
|
||||
// // check if token exists in our DB
|
||||
// // there is no need to check for anything beyond existence
|
||||
// // because
|
||||
// //
|
||||
// // @todo we could use a simple caching mechanism here
|
||||
// // 1. if lookup is successful, add a JWT ID to the list
|
||||
// // 2. add short exp time (that should not last onger than token's exp time)
|
||||
// // 3. check against the list first; if JWT ID is not present there check in storage
|
||||
// //
|
||||
// if _, err = store.LookupAuthOa2tokenByAccess(ctx, s, token.JwtID()); err != nil {
|
||||
// return ErrUnauthorized()
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
||||
@@ -104,7 +104,7 @@ type (
|
||||
}
|
||||
|
||||
authTokenMaker interface {
|
||||
auth.TokenGenerator
|
||||
Generate(ctx context.Context, i auth.Identifiable, clientID uint64, scope ...string) (token []byte, err error)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -168,7 +168,7 @@ func NewService(logger *zap.Logger, opt options.CorredorOpt) *service {
|
||||
|
||||
iteratorProviders: make(map[string]IteratorResourceFinder),
|
||||
|
||||
authTokenMaker: auth.DefaultJwtHandler,
|
||||
authTokenMaker: auth.JWT(),
|
||||
eventRegistry: eventbus.Service(),
|
||||
|
||||
denyExec: make(map[string]map[uint64]bool),
|
||||
|
||||
@@ -7,15 +7,15 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza-server/pkg/options"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestSession_procRawMessage(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
s = session{server: Server(nil, options.WebsocketOpt{})}
|
||||
jwtHandler, err = auth.TokenManager("secret", time.Minute)
|
||||
req = require.New(t)
|
||||
s = session{server: Server(nil, options.WebsocketOpt{})}
|
||||
|
||||
userID uint64 = 123
|
||||
token []byte
|
||||
@@ -28,6 +28,9 @@ func TestSession_procRawMessage(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
jwtManager, err := auth.NewJWTManager(nil, jwa.HS512, "secret", time.Minute)
|
||||
req.NoError(err)
|
||||
|
||||
if testing.Verbose() {
|
||||
s.logger = logger.MakeDebugLogger()
|
||||
} else {
|
||||
@@ -36,7 +39,7 @@ func TestSession_procRawMessage(t *testing.T) {
|
||||
|
||||
req.NoError(err)
|
||||
|
||||
token, err = jwtHandler.Encode(auth.Authenticated(userID, 456, 789), 0, "api")
|
||||
token, err = jwtManager.Sign("access-token", auth.Authenticated(userID, 456, 789), 0, "api")
|
||||
req.NoError(err)
|
||||
|
||||
req.EqualError(s.procRawMessage([]byte("{}")), "unauthenticated session")
|
||||
@@ -53,7 +56,7 @@ func TestSession_procRawMessage(t *testing.T) {
|
||||
req.Equal(userID, s.identity.Identity())
|
||||
|
||||
// Repeat with the same user
|
||||
token, err = jwtHandler.Encode(auth.Authenticated(userID, 456, 789), 0, "api")
|
||||
token, err = jwtManager.Sign("access-token", auth.Authenticated(userID, 456, 789), 0, "api")
|
||||
req.NoError(err)
|
||||
|
||||
req.NoError(s.procRawMessage(mockResponse(token)))
|
||||
@@ -61,9 +64,10 @@ func TestSession_procRawMessage(t *testing.T) {
|
||||
req.Equal(userID, s.identity.Identity())
|
||||
|
||||
// Try to authenticate on an existing authenticated session as a different user
|
||||
token, err = jwtHandler.Encode(auth.Authenticated(userID+1, 456, 789), 0, "api")
|
||||
token, err = jwtManager.Sign("access-token", auth.Authenticated(userID+1, 456, 789), 0, "api")
|
||||
req.NoError(err)
|
||||
|
||||
req.EqualError(s.procRawMessage(mockResponse(token)), "unauthorized: identity does not match")
|
||||
|
||||
t.Error("are we actually checking if access token exists?")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user