Adds JWT token generation methods
- Generates token encoded with user and save it to store - Updated token generation method usage in Corredor and Impersonalization
This commit is contained in:
@@ -406,6 +406,8 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
auth.SetJWTStore(app.Store)
|
||||
|
||||
corredor.Service().SetUserFinder(sysService.DefaultUser)
|
||||
corredor.Service().SetRoleFinder(sysService.DefaultRole)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"net/http"
|
||||
)
|
||||
@@ -17,8 +18,13 @@ type (
|
||||
Encode(identity Identifiable, scope ...string) string
|
||||
}
|
||||
|
||||
TokenGenerator interface {
|
||||
Generate(ctx context.Context, identity Identifiable) (string, error)
|
||||
}
|
||||
|
||||
TokenHandler interface {
|
||||
TokenEncoder
|
||||
TokenGenerator
|
||||
Authenticate(token string) (jwt.MapClaims, error)
|
||||
HttpVerifier() func(http.Handler) http.Handler
|
||||
HttpAuthenticator() func(http.Handler) http.Handler
|
||||
|
||||
@@ -2,7 +2,10 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/id"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -20,17 +23,27 @@ type (
|
||||
expiry time.Duration
|
||||
tokenAuth *jwtauth.JWTAuth
|
||||
}
|
||||
|
||||
tokenStore interface {
|
||||
CreateAuthOa2token(ctx context.Context, rr ...*types.AuthOa2token) error
|
||||
UpsertAuthConfirmedClient(ctx context.Context, rr ...*types.AuthConfirmedClient) error
|
||||
}
|
||||
|
||||
ExtraReqInfo struct {
|
||||
RemoteAddr string
|
||||
UserAgent string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultJwtHandler TokenHandler
|
||||
DefaultJwtStore tokenStore
|
||||
)
|
||||
|
||||
func SetupDefault(secret string, expiry time.Duration) {
|
||||
// Use JWT secret for hmac signer for now
|
||||
DefaultSigner = HmacSigner(secret)
|
||||
DefaultJwtHandler, _ = JWT(secret, expiry)
|
||||
|
||||
}
|
||||
|
||||
func JWT(secret string, expiry time.Duration) (tkn *token, err error) {
|
||||
@@ -46,6 +59,13 @@ func JWT(secret string, expiry time.Duration) (tkn *token, err error) {
|
||||
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
|
||||
}
|
||||
|
||||
func (t *token) Authenticate(token string) (jwt.MapClaims, error) {
|
||||
dt, err := t.tokenAuth.Decode(token)
|
||||
if err != nil {
|
||||
@@ -88,7 +108,6 @@ func (t *token) Encode(i Identifiable, scope ...string) string {
|
||||
}
|
||||
|
||||
func (t *token) encode(i Identifiable, clientID uint64, scope ...string) string {
|
||||
|
||||
roles := ""
|
||||
for _, r := range i.Roles() {
|
||||
roles += fmt.Sprintf(" %d", r)
|
||||
@@ -132,6 +151,59 @@ func (t *token) HttpAuthenticator() func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *token) Generate(ctx context.Context, i Identifiable) (tokenString string, err error) {
|
||||
var (
|
||||
eti = GetExtraReqInfoFromContext(ctx)
|
||||
oa2t = &types.AuthOa2token{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now().Round(time.Second),
|
||||
RemoteAddr: eti.RemoteAddr,
|
||||
UserAgent: eti.UserAgent,
|
||||
}
|
||||
|
||||
acc = &types.AuthConfirmedClient{
|
||||
ConfirmedAt: oa2t.CreatedAt,
|
||||
}
|
||||
)
|
||||
|
||||
tokenString = t.Encode(i)
|
||||
oa2t.Access = tokenString
|
||||
oa2t.ExpiresAt = oa2t.CreatedAt.Add(t.expiry)
|
||||
|
||||
if oa2t.Data, err = json.Marshal(oa2t); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// extend this with the client
|
||||
oa2t.ClientID = 0
|
||||
|
||||
// copy client id to auth client confirmation
|
||||
acc.ClientID = oa2t.ClientID
|
||||
|
||||
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 "", 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 tokenString, 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
|
||||
func ClaimsToIdentity(c jwt.MapClaims) (i *identity) {
|
||||
var (
|
||||
|
||||
@@ -104,7 +104,8 @@ type (
|
||||
}
|
||||
|
||||
authTokenMaker interface {
|
||||
Encode(auth.Identifiable, ...string) string
|
||||
auth.TokenEncoder
|
||||
auth.TokenGenerator
|
||||
}
|
||||
)
|
||||
|
||||
@@ -653,6 +654,8 @@ func (svc service) exec(ctx context.Context, script string, runAs string, args S
|
||||
zap.String("eventType", args.EventType()),
|
||||
zap.String("resourceType", args.ResourceType()),
|
||||
)
|
||||
|
||||
token string
|
||||
)
|
||||
|
||||
log.Debug("triggered")
|
||||
@@ -716,7 +719,13 @@ func (svc service) exec(ctx context.Context, script string, runAs string, args S
|
||||
return
|
||||
}
|
||||
|
||||
if err = encodeArguments(req.Args, "authToken", svc.authTokenMaker.Encode(definer)); err != nil {
|
||||
// Generate and save the token
|
||||
token, err = svc.authTokenMaker.Generate(ctx, definer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = encodeArguments(req.Args, "authToken", token); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -728,7 +737,13 @@ func (svc service) exec(ctx context.Context, script string, runAs string, args S
|
||||
return
|
||||
}
|
||||
|
||||
if err = encodeArguments(req.Args, "authToken", svc.authTokenMaker.Encode(invoker)); err != nil {
|
||||
// Generate and save the token
|
||||
token, err = svc.authTokenMaker.Generate(ctx, invoker)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = encodeArguments(req.Args, "authToken", token); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ var _ = errors.Wrap
|
||||
|
||||
type (
|
||||
Auth struct {
|
||||
tokenEncoder auth.TokenEncoder
|
||||
tokenHandler auth.TokenGenerator
|
||||
settings *types.AppSettings
|
||||
authSvc authUserService
|
||||
}
|
||||
@@ -46,7 +46,7 @@ type (
|
||||
|
||||
func (Auth) New() *Auth {
|
||||
return &Auth{
|
||||
tokenEncoder: auth.DefaultJwtHandler,
|
||||
tokenHandler: auth.DefaultJwtHandler,
|
||||
settings: service.CurrentSettings,
|
||||
authSvc: service.DefaultAuth,
|
||||
}
|
||||
@@ -69,8 +69,14 @@ func (ctrl *Auth) makePayload(ctx context.Context, user *types.User) (*authUserR
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate and save the token
|
||||
t, err := ctrl.tokenHandler.Generate(ctx, user)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &authUserResponse{
|
||||
JWT: ctrl.tokenEncoder.Encode(user),
|
||||
JWT: t,
|
||||
User: &authUserPayload{
|
||||
userPayload: &userPayload{
|
||||
ID: user.ID,
|
||||
|
||||
52
tests/system/auth_test.go
Normal file
52
tests/system/auth_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/system/service"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/tests/helpers"
|
||||
)
|
||||
|
||||
func TestAuthImpersonate(t *testing.T) {
|
||||
|
||||
var (
|
||||
h = newHelper(t)
|
||||
ctx = context.Background()
|
||||
user = h.createUserWithEmail(h.randEmail())
|
||||
input = &struct {
|
||||
UserID uint64 `json:",string"`
|
||||
}{
|
||||
UserID: user.ID,
|
||||
}
|
||||
)
|
||||
|
||||
helpers.AllowMe(h, types.UserRbacResource(user.ID), "impersonate")
|
||||
|
||||
var res struct {
|
||||
Response struct {
|
||||
JWT string `json:"jwt"`
|
||||
} `json:"response"`
|
||||
}
|
||||
h.apiInit().
|
||||
Post("/auth/impersonate").
|
||||
Header("Accept", "application/json").
|
||||
JSON(helpers.JSON(input)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&res)
|
||||
|
||||
// make sure response has JWT token
|
||||
jwt := res.Response.JWT
|
||||
h.a.Greater(len(jwt), 0)
|
||||
|
||||
at, err := service.DefaultStore.LookupAuthOa2tokenByAccess(ctx, jwt)
|
||||
h.a.Nil(err)
|
||||
h.a.NotNil(at)
|
||||
h.a.Greater(len(at.Access), 0)
|
||||
h.a.Equal(at.Access, jwt)
|
||||
}
|
||||
Reference in New Issue
Block a user