From 4ff71312f9674a6f7b5f2399b7d9f1d6e145bd4f Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Mon, 4 Aug 2025 20:45:21 +0200 Subject: [PATCH] Split Config Signed-off-by: Sacha Al Himdani --- pkg/probo/service.go | 12 ++++ pkg/probo/trust_center_access_service.go | 10 +-- pkg/probod/auth_config.go | 29 +++++++++ pkg/probod/probod.go | 36 ++++++++++- pkg/server/api/api.go | 26 +++++++- pkg/server/api/trust/v1/resolver.go | 62 ++++++++++--------- .../trust/v1/trust_center_access_handler.go | 32 +++++----- pkg/server/server.go | 4 +- 8 files changed, 154 insertions(+), 57 deletions(-) diff --git a/pkg/probo/service.go b/pkg/probo/service.go index 1f065cd89..535142069 100644 --- a/pkg/probo/service.go +++ b/pkg/probo/service.go @@ -17,6 +17,7 @@ package probo import ( "context" "fmt" + "time" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/getprobo/probo/pkg/agents" @@ -30,6 +31,12 @@ import ( ) type ( + TrustConfig struct { + TokenSecret string + TokenDuration time.Duration + TokenType string + } + Service struct { pg *pg.Client s3 *s3.Client @@ -37,6 +44,7 @@ type ( encryptionKey cipher.EncryptionKey hostname string tokenSecret string + trustConfig TrustConfig agentConfig agents.Config html2pdfConverter *html2pdf.Converter usrmgr *usrmgr.Service @@ -50,6 +58,7 @@ type ( scope coredata.Scoper hostname string tokenSecret string + trustConfig TrustConfig agent *agents.Agent Frameworks *FrameworkService Measures *MeasureService @@ -80,6 +89,7 @@ func NewService( bucket string, hostname string, tokenSecret string, + trustConfig TrustConfig, agentConfig agents.Config, html2pdfConverter *html2pdf.Converter, usrmgrService *usrmgr.Service, @@ -95,6 +105,7 @@ func NewService( encryptionKey: encryptionKey, hostname: hostname, tokenSecret: tokenSecret, + trustConfig: trustConfig, agentConfig: agentConfig, html2pdfConverter: html2pdfConverter, usrmgr: usrmgrService, @@ -112,6 +123,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService { hostname: s.hostname, scope: coredata.NewScope(tenantID), tokenSecret: s.tokenSecret, + trustConfig: s.trustConfig, agent: agents.NewAgent(nil, s.agentConfig), } diff --git a/pkg/probo/trust_center_access_service.go b/pkg/probo/trust_center_access_service.go index ca1b27667..d0ccc4424 100644 --- a/pkg/probo/trust_center_access_service.go +++ b/pkg/probo/trust_center_access_service.go @@ -78,8 +78,8 @@ func (s TrustCenterAccessService) ValidateToken( tokenString string, ) (*TrustCenterAccessData, error) { token, err := statelesstoken.ValidateToken[TrustCenterAccessData]( - s.svc.tokenSecret, - TokenTypeTrustCenterAccess, + s.svc.trustConfig.TokenSecret, + s.svc.trustConfig.TokenType, tokenString, ) if err != nil { @@ -176,9 +176,9 @@ func (s TrustCenterAccessService) Delete( func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, access *coredata.TrustCenterAccess) error { accessToken, err := statelesstoken.NewToken( - s.svc.tokenSecret, - TokenTypeTrustCenterAccess, - 7*24*time.Hour, + s.svc.trustConfig.TokenSecret, + s.svc.trustConfig.TokenType, + s.svc.trustConfig.TokenDuration, TrustCenterAccessData{ TrustCenterID: access.TrustCenterID, Email: access.Email, diff --git a/pkg/probod/auth_config.go b/pkg/probod/auth_config.go index b00b8b1f3..870d4e2ec 100644 --- a/pkg/probod/auth_config.go +++ b/pkg/probod/auth_config.go @@ -26,6 +26,16 @@ type ( DisableSignup bool `json:"disable-signup"` } + trustAuthConfig struct { + CookieName string `json:"cookie-name"` + CookieDomain string `json:"cookie-domain"` + CookieDuration int `json:"cookie-duration"` + TokenDuration int `json:"token-duration"` + TokenSecret string `json:"token-secret"` + Scope string `json:"scope"` + TokenType string `json:"token-type"` + } + cookieConfig struct { Domain string `json:"domain"` Secret string `json:"secret"` @@ -76,3 +86,22 @@ func (c authConfig) GetCookieSecretBytes() ([]byte, error) { return []byte(c.Cookie.Secret), nil } + +func (c trustAuthConfig) GetTokenSecretBytes() ([]byte, error) { + if c.TokenSecret == "" { + return nil, fmt.Errorf("token secret cannot be empty") + } + + if decoded, err := base64.StdEncoding.DecodeString(c.TokenSecret); err == nil { + if len(decoded) < 32 { + return nil, fmt.Errorf("decoded token secret must be at least 32 bytes long") + } + return decoded, nil + } + + if len(c.TokenSecret) < 32 { + return nil, fmt.Errorf("token secret must be at least 32 bytes long") + } + + return []byte(c.TokenSecret), nil +} diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index 01ca31afd..667b25b97 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -59,6 +59,7 @@ type ( Pg pgConfig `json:"pg"` Api apiConfig `json:"api"` Auth authConfig `json:"auth"` + TrustAuth trustAuthConfig `json:"trust-auth"` AWS awsConfig `json:"aws"` Mailer mailerConfig `json:"mailer"` Connectors []connectorConfig `json:"connectors"` @@ -100,6 +101,15 @@ func New() *Implm { }, DisableSignup: false, }, + TrustAuth: trustAuthConfig{ + CookieName: "TCT", + CookieDomain: "localhost", + CookieDuration: 24, + TokenDuration: 168, + TokenSecret: "this-is-a-secure-secret-for-trust-token-signing-at-least-32-bytes", + Scope: "trust_center_readonly", + TokenType: "trust_center_access", + }, AWS: awsConfig{ Region: "us-east-1", Bucket: "probod", @@ -157,6 +167,12 @@ func (impl *Implm) Run( return fmt.Errorf("cannot get cookie secret bytes: %w", err) } + _, err = impl.cfg.TrustAuth.GetTokenSecretBytes() + if err != nil { + rootSpan.RecordError(err) + return fmt.Errorf("cannot get trust auth token secret bytes: %w", err) + } + awsConfig := awsconfig.NewConfig( l, httpclient.DefaultPooledClient( @@ -203,6 +219,12 @@ func (impl *Implm) Run( ModelName: impl.cfg.OpenAI.ModelName, } + trustConfig := probo.TrustConfig{ + TokenSecret: impl.cfg.TrustAuth.TokenSecret, + TokenDuration: time.Duration(impl.cfg.TrustAuth.TokenDuration) * time.Hour, + TokenType: impl.cfg.TrustAuth.TokenType, + } + agent := agents.NewAgent(l.Named("agent"), agentConfig) usrmgrService, err := usrmgr.NewService( @@ -225,6 +247,7 @@ func (impl *Implm) Run( impl.cfg.AWS.Bucket, impl.cfg.Hostname, impl.cfg.Auth.Cookie.Secret, + trustConfig, agentConfig, html2pdfConverter, usrmgrService, @@ -238,7 +261,7 @@ func (impl *Implm) Run( s3Client, impl.cfg.AWS.Bucket, impl.cfg.EncryptionKey, - impl.cfg.Auth.Cookie.Secret, + impl.cfg.TrustAuth.TokenSecret, usrmgrService, html2pdfConverter, ) @@ -254,12 +277,21 @@ func (impl *Implm) Run( Agent: agent, SafeRedirect: &saferedirect.SafeRedirect{AllowedHost: impl.cfg.Hostname}, Logger: l.Named("http.server"), - Auth: api.AuthConfig{ + Auth: api.ConsoleAuthConfig{ CookieName: impl.cfg.Auth.Cookie.Name, CookieDomain: impl.cfg.Auth.Cookie.Domain, SessionDuration: time.Duration(impl.cfg.Auth.Cookie.Duration) * time.Hour, CookieSecret: impl.cfg.Auth.Cookie.Secret, }, + TrustAuth: api.TrustAuthConfig{ + CookieName: impl.cfg.TrustAuth.CookieName, + CookieDomain: impl.cfg.TrustAuth.CookieDomain, + CookieDuration: time.Duration(impl.cfg.TrustAuth.CookieDuration) * time.Hour, + TokenDuration: time.Duration(impl.cfg.TrustAuth.TokenDuration) * time.Hour, + TokenSecret: impl.cfg.TrustAuth.TokenSecret, + Scope: impl.cfg.TrustAuth.Scope, + TokenType: impl.cfg.TrustAuth.TokenType, + }, }, ) if err != nil { diff --git a/pkg/server/api/api.go b/pkg/server/api/api.go index 6516ad099..e5a6fa9f8 100644 --- a/pkg/server/api/api.go +++ b/pkg/server/api/api.go @@ -34,19 +34,30 @@ import ( ) type ( - AuthConfig struct { + ConsoleAuthConfig struct { CookieName string CookieDomain string SessionDuration time.Duration CookieSecret string } + TrustAuthConfig struct { + CookieName string + CookieDomain string + CookieDuration time.Duration + TokenDuration time.Duration + TokenSecret string + Scope string + TokenType string + } + Config struct { AllowedOrigins []string Probo *probo.Service Usrmgr *usrmgr.Service Trust *trust.Service - Auth AuthConfig + Auth ConsoleAuthConfig + TrustAuth TrustAuthConfig ConnectorRegistry *connector.ConnectorRegistry SafeRedirect *saferedirect.SafeRedirect Logger *log.Logger @@ -152,12 +163,21 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.cfg.Logger.Named("trust.v1"), s.cfg.Usrmgr, s.cfg.Trust, - trust_v1.AuthConfig{ + console_v1.AuthConfig{ CookieName: s.cfg.Auth.CookieName, CookieDomain: s.cfg.Auth.CookieDomain, SessionDuration: s.cfg.Auth.SessionDuration, CookieSecret: s.cfg.Auth.CookieSecret, }, + trust_v1.TrustAuthConfig{ + CookieName: s.cfg.TrustAuth.CookieName, + CookieDomain: s.cfg.TrustAuth.CookieDomain, + CookieDuration: s.cfg.TrustAuth.CookieDuration, + TokenDuration: s.cfg.TrustAuth.TokenDuration, + TokenSecret: s.cfg.TrustAuth.TokenSecret, + Scope: s.cfg.TrustAuth.Scope, + TokenType: s.cfg.TrustAuth.TokenType, + }, ), ) diff --git a/pkg/server/api/trust/v1/resolver.go b/pkg/server/api/trust/v1/resolver.go index 4b90d8b8e..12f596814 100644 --- a/pkg/server/api/trust/v1/resolver.go +++ b/pkg/server/api/trust/v1/resolver.go @@ -31,6 +31,7 @@ import ( "github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/probo" "github.com/getprobo/probo/pkg/securecookie" + console_v1 "github.com/getprobo/probo/pkg/server/api/console/v1" "github.com/getprobo/probo/pkg/server/api/trust/v1/auth" "github.com/getprobo/probo/pkg/server/api/trust/v1/schema" "github.com/getprobo/probo/pkg/statelesstoken" @@ -42,26 +43,25 @@ import ( ) type ( - AuthConfig struct { - CookieName string - CookieDomain string - SessionDuration time.Duration - CookieSecret string + TrustAuthConfig struct { + CookieName string + CookieDomain string + CookieDuration time.Duration + TokenDuration time.Duration + TokenSecret string + Scope string + TokenType string } Resolver struct { trustCenterSvc *trust.Service - authCfg AuthConfig + authCfg console_v1.AuthConfig + trustAuthCfg TrustAuthConfig } ctxKey struct{ name string } ) -const ( - TokenScopeTrustCenterReadOnly = "trust_center_readonly" - TokenCookieName = "trust_center_token" -) - var ( sessionContextKey = &ctxKey{name: "session"} userContextKey = &ctxKey{name: "user"} @@ -98,22 +98,24 @@ func NewMux( logger *log.Logger, usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, - authCfg AuthConfig, + authCfg console_v1.AuthConfig, + trustAuthCfg TrustAuthConfig, ) *chi.Mux { r := chi.NewMux() - r.Handle("/graphql", graphqlHandler(logger, usrmgrSvc, trustSvc, authCfg)) + r.Handle("/graphql", graphqlHandler(logger, usrmgrSvc, trustSvc, authCfg, trustAuthCfg)) - r.Post("/auth/authenticate", authTokenHandler(trustSvc, authCfg)) - r.Delete("/auth/logout", trustCenterLogoutHandler(authCfg)) + r.Post("/auth/authenticate", authTokenHandler(trustSvc, trustAuthCfg)) + r.Delete("/auth/logout", trustCenterLogoutHandler(trustAuthCfg)) return r } -func graphqlHandler(logger *log.Logger, usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg AuthConfig) http.HandlerFunc { +func graphqlHandler(logger *log.Logger, usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg console_v1.AuthConfig, trustAuthCfg TrustAuthConfig) http.HandlerFunc { resolver := &Resolver{ trustCenterSvc: trustSvc, authCfg: authCfg, + trustAuthCfg: trustAuthCfg, } c := schema.Config{ @@ -139,7 +141,7 @@ func graphqlHandler(logger *log.Logger, usrmgrSvc *usrmgr.Service, trustSvc *tru return errors.New("internal server error") }) - return WithSession(usrmgrSvc, trustSvc, authCfg, srv.ServeHTTP) + return WithSession(usrmgrSvc, trustSvc, authCfg, trustAuthCfg, srv.ServeHTTP) } // TrustService returns a trust service scoped to the given tenant @@ -152,11 +154,11 @@ func (r *Resolver) GetTenantService(ctx context.Context, tenantID gid.TenantID) return r.trustCenterSvc.WithTenant(tenantID) } -func WithSession(usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg AuthConfig, next http.HandlerFunc) http.HandlerFunc { +func WithSession(usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg console_v1.AuthConfig, trustAuthCfg TrustAuthConfig, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - if authCtx := tryTokenAuth(ctx, w, r, trustSvc, authCfg); authCtx != nil { + if authCtx := tryTokenAuth(ctx, w, r, trustSvc, trustAuthCfg); authCtx != nil { next(w, r.WithContext(authCtx)) return } @@ -171,7 +173,7 @@ func WithSession(usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg Aut } } -func trySessionAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, usrmgrSvc *usrmgr.Service, authCfg AuthConfig) context.Context { +func trySessionAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, usrmgrSvc *usrmgr.Service, authCfg console_v1.AuthConfig) context.Context { cookieValue, err := securecookie.Get(r, securecookie.DefaultConfig( authCfg.CookieName, authCfg.CookieSecret, @@ -211,19 +213,19 @@ func trySessionAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, return ctx } -func tryTokenAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, trustSvc *trust.Service, authCfg AuthConfig) context.Context { - cookie, err := r.Cookie(TokenCookieName) +func tryTokenAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, trustSvc *trust.Service, trustAuthCfg TrustAuthConfig) context.Context { + cookie, err := r.Cookie(trustAuthCfg.CookieName) if err != nil { return nil } payload, err := statelesstoken.ValidateToken[probo.TrustCenterAccessData]( - authCfg.CookieSecret, - probo.TokenTypeTrustCenterAccess, + trustAuthCfg.TokenSecret, + trustAuthCfg.TokenType, cookie.Value, ) if err != nil { - clearTokenCookie(w, authCfg) + clearTokenCookie(w, trustAuthCfg) return nil } @@ -233,24 +235,24 @@ func tryTokenAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, t TrustCenterID: payload.Data.TrustCenterID, Email: payload.Data.Email, TenantID: tenantID, - Scope: TokenScopeTrustCenterReadOnly, + Scope: trustAuthCfg.Scope, } return context.WithValue(ctx, tokenAccessContextKey, tokenAccess) } -func clearSessionCookie(w http.ResponseWriter, authCfg AuthConfig) { +func clearSessionCookie(w http.ResponseWriter, authCfg console_v1.AuthConfig) { securecookie.Clear(w, securecookie.DefaultConfig( authCfg.CookieName, authCfg.CookieSecret, )) } -func clearTokenCookie(w http.ResponseWriter, authCfg AuthConfig) { +func clearTokenCookie(w http.ResponseWriter, trustAuthCfg TrustAuthConfig) { http.SetCookie(w, &http.Cookie{ - Name: TokenCookieName, + Name: trustAuthCfg.CookieName, Value: "", - Domain: authCfg.CookieDomain, + Domain: trustAuthCfg.CookieDomain, Path: "/", MaxAge: -1, Secure: true, diff --git a/pkg/server/api/trust/v1/trust_center_access_handler.go b/pkg/server/api/trust/v1/trust_center_access_handler.go index 2e6a6c0ec..06c081bcf 100644 --- a/pkg/server/api/trust/v1/trust_center_access_handler.go +++ b/pkg/server/api/trust/v1/trust_center_access_handler.go @@ -39,7 +39,7 @@ type ( } ) -func authTokenHandler(trustSvc *trust.Service, authCfg AuthConfig) http.HandlerFunc { +func authTokenHandler(trustSvc *trust.Service, trustAuthCfg TrustAuthConfig) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req AuthTokenRequest // Limit request body size to 1KB to prevent DoS attacks @@ -57,7 +57,7 @@ func authTokenHandler(trustSvc *trust.Service, authCfg AuthConfig) http.HandlerF return } - accessData, err := validateTrustCenterAccessToken(r.Context(), trustSvc, authCfg, req.Token) + accessData, err := validateTrustCenterAccessToken(r.Context(), trustSvc, trustAuthCfg, req.Token) if err != nil { httpserver.RenderJSON(w, http.StatusUnauthorized, AuthTokenResponse{ Success: false, @@ -67,9 +67,9 @@ func authTokenHandler(trustSvc *trust.Service, authCfg AuthConfig) http.HandlerF } tokenString, err := statelesstoken.NewToken( - authCfg.CookieSecret, - probo.TokenTypeTrustCenterAccess, - 24*time.Hour, + trustAuthCfg.TokenSecret, + trustAuthCfg.TokenType, + trustAuthCfg.TokenDuration, *accessData, ) if err != nil { @@ -78,11 +78,11 @@ func authTokenHandler(trustSvc *trust.Service, authCfg AuthConfig) http.HandlerF } cookie := &http.Cookie{ - Name: TokenCookieName, + Name: trustAuthCfg.CookieName, Value: tokenString, - Domain: authCfg.CookieDomain, + Domain: trustAuthCfg.CookieDomain, Path: "/", - MaxAge: int(24 * time.Hour / time.Second), // 24 hours + MaxAge: int(trustAuthCfg.CookieDuration / time.Second), Secure: true, HttpOnly: true, SameSite: http.SameSiteStrictMode, @@ -97,10 +97,10 @@ func authTokenHandler(trustSvc *trust.Service, authCfg AuthConfig) http.HandlerF } } -func validateTrustCenterAccessToken(ctx context.Context, trustSvc *trust.Service, authCfg AuthConfig, tokenString string) (*probo.TrustCenterAccessData, error) { +func validateTrustCenterAccessToken(ctx context.Context, trustSvc *trust.Service, trustAuthCfg TrustAuthConfig, tokenString string) (*probo.TrustCenterAccessData, error) { token, err := statelesstoken.ValidateToken[probo.TrustCenterAccessData]( trustSvc.GetTokenSecret(), - probo.TokenTypeTrustCenterAccess, + trustAuthCfg.TokenType, tokenString, ) if err != nil { @@ -113,13 +113,13 @@ func validateTrustCenterAccessToken(ctx context.Context, trustSvc *trust.Service return tenantSvc.TrustCenterAccesses.ValidateToken(ctx, tokenString) } -func trustCenterLogoutHandler(authCfg AuthConfig) http.HandlerFunc { +func trustCenterLogoutHandler(trustAuthCfg TrustAuthConfig) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Clear cookie directly http.SetCookie(w, &http.Cookie{ - Name: TokenCookieName, + Name: trustAuthCfg.CookieName, Value: "", - Domain: authCfg.CookieDomain, + Domain: trustAuthCfg.CookieDomain, Path: "/", MaxAge: -1, Secure: true, @@ -127,8 +127,8 @@ func trustCenterLogoutHandler(authCfg AuthConfig) http.HandlerFunc { SameSite: http.SameSiteStrictMode, }) - w.Header().Set("Clear-Site-Data", "*") - - httpserver.RenderJSON(w, http.StatusOK, map[string]bool{"success": true}) + httpserver.RenderJSON(w, http.StatusOK, map[string]string{ + "message": "Logged out successfully", + }) } } diff --git a/pkg/server/server.go b/pkg/server/server.go index 9c84cb9d8..ee22ab2db 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -38,7 +38,8 @@ type Config struct { Probo *probo.Service Usrmgr *usrmgr.Service Trust *trust.Service - Auth api.AuthConfig + Auth api.ConsoleAuthConfig + TrustAuth api.TrustAuthConfig ConnectorRegistry *connector.ConnectorRegistry Agent *agents.Agent SafeRedirect *saferedirect.SafeRedirect @@ -62,6 +63,7 @@ func NewServer(cfg Config) (*Server, error) { Usrmgr: cfg.Usrmgr, Trust: cfg.Trust, Auth: cfg.Auth, + TrustAuth: cfg.TrustAuth, ConnectorRegistry: cfg.ConnectorRegistry, SafeRedirect: cfg.SafeRedirect, Logger: cfg.Logger.Named("api"),