3
0

Refactor JWT implementation

This commit is contained in:
Denis Arh 2022-01-14 22:19:25 +01:00
parent 2e0adf43b7
commit 59ec77e204
47 changed files with 730 additions and 536 deletions

View File

@ -5,11 +5,13 @@ import (
"net/http"
"github.com/cortezaproject/corteza-server/auth/settings"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/pkg/plugin"
"github.com/cortezaproject/corteza-server/store"
"github.com/go-chi/chi/v5"
"github.com/go-oauth2/oauth2/v4"
"github.com/spf13/cobra"
"go.uber.org/zap"
"google.golang.org/grpc"
@ -62,6 +64,10 @@ type (
// CLI Commands
Command *cobra.Command
jwt auth.MiddlewareValidator
oa2m oauth2.Manager
// Servers
HttpServer httpApiServer
GrpcServer grpcServer

View File

@ -9,6 +9,7 @@ import (
authService "github.com/cortezaproject/corteza-server/auth"
authHandlers "github.com/cortezaproject/corteza-server/auth/handlers"
"github.com/cortezaproject/corteza-server/auth/oauth2"
"github.com/cortezaproject/corteza-server/auth/saml"
authSettings "github.com/cortezaproject/corteza-server/auth/settings"
autService "github.com/cortezaproject/corteza-server/automation/service"
@ -120,13 +121,6 @@ func (app *CortezaApp) Setup() (err error) {
}
}
// set base path for links&routes in auth server
authHandlers.BasePath = app.Opt.HTTPServer.BaseUrl
if err = auth.SetupDefault(app.Opt.Auth.Secret, app.Opt.Auth.Expiry); err != nil {
return
}
http.SetupDefaults(
app.Opt.HTTPClient.HttpClientTimeout,
app.Opt.HTTPClient.ClientTSLInsecure,
@ -323,6 +317,24 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) {
return
}
{
app.oa2m = oauth2.NewManager(
app.Opt.Auth,
app.Log,
&oauth2.ContextClientStore{},
&oauth2.CortezaTokenStore{Store: app.Store},
)
// set base path for links&routes in auth server
authHandlers.BasePath = app.Opt.HTTPServer.BaseUrl
if err = auth.SetupDefault(app.oa2m, app.Opt.Auth.Secret, app.Opt.Auth.Expiry); err != nil {
return
}
app.jwt = auth.JWT()
}
app.WsServer = websocket.Server(app.Log, app.Opt.Websocket)
ctx = actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_APP_Init)
@ -402,7 +414,8 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) {
return
}
auth.SetJWTStore(app.Store)
//@todo remove vv
//auth.SetJWTStore(app.Store)
corredor.Service().SetUserFinder(sysService.DefaultUser)
corredor.Service().SetRoleFinder(sysService.DefaultRole)
@ -498,7 +511,7 @@ func (app *CortezaApp) Activate(ctx context.Context) (err error) {
updateSmtpSettings(app.Log, sysService.CurrentSettings)
if app.AuthService, err = authService.New(ctx, app.Log, app.Store, app.Opt.Auth); err != nil {
if app.AuthService, err = authService.New(ctx, app.Log, app.oa2m, app.Store, app.Opt.Auth); err != nil {
return fmt.Errorf("failed to init auth service: %w", err)
}

View File

@ -95,13 +95,13 @@ func (app *CortezaApp) mountHttpRoutes(r chi.Router) {
zap.String("baseUrl", fullpathAPI),
)
r.Route("/system", systemRest.MountRoutes)
r.Route("/automation", automationRest.MountRoutes)
r.Route("/compose", composeRest.MountRoutes)
r.Route("/system", systemRest.MountRoutes(app.jwt))
r.Route("/automation", automationRest.MountRoutes(app.jwt))
r.Route("/compose", composeRest.MountRoutes(app.jwt))
r.Route("/websocket", app.WsServer.MountRoutes)
if app.Opt.Federation.Enabled {
r.Route("/federation", federationRest.MountRoutes)
r.Route("/federation", federationRest.MountRoutes(app.jwt))
}
var fullpathDocs = options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl, "docs")

View File

@ -51,7 +51,7 @@ type (
var PublicAssets embed.FS
// New initializes Auth service that orchestrates session manager, oauth2 manager and http request handlers
func New(ctx context.Context, log *zap.Logger, s store.Storer, opt options.AuthOpt) (svc *service, err error) {
func New(ctx context.Context, log *zap.Logger, oa2m oauth2def.Manager, s store.Storer, opt options.AuthOpt) (svc *service, err error) {
var (
tpls templateExecutor
defClient *types.AuthClient
@ -73,14 +73,7 @@ func New(ctx context.Context, log *zap.Logger, s store.Storer, opt options.AuthO
sesManager := request.NewSessionManager(s, opt, log)
oauth2Manager := oauth2.NewManager(
opt,
log,
&oauth2.ContextClientStore{},
&oauth2.CortezaTokenStore{Store: s},
)
oauth2Server := oauth2.NewServer(oauth2Manager)
oauth2Server := oauth2.NewServer(oa2m)
// Called after oauth2 authorization request is validated
// We'll try to get valid user out of the session or redirect user to login page

View File

@ -91,9 +91,9 @@ func Command(ctx context.Context, app serviceInitializer, storeInit func(ctx con
Run: func(cmd *cobra.Command, args []string) {
ctx = auth.SetIdentityToContext(ctx, auth.ServiceUser())
var (
at []byte
user *types.User
err error
signedToken []byte
user *types.User
err error
userStr = args[0]
)
@ -104,15 +104,15 @@ func Command(ctx context.Context, app serviceInitializer, storeInit func(ctx con
err = service.DefaultAuth.LoadRoleMemberships(ctx, user)
cli.HandleError(err)
at, err = auth.DefaultJwtHandler.Generate(ctx, user, 0)
signedToken, err = auth.JWT().Generate(ctx, user, 0, "api", "profile")
cli.HandleError(err)
cmd.Println(string(at))
cmd.Println(string(signedToken))
},
}
testEmails := &cobra.Command{
Use: "test-notifications [recipient]",
Short: "Sends samples of all authentication notification to receipient",
Short: "Sends samples of all authentication notification to recipient",
Args: cobra.ExactArgs(1),
PreRunE: commandPreRunInitService(app),
Run: func(cmd *cobra.Command, args []string) {

View File

@ -49,8 +49,7 @@ func (h AuthHandlers) oauth2Authorize(req *request.AuthReq) (err error) {
return err
}
// add client to context so we can reach it from client store via context.Value() fn
//
// add client to context, now we can reach it from client store via context.Value() fn
// this way we work around the limitations we have with the oauth2 lib.
ctx = context.WithValue(req.Context(), &oauth2.ContextClientStore{}, client)
@ -60,7 +59,7 @@ func (h AuthHandlers) oauth2Authorize(req *request.AuthReq) (err error) {
request.SetOauth2Client(req.Session, client)
}
// set to -1 to make sure that wrapping request handler
// set to -1 to make sure wrapping request handler
// does not send status code!
req.Status = -1

View File

@ -3,8 +3,8 @@ package handlers
import (
"net/http"
"github.com/cortezaproject/corteza-server/auth/request"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/locale"
"github.com/go-chi/chi/v5"
"github.com/go-chi/httprate"
@ -36,7 +36,7 @@ func (h *AuthHandlers) MountHttpRoutes(r chi.Router) {
r.Use(httprate.LimitByIP(h.Opt.RequestRateLimit, h.Opt.RequestRateWindowLength))
}
r.Use(request.ExtraReqInfoMiddleware)
r.Use(auth.ExtraReqInfoMiddleware)
r.Group(func(r chi.Router) {
// all routes protected with CSRF:

View File

@ -0,0 +1,62 @@
package oauth2
//import (
// "context"
// "strings"
//
// "github.com/cortezaproject/corteza-server/pkg/auth"
// "github.com/cortezaproject/corteza-server/pkg/payload"
// "github.com/cortezaproject/corteza-server/pkg/rand"
// "github.com/go-oauth2/oauth2/v4"
// "github.com/spf13/cast"
//)
//
//// JWTAccessGenerate generate the jwt access token
//type (
// tokenGenerator interface {
// Generate(ctx context.Context, i auth.Identifiable, clientID uint64, scope ...string) (token []byte, err error)
// }
//
// JWTAccessGenerate struct {
// tm tokenGenerator
// }
//)
//
//// NewJWTAccessGenerate create to generate the jwt access token instance
////
//// @todo move this to pkg/auth (??) so it can be re-used
//func NewJWTAccessGenerate(tg tokenGenerator) *JWTAccessGenerate {
// return &JWTAccessGenerate{tg}
//}
//
//// Token based on the UUID generated token
//func (a *JWTAccessGenerate) Token(ctx context.Context, data *oauth2.GenerateBasic, isGenRefresh bool) (_ string, refresh string, err error) {
// var (
// user auth.Identifiable
// rawToken []byte
// )
//
// {
// // extract user ID and roles from a space-delimited list of IDs stored in userID
// userIdWithRoles := strings.Split(data.TokenInfo.GetUserID(), " ")
// if len(userIdWithRoles) == 1 {
// user = auth.Authenticated(cast.ToUint64(userIdWithRoles[0]))
// } else {
// user = auth.Authenticated(
// cast.ToUint64(userIdWithRoles[0]),
// payload.ParseUint64s(userIdWithRoles)...,
// )
// }
// }
//
// rawToken, err = a.tm.Generate(ctx, user, cast.ToUint64(data.Client.GetID()), data.TokenInfo.GetScope())
// if err != nil {
// return
// }
//
// if isGenRefresh {
// refresh = string(rand.Bytes(48))
// }
//
// return string(rawToken), refresh, nil
//}

View File

@ -19,7 +19,8 @@ var _ oauth2.ClientStore = &ContextClientStore{}
//
// This requires that client is put in context before oauth2 procedures are executed!
func (s ContextClientStore) GetByID(ctx context.Context, id string) (oauth2.ClientInfo, error) {
return &clientInfo{ctx.Value(&ContextClientStore{}).(*types.AuthClient)}, nil
return &clientInfo{&types.AuthClient{}}, nil
//return &clientInfo{ctx.Value(&ContextClientStore{}).(*types.AuthClient)}, nil
}
type (

View File

@ -7,7 +7,6 @@ import (
"strconv"
"time"
"github.com/cortezaproject/corteza-server/auth/request"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/id"
@ -44,23 +43,36 @@ var (
func (c CortezaTokenStore) Create(ctx context.Context, info oauth2.TokenInfo) (err error) {
var (
eti = request.GetExtraReqInfoFromContext(ctx)
oa2t = &types.AuthOa2token{
ID: nextID(),
CreatedAt: *now(),
RemoteAddr: eti.RemoteAddr,
UserAgent: eti.UserAgent,
}
oa2t *types.AuthOa2token
acc *types.AuthConfirmedClient
acc = &types.AuthConfirmedClient{
ConfirmedAt: oa2t.CreatedAt,
}
userID uint64
clientID uint64
jwtID = id.Next()
)
if clientID, err = strconv.ParseUint(info.GetClientID(), 10, 64); err != nil {
return fmt.Errorf("could not parse client ID from token info: %w", err)
}
if userID, _ = auth.ExtractFromSubClaim(info.GetUserID()); userID == 0 {
return fmt.Errorf("could not parse user ID from token info")
}
// Make oauth2 token and auth confirmation structs from user and client IDs
if oa2t, acc, err = makeAuthStructs(ctx, jwtID, userID, clientID, info, info.GetCodeExpiresIn()); err != nil {
return
}
// this is oauth2 specific go-code and there is no
// need for it to be moved to MakeAuthStructs fn in auth pkg
if code := info.GetCode(); code != "" {
oa2t.Code = code
oa2t.ExpiresAt = info.GetCodeCreateAt().Add(info.GetCodeExpiresIn())
} else {
// When creating non-access-code tokens,
// we need to overwrite expiration time
// with custom values for access or refresh token
oa2t.Access = info.GetAccess()
oa2t.ExpiresAt = info.GetAccessCreateAt().Add(info.GetAccessExpiresIn())
@ -70,32 +82,28 @@ func (c CortezaTokenStore) Create(ctx context.Context, info oauth2.TokenInfo) (e
}
}
if oa2t.Data, err = json.Marshal(info); err != nil {
return
}
if oa2t.ClientID, err = strconv.ParseUint(info.GetClientID(), 10, 64); err != nil {
return fmt.Errorf("could not parse client ID from token info: %w", err)
}
// copy client id to auth client confirmation
acc.ClientID = oa2t.ClientID
if info.GetUserID() != "" {
if oa2t.UserID, _ = auth.ExtractFromSubClaim(info.GetUserID()); 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 info")
}
}
// copy user id to auth client confirmation
acc.UserID = oa2t.UserID
//if oa2t.ClientID, err = strconv.ParseUint(info.GetClientID(), 10, 64); err != nil {
// return fmt.Errorf("could not parse client ID from token info: %w", err)
//}
//if info.GetUserID() != "" {
// if oa2t.UserID, _ = auth.ExtractFromSubClaim(info.GetUserID()); 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 info")
// }
//}
//
//// copy user id to auth client confirmation
//acc.UserID = oa2t.UserID
if err = store.UpsertAuthConfirmedClient(ctx, c.Store, acc); err != nil {
return
}
return store.CreateAuthOa2token(ctx, c.Store, oa2t)
if err = store.CreateAuthOa2token(ctx, c.Store, oa2t); err != nil {
return
}
return nil
}
func (c CortezaTokenStore) RemoveByCode(ctx context.Context, code string) error {
@ -115,8 +123,8 @@ func (c CortezaTokenStore) GetByCode(ctx context.Context, code string) (oauth2.T
internal = &oauth2models.Token{}
t, err = store.LookupAuthOa2tokenByCode(ctx, c.Store, code)
)
if err != nil {
if err != nil {
if errors.IsNotFound(err) {
return nil, oauth2errors.ErrInvalidAuthorizeCode
}
@ -132,6 +140,7 @@ func (c CortezaTokenStore) GetByAccess(ctx context.Context, access string) (oaut
internal = &oauth2models.Token{}
t, err = store.LookupAuthOa2tokenByAccess(ctx, c.Store, access)
)
if err != nil {
return nil, fmt.Errorf("failed to get access token: %w", err)
}
@ -144,6 +153,7 @@ func (c CortezaTokenStore) GetByRefresh(ctx context.Context, refresh string) (oa
internal = &oauth2models.Token{}
t, err = store.LookupAuthOa2tokenByRefresh(ctx, c.Store, refresh)
)
if err != nil {
if errors.IsNotFound(err) {
return nil, oauth2errors.ErrInvalidRefreshToken
@ -158,3 +168,32 @@ func (c CortezaTokenStore) GetByRefresh(ctx context.Context, refresh string) (oa
return internal, t.Data.Unmarshal(internal)
}
func makeAuthStructs(ctx context.Context, jwtID, userID, clientID uint64, data oauth2.TokenInfo, expiresAt time.Duration) (oa2t *types.AuthOa2token, acc *types.AuthConfirmedClient, err error) {
var (
eti = auth.GetExtraReqInfoFromContext(ctx)
createdAt = time.Now().Round(time.Second)
)
oa2t = &types.AuthOa2token{
ID: jwtID,
CreatedAt: createdAt,
RemoteAddr: eti.RemoteAddr,
UserAgent: eti.UserAgent,
ClientID: clientID,
UserID: userID,
ExpiresAt: createdAt.Add(expiresAt),
}
acc = &types.AuthConfirmedClient{
ClientID: clientID,
UserID: userID,
ConfirmedAt: createdAt,
}
if oa2t.Data, err = json.Marshal(data); err != nil {
return nil, nil, err
}
return
}

View File

@ -1,58 +0,0 @@
package oauth2
import (
"context"
"strings"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/cortezaproject/corteza-server/pkg/rand"
"github.com/go-oauth2/oauth2/v4"
"github.com/spf13/cast"
)
// JWTAccessGenerate generate the jwt access token
type (
JWTAccessGenerate struct {
tm auth.TokenGenerator
}
)
// NewJWTAccessGenerate create to generate the jwt access token instance
//
// @todo move this to pkg/auth (??) so it can be re-used
func NewJWTAccessGenerate(tg auth.TokenGenerator) *JWTAccessGenerate {
return &JWTAccessGenerate{tg}
}
// Token based on the UUID generated token
func (a *JWTAccessGenerate) Token(_ context.Context, data *oauth2.GenerateBasic, isGenRefresh bool) (_ string, refresh string, err error) {
var (
user auth.Identifiable
rawToken []byte
)
{
// extract user ID and roles from a space-delimited list of IDs stored in userID
userIdWithRoles := strings.Split(data.TokenInfo.GetUserID(), " ")
if len(userIdWithRoles) == 1 {
user = auth.Authenticated(cast.ToUint64(userIdWithRoles[0]))
} else {
user = auth.Authenticated(
cast.ToUint64(userIdWithRoles[0]),
payload.ParseUint64s(userIdWithRoles)...,
)
}
}
rawToken, err = a.tm.Encode(user, cast.ToUint64(data.Client.GetID()), data.TokenInfo.GetScope())
if err != nil {
return
}
if isGenRefresh {
refresh = string(rand.Bytes(48))
}
return string(rawToken), refresh, nil
}

View File

@ -3,7 +3,6 @@ package oauth2
import (
"strings"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/go-oauth2/oauth2/v4"
@ -30,9 +29,6 @@ func NewManager(opt options.AuthOpt, log *zap.Logger, cs oauth2.ClientStore, ts
// token store
manager.MapTokenStorage(ts)
// generate jwt access token
manager.MapAccessGenerate(NewJWTAccessGenerate(auth.DefaultJwtHandler))
manager.MapClientStorage(cs)
manager.SetValidateURIHandler(func(baseURI, redirectURI string) (err error) {
@ -67,7 +63,7 @@ func NewManager(opt options.AuthOpt, log *zap.Logger, cs oauth2.ClientStore, ts
return manager
}
func NewServer(manager *manage.Manager) *server.Server {
func NewServer(manager oauth2.Manager) *server.Server {
srv := server.NewServer(&server.Config{
TokenType: "Bearer",
AllowGetAccessRequest: false,

View File

@ -134,21 +134,3 @@ func (req *AuthReq) PopKV() map[string]string {
req.SetKV(nil)
return val
}
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{}
}
}

View File

@ -5,15 +5,17 @@ import (
"context"
"encoding/gob"
"fmt"
"net/http"
"strings"
"time"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/pkg/rand"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
"net/http"
"strings"
"time"
)
// cortezaSessionStore implements the session store and bridge to corteza store
@ -186,7 +188,7 @@ func (s cortezaSessionStore) save(ctx context.Context, ses *sessions.Session) (e
cortezaSession.UserID = au.User.ID
}
extra := GetExtraReqInfoFromContext(ctx)
extra := auth.GetExtraReqInfoFromContext(ctx)
cortezaSession.UserAgent = extra.UserAgent
cortezaSession.RemoteAddr = extra.RemoteAddr

View File

@ -7,17 +7,19 @@ import (
"github.com/cortezaproject/corteza-server/pkg/auth"
)
func MountRoutes(r chi.Router) {
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(auth.MiddlewareValidOnly)
func MountRoutes(mv auth.MiddlewareValidator) func(r chi.Router) {
return func(r chi.Router) {
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(mv.HttpValidator("api"))
handlers.NewWorkflow(Workflow{}.New()).MountRoutes(r)
handlers.NewTrigger(Trigger{}.New()).MountRoutes(r)
handlers.NewSession(Session{}.New()).MountRoutes(r)
handlers.NewFunction(Function{}.New()).MountRoutes(r)
handlers.NewType(Type{}.New()).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewEventTypes(EventTypes{}.New()).MountRoutes(r)
})
handlers.NewWorkflow(Workflow{}.New()).MountRoutes(r)
handlers.NewTrigger(Trigger{}.New()).MountRoutes(r)
handlers.NewSession(Session{}.New()).MountRoutes(r)
handlers.NewFunction(Function{}.New()).MountRoutes(r)
handlers.NewType(Type{}.New()).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewEventTypes(EventTypes{}.New()).MountRoutes(r)
})
}
}

View File

@ -7,34 +7,37 @@ import (
"github.com/cortezaproject/corteza-server/pkg/auth"
)
func MountRoutes(r chi.Router) {
var (
namespace = Namespace{}.New()
module = Module{}.New()
record = Record{}.New()
page = Page{}.New()
chart = Chart{}.New()
notification = Notification{}.New()
attachment = Attachment{}.New()
automation = Automation{}.New()
)
func MountRoutes(mv auth.MiddlewareValidator) func(r chi.Router) {
return func(r chi.Router) {
var (
namespace = Namespace{}.New()
module = Module{}.New()
record = Record{}.New()
page = Page{}.New()
chart = Chart{}.New()
notification = Notification{}.New()
attachment = Attachment{}.New()
automation = Automation{}.New()
)
// Initialize handlers & controllers.
r.Group(func(r chi.Router) {
// Use alternative handlers that support file serving
handlers.NewAttachment(attachment).MountRoutes(r)
})
// Initialize handlers & controllers.
r.Group(func(r chi.Router) {
// Use alternative handlers that support file serving
handlers.NewAttachment(attachment).MountRoutes(r)
})
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(auth.MiddlewareValidOnly)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewNamespace(namespace).MountRoutes(r)
handlers.NewPage(page).MountRoutes(r)
handlers.NewAutomation(automation).MountRoutes(r)
handlers.NewModule(module).MountRoutes(r)
handlers.NewRecord(record).MountRoutes(r)
handlers.NewChart(chart).MountRoutes(r)
handlers.NewNotification(notification).MountRoutes(r)
})
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(mv.HttpValidator("api"))
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewNamespace(namespace).MountRoutes(r)
handlers.NewPage(page).MountRoutes(r)
handlers.NewAutomation(automation).MountRoutes(r)
handlers.NewModule(module).MountRoutes(r)
handlers.NewRecord(record).MountRoutes(r)
handlers.NewChart(chart).MountRoutes(r)
handlers.NewNotification(notification).MountRoutes(r)
})
}
}

View File

@ -14,9 +14,8 @@ package types
import (
"fmt"
"strconv"
"github.com/cortezaproject/corteza-server/pkg/locale"
"strconv"
)
type (

View File

@ -57,17 +57,17 @@ func (m Module) LabelResourceID() uint64 {
}
// SetLabel adds new label to label map
func (f *ModuleField) SetLabel(key string, value string) {
if f.Labels == nil {
f.Labels = make(map[string]string)
func (m *ModuleField) SetLabel(key string, value string) {
if m.Labels == nil {
m.Labels = make(map[string]string)
}
f.Labels[key] = value
m.Labels[key] = value
}
// GetLabels adds new label to label map
func (f ModuleField) GetLabels() map[string]string {
return f.Labels
func (m ModuleField) GetLabels() map[string]string {
return m.Labels
}
// GetLabels adds new label to label map
@ -76,8 +76,8 @@ func (ModuleField) LabelResourceKind() string {
}
// GetLabels adds new label to label map
func (f ModuleField) LabelResourceID() uint64 {
return f.ID
func (m ModuleField) LabelResourceID() uint64 {
return m.ID
}
// SetLabel adds new label to label map

View File

@ -7,20 +7,23 @@ import (
"github.com/cortezaproject/corteza-server/pkg/auth"
)
func MountRoutes(r chi.Router) {
r.Group(func(r chi.Router) {
handlers.NewNodeHandshake(NodeHandshake{}.New()).MountRoutes(r)
})
func MountRoutes(mv auth.MiddlewareValidator) func(r chi.Router) {
return func(r chi.Router) {
r.Group(func(r chi.Router) {
handlers.NewNodeHandshake(NodeHandshake{}.New()).MountRoutes(r)
})
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(auth.MiddlewareValidOnly)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(mv.HttpValidator("api"))
handlers.NewNode(Node{}.New()).MountRoutes(r)
handlers.NewManageStructure((ManageStructure{}.New())).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewSyncData((SyncData{}.New())).MountRoutes(r)
handlers.NewSyncStructure((SyncStructure{}.New())).MountRoutes(r)
})
handlers.NewNode(Node{}.New()).MountRoutes(r)
handlers.NewManageStructure((ManageStructure{}.New())).MountRoutes(r)
handlers.NewSyncData((SyncData{}.New())).MountRoutes(r)
handlers.NewSyncStructure((SyncStructure{}.New())).MountRoutes(r)
})
}
}

View File

@ -24,13 +24,17 @@ const (
)
type (
tokenGenerator interface {
Generate(ctx context.Context, i auth.Identifiable, clientID uint64, scope ...string) (token []byte, err error)
}
node struct {
store store.Storer
sysUser service.UserService
actionlog actionlog.Recorder
tokenEncoder auth.TokenGenerator
tokenEncoder tokenGenerator
name string
host string
@ -56,7 +60,7 @@ type (
}
)
func Node(s store.Storer, u service.UserService, al actionlog.Recorder, th auth.TokenHandler, options options.FederationOpt, ac nodeAccessController) *node {
func Node(s store.Storer, u service.UserService, al actionlog.Recorder, th tokenGenerator, options options.FederationOpt, ac nodeAccessController) *node {
return &node{
store: s,
sysUser: u,

View File

@ -86,7 +86,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config)
DefaultAccessControl = AccessControl()
DefaultNode = Node(DefaultStore, service.DefaultUser, DefaultActionlog, auth.DefaultJwtHandler, c.Federation, DefaultAccessControl)
DefaultNode = Node(DefaultStore, service.DefaultUser, DefaultActionlog, auth.JWT(), c.Federation, DefaultAccessControl)
DefaultNodeSync = NodeSync()
DefaultExposedModule = ExposedModule()
DefaultSharedModule = SharedModule()

16
go.mod
View File

@ -18,18 +18,18 @@ require (
github.com/PuerkitoBio/goquery v1.5.1 // indirect
github.com/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/brianvoe/gofakeit/v6 v6.11.1
github.com/cortezaproject/corteza-locale v0.0.0-20220103124542-5d327f93f42c
github.com/brianvoe/gofakeit/v6 v6.12.1
github.com/cortezaproject/corteza-locale v0.0.0-20220111135803-4fb0db6196bc
github.com/crewjam/saml v0.4.6
github.com/crusttech/go-oidc v0.0.0-20180918092017-982855dad3e1
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set v1.7.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3
github.com/disintegration/imaging v1.6.2
github.com/dop251/goja v0.0.0-20220102113305-2298ace6d09d
github.com/dop251/goja v0.0.0-20220110113543-261677941f3c
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f
github.com/evanw/esbuild v0.14.10
github.com/evanw/esbuild v0.14.11
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
github.com/fsnotify/fsnotify v1.5.1
github.com/gabriel-vasile/mimetype v1.4.0
@ -56,7 +56,7 @@ require (
github.com/jmoiron/sqlx v1.3.4
github.com/joho/godotenv v1.4.0
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0
github.com/lestrrat-go/jwx v1.2.14
github.com/lestrrat-go/jwx v1.2.15
github.com/lestrrat-go/strftime v1.0.5
github.com/lib/pq v1.10.4
github.com/markbates/goth v1.68.0
@ -71,7 +71,7 @@ require (
github.com/prometheus/tsdb v0.7.1 // indirect
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
github.com/sony/sonyflake v1.0.0
github.com/spf13/afero v1.7.1
github.com/spf13/afero v1.8.0
github.com/spf13/cast v1.4.1
github.com/spf13/cobra v1.3.0
github.com/steinfletcher/apitest v1.5.11
@ -79,7 +79,7 @@ require (
github.com/stretchr/testify v1.7.0
github.com/tebeka/strftime v0.1.5 // indirect
go.uber.org/atomic v1.9.0
go.uber.org/zap v1.19.1
go.uber.org/zap v1.20.0
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/text v0.3.7

17
go.sum
View File

@ -125,6 +125,8 @@ github.com/brianvoe/gofakeit/v6 v6.5.0 h1:zoWqGsuB8TB4MSwUZXtV3OwUSdzi8EHeXO8JfR
github.com/brianvoe/gofakeit/v6 v6.5.0/go.mod h1:palrJUk4Fyw38zIFB/uBZqsgzW5VsNllhHKKwAebzew=
github.com/brianvoe/gofakeit/v6 v6.11.1 h1:Srilo77ZZfDRwqyJ7sYFBtyhrOdC57NG69s6sVUHwmE=
github.com/brianvoe/gofakeit/v6 v6.11.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/brianvoe/gofakeit/v6 v6.12.1 h1:12JSuDkqX/eUiqnNcwetTrbHMdxdLIBx1pBEZNlCp98=
github.com/brianvoe/gofakeit/v6 v6.12.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@ -165,6 +167,8 @@ github.com/cortezaproject/corteza-locale v0.0.0-20211210121242-931011c1250c h1:z
github.com/cortezaproject/corteza-locale v0.0.0-20211210121242-931011c1250c/go.mod h1:wsI1UftEdBqTuEDKBZmx2LfNu/kZun5pRbCAi420JCg=
github.com/cortezaproject/corteza-locale v0.0.0-20220103124542-5d327f93f42c h1:VV5Z9ZiULjNoNltJ0ho7Du6svKovDQ92wkxbkdB2gmg=
github.com/cortezaproject/corteza-locale v0.0.0-20220103124542-5d327f93f42c/go.mod h1:wsI1UftEdBqTuEDKBZmx2LfNu/kZun5pRbCAi420JCg=
github.com/cortezaproject/corteza-locale v0.0.0-20220111135803-4fb0db6196bc h1:d4xI2vuPSA/U+y1JJQKC9nwk2FSnoYansUquX6IPj5E=
github.com/cortezaproject/corteza-locale v0.0.0-20220111135803-4fb0db6196bc/go.mod h1:wsI1UftEdBqTuEDKBZmx2LfNu/kZun5pRbCAi420JCg=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -205,6 +209,8 @@ github.com/dop251/goja v0.0.0-20210726224656-a55e4cfac4cf h1:eK64KqjIBLpCtzIbzci
github.com/dop251/goja v0.0.0-20210726224656-a55e4cfac4cf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20220102113305-2298ace6d09d h1:RHB3jZIxEzQHPzoGtvn47BMbD7jzTfHAXpVC3v4aVI8=
github.com/dop251/goja v0.0.0-20220102113305-2298ace6d09d/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20220110113543-261677941f3c h1:1XnAlcjYBdO7xsa2rhNB/BTztiu4cFKOxE+3brXVtG4=
github.com/dop251/goja v0.0.0-20220110113543-261677941f3c/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b h1:6CBzNasH8+bKeFwr5Bt5JtALHLFN4iQp7sf4ShlP/ik=
@ -228,6 +234,8 @@ github.com/evanw/esbuild v0.12.16 h1:UxvizOzRZk0gnlal2g2MulpCjIiAPtciLr674nOKtcI
github.com/evanw/esbuild v0.12.16/go.mod h1:y2AFBAGVelPqPodpdtxWWqe6n2jYf5FrsJbligmRmuw=
github.com/evanw/esbuild v0.14.10 h1:+7c1VNndl7uLLxVEeRH4rOUz0Y+nrSw8xfmE9rGtrtw=
github.com/evanw/esbuild v0.14.10/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
github.com/evanw/esbuild v0.14.11 h1:bw50N4v70Dqf/B6Wn+3BM6BVttz4A6tHn8m8Ydj9vxk=
github.com/evanw/esbuild v0.14.11/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
@ -305,6 +313,8 @@ github.com/goccy/go-json v0.7.10 h1:ulhbuNe1JqE68nMRXXTJRrUu0uhouf0VevLINxQq4Ec=
github.com/goccy/go-json v0.7.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.0 h1:2flW7bkbrRgU8VuDi0WXDqTmPimjv1thfxkPe8sug+8=
github.com/goccy/go-json v0.9.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@ -555,6 +565,8 @@ github.com/lestrrat-go/jwx v1.2.11 h1:e9BS5NQ003hxXogNsgf5fEWf01ZJvj4Aj1qy7Dykqm
github.com/lestrrat-go/jwx v1.2.11/go.mod h1:25DcLbNWArPA/Ew5CcBmewl32cJKxOk5cbepBsIJFzw=
github.com/lestrrat-go/jwx v1.2.14 h1:69OeaiFKCTn8xDmBGzHTgv/GBoO1LJcXw99GfYCDKzg=
github.com/lestrrat-go/jwx v1.2.14/go.mod h1:3Q3Re8TaOcVTdpx4Tvz++OWmryDklihTDqrrwQiyS2A=
github.com/lestrrat-go/jwx v1.2.15 h1:58CEGJpf1TS3NJASMfMkTp6stlvPTsqs1xxAu/Yf/uM=
github.com/lestrrat-go/jwx v1.2.15/go.mod h1:DJKaoM8f1OvYVwWoW45gBrUxMlpD4FHjT0UnrW3iX28=
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
@ -760,6 +772,8 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.7.1 h1:F37zV8E8RLstLpZ0RUGK2NGg1X57y6/B0Eg6S8oqdoA=
github.com/spf13/afero v1.7.1/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60=
github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
@ -872,6 +886,7 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
@ -882,6 +897,8 @@ go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc=
go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

View File

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

View File

@ -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"),
)
}

31
pkg/auth/extra.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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?")
}

View File

@ -10,12 +10,14 @@ package store
import (
"context"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
AuthOa2tokens interface {
SearchAuthOa2tokens(ctx context.Context, f types.AuthOa2tokenFilter) (types.AuthOa2tokenSet, types.AuthOa2tokenFilter, error)
LookupAuthOa2tokenByID(ctx context.Context, id uint64) (*types.AuthOa2token, error)
LookupAuthOa2tokenByCode(ctx context.Context, code string) (*types.AuthOa2token, error)
LookupAuthOa2tokenByAccess(ctx context.Context, access string) (*types.AuthOa2token, error)
LookupAuthOa2tokenByRefresh(ctx context.Context, refresh string) (*types.AuthOa2token, error)
@ -54,6 +56,11 @@ func SearchAuthOa2tokens(ctx context.Context, s AuthOa2tokens, f types.AuthOa2to
return s.SearchAuthOa2tokens(ctx, f)
}
// LookupAuthOa2tokenByID
func LookupAuthOa2tokenByID(ctx context.Context, s AuthOa2tokens, id uint64) (*types.AuthOa2token, error) {
return s.LookupAuthOa2tokenByID(ctx, id)
}
// LookupAuthOa2tokenByCode
func LookupAuthOa2tokenByCode(ctx context.Context, s AuthOa2tokens, code string) (*types.AuthOa2token, error) {
return s.LookupAuthOa2tokenByCode(ctx, code)

View File

@ -20,6 +20,7 @@ rdbms:
customFilterConverter: true
lookups:
- fields: [ ID ]
- fields: [ Code ]
uniqueConstraintCheck: true
- fields: [ Access ]

View File

@ -11,6 +11,7 @@ package rdbms
import (
"context"
"database/sql"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/store"
@ -85,6 +86,13 @@ func (s Store) QueryAuthOa2tokens(
return set, nil
}
// LookupAuthOa2tokenByID
func (s Store) LookupAuthOa2tokenByID(ctx context.Context, id uint64) (*types.AuthOa2token, error) {
return s.execLookupAuthOa2token(ctx, squirrel.Eq{
s.preprocessColumn("tkn.id", ""): store.PreprocessValue(id, ""),
})
}
// LookupAuthOa2tokenByCode
func (s Store) LookupAuthOa2tokenByCode(ctx context.Context, code string) (*types.AuthOa2token, error) {
return s.execLookupAuthOa2token(ctx, squirrel.Eq{

View File

@ -2,6 +2,7 @@ package commands
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/cli"
"github.com/spf13/cobra"
)

View File

@ -14,8 +14,12 @@ import (
var _ = errors.Wrap
type (
tokenGenerator interface {
Generate(ctx context.Context, i auth.Identifiable, clientID uint64, scope ...string) (token []byte, err error)
}
Auth struct {
tokenHandler auth.TokenGenerator
tokenHandler tokenGenerator
settings *types.AppSettings
authSvc authUserService
}
@ -47,7 +51,7 @@ type (
func (Auth) New() *Auth {
return &Auth{
tokenHandler: auth.DefaultJwtHandler,
tokenHandler: auth.JWT(),
settings: service.CurrentSettings,
authSvc: service.DefaultAuth,
}

View File

@ -8,39 +8,41 @@ import (
"github.com/cortezaproject/corteza-server/system/service"
)
func MountRoutes(r chi.Router) {
r.Group(func(r chi.Router) {
handlers.NewLocale(Locale{}.New()).MountRoutes(r)
func MountRoutes(mv auth.MiddlewareValidator) func(r chi.Router) {
return func(r chi.Router) {
r.Group(func(r chi.Router) {
handlers.NewLocale(Locale{}.New()).MountRoutes(r)
handlers.NewAttachment(Attachment{}.New()).MountRoutes(r)
handlers.NewAuth((Auth{}).New()).MountRoutes(r)
handlers.NewAttachment(Attachment{}.New()).MountRoutes(r)
handlers.NewAuth((Auth{}).New()).MountRoutes(r)
// A special case that, we do not add this through standard request, handlers & controllers
// combo but directly -- we need access to r.Body
r.Handle(service.SinkBaseURL+"*", &Sink{
svc: service.DefaultSink,
sign: auth.DefaultSigner,
// A special case that, we do not add this through standard request, handlers & controllers
// combo but directly -- we need access to r.Body
r.Handle(service.SinkBaseURL+"*", &Sink{
svc: service.DefaultSink,
sign: auth.DefaultSigner,
})
})
})
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(auth.MiddlewareValidOnly)
// Protect all _private_ routes
r.Group(func(r chi.Router) {
r.Use(mv.HttpValidator("api"))
handlers.NewAuthClient(AuthClient{}.New()).MountRoutes(r)
handlers.NewAutomation(Automation{}.New()).MountRoutes(r)
handlers.NewUser(User{}.New()).MountRoutes(r)
handlers.NewRole(Role{}.New()).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewApplication(Application{}.New()).MountRoutes(r)
handlers.NewTemplate(Template{}.New()).MountRoutes(r)
handlers.NewReport(Report{}.New()).MountRoutes(r)
handlers.NewSettings(Settings{}.New()).MountRoutes(r)
handlers.NewStats(Stats{}.New()).MountRoutes(r)
handlers.NewReminder(Reminder{}.New()).MountRoutes(r)
handlers.NewActionlog(Actionlog{}.New()).MountRoutes(r)
handlers.NewQueues(Queue{}.New()).MountRoutes(r)
handlers.NewApigwRoute(ApigwRoute{}.New()).MountRoutes(r)
handlers.NewApigwFilter(ApigwFilter{}.New()).MountRoutes(r)
})
handlers.NewAuthClient(AuthClient{}.New()).MountRoutes(r)
handlers.NewAutomation(Automation{}.New()).MountRoutes(r)
handlers.NewUser(User{}.New()).MountRoutes(r)
handlers.NewRole(Role{}.New()).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewApplication(Application{}.New()).MountRoutes(r)
handlers.NewTemplate(Template{}.New()).MountRoutes(r)
handlers.NewReport(Report{}.New()).MountRoutes(r)
handlers.NewSettings(Settings{}.New()).MountRoutes(r)
handlers.NewStats(Stats{}.New()).MountRoutes(r)
handlers.NewReminder(Reminder{}.New()).MountRoutes(r)
handlers.NewActionlog(Actionlog{}.New()).MountRoutes(r)
handlers.NewQueues(Queue{}.New()).MountRoutes(r)
handlers.NewApigwRoute(ApigwRoute{}.New()).MountRoutes(r)
handlers.NewApigwFilter(ApigwFilter{}.New()).MountRoutes(r)
})
}
}

View File

@ -1,8 +1,9 @@
package types
import (
sqlxTypes "github.com/jmoiron/sqlx/types"
"time"
sqlxTypes "github.com/jmoiron/sqlx/types"
)
type (

View File

@ -79,7 +79,7 @@ func InitTestApp() {
helpers.BindAuthMiddleware(r)
// Sys routes for route management tests
rest.MountRoutes(r)
r.Group(rest.MountRoutes(auth.JWT()))
// API gw routes
apigw.Setup(options.Apigw(), service.DefaultLogger, service.DefaultStore)
@ -111,7 +111,7 @@ func newHelper(t *testing.T) helper {
helpers.UpdateRBAC(h.roleID)
var err error
h.token, err = auth.DefaultJwtHandler.Generate(context.Background(), h.cUser, 0)
h.token, err = auth.JWT().Generate(context.Background(), h.cUser, 0)
if err != nil {
panic(err)
}

View File

@ -84,7 +84,7 @@ func InitTestApp() {
r = chi.NewRouter()
r.Use(server.BaseMiddleware(false, logger.Default())...)
helpers.BindAuthMiddleware(r)
rest.MountRoutes(r)
r.Group(rest.MountRoutes(auth.JWT()))
}
}
@ -107,7 +107,7 @@ func newHelper(t *testing.T) helper {
helpers.UpdateRBAC(h.roleID)
var err error
h.token, err = auth.DefaultJwtHandler.Generate(context.Background(), h.cUser, 0)
h.token, err = auth.JWT().Generate(context.Background(), h.cUser, 0)
if err != nil {
panic(err)
}

View File

@ -95,7 +95,7 @@ func InitTestApp() {
r = chi.NewRouter()
r.Use(server.BaseMiddleware(false, logger.Default())...)
helpers.BindAuthMiddleware(r)
rest.MountRoutes(r)
r.Group(rest.MountRoutes(auth.JWT()))
}
}
@ -124,7 +124,7 @@ func newHelper(t *testing.T) helper {
func (h *helper) identityToHelper(u *sysTypes.User) {
var err error
h.cUser = u
h.token, err = auth.DefaultJwtHandler.Generate(context.Background(), u, 0)
h.token, err = auth.JWT().Generate(context.Background(), u, 0)
if err != nil {
panic(err)
}

View File

@ -64,7 +64,7 @@ func InitTestApp() {
r = chi.NewRouter()
r.Use(server.BaseMiddleware(false, logger.Default())...)
helpers.BindAuthMiddleware(r)
rest.MountRoutes(r)
r.Group(rest.MountRoutes(auth.JWT()))
}
}
@ -87,7 +87,7 @@ func newHelper(t *testing.T) helper {
helpers.UpdateRBAC(h.roleID)
var err error
h.token, err = auth.DefaultJwtHandler.Generate(context.Background(), h.cUser, 0)
h.token, err = auth.JWT().Generate(context.Background(), h.cUser, 0)
if err != nil {
panic(err)
}

View File

@ -11,8 +11,8 @@ import (
func BindAuthMiddleware(r chi.Router) {
r.Use(
auth.DefaultJwtHandler.HttpVerifier(),
auth.DefaultJwtHandler.HttpAuthenticator(),
auth.JWT().HttpVerifier(),
auth.JWT().HttpValidator("api"),
)
}

View File

@ -102,7 +102,7 @@ func InitTestApp() {
r = chi.NewRouter()
r.Use(server.BaseMiddleware(false, logger.Default())...)
helpers.BindAuthMiddleware(r)
rest.MountRoutes(r)
r.Group(rest.MountRoutes(auth.JWT()))
}
}
@ -125,7 +125,7 @@ func newHelper(t *testing.T) helper {
helpers.UpdateRBAC(h.roleID)
var err error
h.token, err = auth.DefaultJwtHandler.Generate(context.Background(), h.cUser, 0)
h.token, err = auth.JWT().Generate(context.Background(), h.cUser, 0)
if err != nil {
panic(err)
}

View File

@ -116,7 +116,7 @@ func InitTestApp() {
r.Use(server.BaseMiddleware(false, logger.Default())...)
helpers.BindAuthMiddleware(r)
rest.MountRoutes(r)
r.Group(rest.MountRoutes(auth.JWT()))
hh.MountHttpRoutes(r)
}
}
@ -142,7 +142,7 @@ func newHelper(t *testing.T) helper {
h.mockPermissionsWithAccess()
var err error
h.token, err = auth.DefaultJwtHandler.Generate(context.Background(), h.cUser, 0)
h.token, err = auth.JWT().Generate(context.Background(), h.cUser, 0)
if err != nil {
panic(err)
}

View File

@ -1 +1 @@
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODQiLCJleHAiOjE2MjEyNDMwODIsImlhdCI6MTYyMTI0Mjk5MiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg0IiwibmJmIjoxNjIxMjQyOTkyLCJzdWIiOiJ0Q3U1UFY2RWd4Y3ZVQWE5ZTU3dUoyZy1iVGtxbk5reXlISGFPdTE1eUVmWmpnV0t0MDJBdFhHZSIsImlkIjoiaWQtN2JmZmIyNmQ2YmVlNGI1YmRmODZmOGRjNGRhMmNmYzExN2Q4OWQwZiIsInVyaSI6Ii9hdXRoL2V4dGVybmFsL3NhbWwvaW5pdCIsInNhbWwtYXV0aG4tcmVxdWVzdCI6dHJ1ZX0.CU_jrc5gx6JhafzbekO-7VXLJU6jzDd-R4QyrtQIZN3jyqIZtYB466KiTFZnyYeEWEjK7GW18eHuzZFHmDpcQ9weOtvu9u0Z7UUDm3YQoG-6XgUeQKTV2i1uPzq1ZlT8iiMBUsn0kdKL2F18U4jl4Fss0_Ysdc3OqoEJ73xcu0P721ZSsg-vEwyooe1WMSosunN_HEmWOU2aC61uQwNFSRk5_JotdUEytko1Jzn9JeOnLllf8izr7z7JWEnBqN8845IV_zqiScrAptpAZRocAeMAbFPFPmj5_lL2SzKC4GF4lkoOIZ5vWRBVdfzIxvD21rVZK5rRlSdYUmT0txFoQw
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODQiLCJpYXQiOjE2MjEyNDI5OTIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4NCIsIm5iZiI6MTYyMTI0Mjk5Miwic3ViIjoidEN1NVBWNkVneGN2VUFhOWU1N3VKMmctYlRrcW5Oa3l5SEhhT3UxNXlFZlpqZ1dLdDAyQXRYR2UiLCJpZCI6ImlkLTdiZmZiMjZkNmJlZTRiNWJkZjg2ZjhkYzRkYTJjZmMxMTdkODlkMGYiLCJ1cmkiOiIvYXV0aC9leHRlcm5hbC9zYW1sL2luaXQiLCJzYW1sLWF1dGhuLXJlcXVlc3QiOnRydWV9.fBI_TorEbXYMtoNRGApQs5_89Q9IZjV-1dwkOeF5ZC9xQ6p3Mbo3r0x4CgKzYS2n8i4mMIEUDI_C4bY5jqyVEfmrwtv4qGbhYCJjnvSu1vAncJGQNfbcWCmSW0RMiiJZzfj3whHTzmK_mLgOch07iwxKGBOyNscdZfxfJp_sDMuHePwiggGssWglCC_KWXNkGh3TPad-_mo4kc_9qUf4onyISms6uyZpbJW-BqGP-iYiTnbEGdbF-24bmbTpVBU8Arv3jQjaHq8teT9XI4vFgWHfEp497LD7snYNOn3-9S05JGWKA74wrgZwFcRBaIhfVMtOqy7YMHBJZ8NNbVH8Tg

File diff suppressed because one or more lines are too long