3
0

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:
Vivek Patel
2021-09-17 08:50:26 +05:30
committed by Denis Arh
parent 5ac8790bc8
commit 54cbfe20d8
6 changed files with 161 additions and 8 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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 (

View File

@@ -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
}
}

View File

@@ -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
View 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)
}