Add tools and update authenticztion and RBAC

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-11-21 10:22:01 +01:00
parent deb656d95e
commit dfd924abeb
16 changed files with 15634 additions and 39441 deletions

View File

@@ -21,7 +21,6 @@ import (
"encoding/json"
"fmt"
"net/http"
"slices"
"strings"
"time"
@@ -44,6 +43,7 @@ import (
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/saferedirect"
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
serverauth "go.probo.inc/probo/pkg/server/auth"
"go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/server/session"
"go.probo.inc/probo/pkg/statelesstoken"
@@ -69,18 +69,10 @@ type (
}
ctxKey struct{ name string }
userTenantAccess struct {
tenantIDs []gid.TenantID
authErrors map[gid.TenantID]error
}
)
var (
sessionContextKey = &ctxKey{name: "session"}
userContextKey = &ctxKey{name: "user"}
userTenantContextKey = &ctxKey{name: "user_tenants"}
userAPIKeyContextKey = &ctxKey{name: "user_api_key"}
sessionContextKey = &ctxKey{name: "session"}
)
func SessionFromContext(ctx context.Context) *coredata.Session {
@@ -89,13 +81,11 @@ func SessionFromContext(ctx context.Context) *coredata.Session {
}
func UserFromContext(ctx context.Context) *coredata.User {
user, _ := ctx.Value(userContextKey).(*coredata.User)
return user
return serverauth.UserFromContext(ctx)
}
func UserAPIKeyFromContext(ctx context.Context) *coredata.UserAPIKey {
userAPIKey, _ := ctx.Value(userAPIKeyContextKey).(*coredata.UserAPIKey)
return userAPIKey
return serverauth.UserAPIKeyFromContext(ctx)
}
func NewMux(
@@ -377,7 +367,7 @@ func WithSession(authSvc *auth.Service, authzSvc *authz.Service, authCfg AuthCon
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if authCtx := tryAPIKeyAuth(ctx, r, authSvc, authzSvc); authCtx != nil {
if authCtx := serverauth.AuthenticateWithAPIKey(ctx, r, authSvc, authzSvc); authCtx != nil {
next(w, r.WithContext(authCtx))
return
}
@@ -413,10 +403,10 @@ func WithSession(authSvc *auth.Service, authzSvc *authz.Service, authCfg AuthCon
}
ctx = context.WithValue(ctx, sessionContextKey, authResult.Session)
ctx = context.WithValue(ctx, userContextKey, authResult.User)
ctx = context.WithValue(ctx, userTenantContextKey, &userTenantAccess{
tenantIDs: authResult.TenantIDs,
authErrors: authResult.AuthErrors,
ctx = context.WithValue(ctx, serverauth.UserContextKey, authResult.User)
ctx = context.WithValue(ctx, serverauth.UserTenantContextKey, &serverauth.UserTenantAccess{
TenantIDs: authResult.TenantIDs,
AuthErrors: authResult.AuthErrors,
})
next(w, r.WithContext(ctx))
@@ -428,43 +418,6 @@ func WithSession(authSvc *auth.Service, authzSvc *authz.Service, authCfg AuthCon
}
}
func tryAPIKeyAuth(ctx context.Context, r *http.Request, authSvc *auth.Service, authzSvc *authz.Service) context.Context {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return nil
}
if !strings.HasPrefix(authHeader, "Bearer ") {
return nil
}
apiKeyString := strings.TrimPrefix(authHeader, "Bearer ")
user, userAPIKey, err := authSvc.ValidateUserAPIKey(ctx, apiKeyString)
if err != nil {
return nil
}
organizations, err := authzSvc.GetAllOrganizationsForUserAPIKeyId(ctx, userAPIKey.ID)
if err != nil {
return nil
}
tenantIDs := make([]gid.TenantID, 0, len(organizations))
for _, org := range organizations {
tenantIDs = append(tenantIDs, org.ID.TenantID())
}
ctx = context.WithValue(ctx, userContextKey, user)
ctx = context.WithValue(ctx, userAPIKeyContextKey, userAPIKey)
ctx = context.WithValue(ctx, userTenantContextKey, &userTenantAccess{
tenantIDs: tenantIDs,
authErrors: make(map[gid.TenantID]error),
})
return ctx
}
func (r *Resolver) ProboService(ctx context.Context, tenantID gid.TenantID) *probo.TenantService {
return GetTenantService(ctx, r.proboSvc, tenantID)
}
@@ -486,38 +439,20 @@ func UnwrapOmittable[T any](field graphql.Omittable[T]) *T {
}
func GetTenantService(ctx context.Context, proboSvc *probo.Service, tenantID gid.TenantID) *probo.TenantService {
validateTenantAccess(ctx, tenantID)
serverauth.RequireTenantAccess(ctx, tenantID)
return proboSvc.WithTenant(tenantID)
}
func GetTenantAuthzService(ctx context.Context, authzSvc *authz.Service, tenantID gid.TenantID) *authz.TenantAuthzService {
validateTenantAccess(ctx, tenantID)
serverauth.RequireTenantAccess(ctx, tenantID)
return authzSvc.WithTenant(tenantID)
}
func GetTenantAuthService(ctx context.Context, authSvc *auth.Service, tenantID gid.TenantID) *auth.TenantAuthService {
validateTenantAccess(ctx, tenantID)
serverauth.RequireTenantAccess(ctx, tenantID)
return authSvc.WithTenant(tenantID)
}
func validateTenantAccess(ctx context.Context, tenantID gid.TenantID) {
access, _ := ctx.Value(userTenantContextKey).(*userTenantAccess)
if access == nil {
panic(&authz.TenantAccessError{Message: "tenant not found"})
}
if !slices.Contains(access.tenantIDs, tenantID) {
if access.authErrors != nil {
if authErr := access.authErrors[tenantID]; authErr != nil {
panic(authErr)
}
}
panic(&authz.TenantAccessError{Message: "tenant not found"})
}
}
func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, action authz.Action) {
user := UserFromContext(ctx)
apiKey := UserAPIKeyFromContext(ctx)

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ package console_v1
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.76
// Code generated by github.com/99designs/gqlgen version v0.17.83
import (
"context"
@@ -23,6 +23,7 @@ import (
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
"go.probo.inc/probo/pkg/server/api/console/v1/types"
serverauth "go.probo.inc/probo/pkg/server/auth"
"go.probo.inc/probo/pkg/server/gqlutils"
)
@@ -1451,9 +1452,9 @@ func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.C
}
// Append tenant to allowed one
access, _ := ctx.Value(userTenantContextKey).(*userTenantAccess)
access := serverauth.UserTenantAccessFromContext(ctx)
if access != nil {
access.tenantIDs = append(access.tenantIDs, organization.ID.TenantID())
access.TenantIDs = append(access.TenantIDs, organization.ID.TenantID())
}
return &types.CreateOrganizationPayload{