diff --git a/taco/cmd/statesman/main.go b/taco/cmd/statesman/main.go index e92896855..8bd7b1e1f 100644 --- a/taco/cmd/statesman/main.go +++ b/taco/cmd/statesman/main.go @@ -38,7 +38,7 @@ func main() { // Load configuration from environment variables into our struct. var queryCfg query.Config - err := envconfig.Process("taco", &queryCfg) // The prefix "TACO" will be used for all vars. + err := envconfig.Process("opentaco", &queryCfg) // The prefix "TACO" will be used for all vars. if err != nil { log.Fatalf("Failed to process configuration: %v", err) } diff --git a/taco/internal/api/org_handler.go b/taco/internal/api/org_handler.go index b8aeb118e..2463839ea 100644 --- a/taco/internal/api/org_handler.go +++ b/taco/internal/api/org_handler.go @@ -5,7 +5,6 @@ import ( "log/slog" "net/http" "context" - "fmt" "github.com/diggerhq/digger/opentaco/internal/domain" "github.com/diggerhq/digger/opentaco/internal/rbac" @@ -96,40 +95,17 @@ func (h *OrgHandler) CreateOrganization(c echo.Context) error { ) // ======================================== - // Use transaction to create org + init RBAC atomically + // Create org first, then init RBAC (SQLite-friendly) // ======================================== var org *domain.Organization + // Create organization in transaction err := h.orgRepo.WithTransaction(ctx, func(ctx context.Context, txRepo domain.OrganizationRepository) error { - // Create organization within transaction createdOrg, err := txRepo.Create(ctx, req.OrgID, req.Name, userIDStr) if err != nil { return err } org = createdOrg - - // Initialize RBAC within the same transaction - if h.rbacManager != nil { - slog.Info("Initializing RBAC for new organization", - "orgID", req.OrgID, - "adminUser", userIDStr, - ) - - if err := h.rbacManager.InitializeRBAC(ctx, userIDStr, emailStr); err != nil { - // IMPORTANT: Returning error here will rollback the entire transaction - slog.Error("Failed to initialize RBAC, rolling back org creation", - "orgID", req.OrgID, - "error", err, - ) - return fmt.Errorf("failed to initialize RBAC: %w", err) - } - - slog.Info("RBAC initialized successfully", - "orgID", req.OrgID, - "adminUser", userIDStr, - ) - } - return nil }) @@ -146,8 +122,7 @@ func (h *OrgHandler) CreateOrganization(c echo.Context) error { }) } - // Any other error (including RBAC init failure) returns 500 - slog.Error("Failed to create organization with RBAC", + slog.Error("Failed to create organization", "orgID", req.OrgID, "error", err, ) @@ -157,7 +132,31 @@ func (h *OrgHandler) CreateOrganization(c echo.Context) error { }) } - // Success - both org and RBAC were created + // Initialize RBAC after org creation (outside transaction for SQLite compatibility) + if h.rbacManager != nil { + slog.Info("Initializing RBAC for new organization", + "orgID", req.OrgID, + "adminUser", userIDStr, + ) + + if err := h.rbacManager.InitializeRBAC(ctx, userIDStr, emailStr); err != nil { + // Org was created but RBAC failed - log warning but don't fail the request + // User can retry RBAC initialization or assign roles manually + slog.Warn("Organization created but RBAC initialization failed", + "orgID", req.OrgID, + "error", err, + "recommendation", "RBAC can be initialized later via /rbac/init endpoint", + ) + // Continue with success response - org was created + } else { + slog.Info("RBAC initialized successfully", + "orgID", req.OrgID, + "adminUser", userIDStr, + ) + } + } + + // Success - org created (and RBAC initialized if available) return c.JSON(http.StatusCreated, CreateOrgResponse{ OrgID: org.OrgID, Name: org.Name, diff --git a/taco/internal/query/config.go b/taco/internal/query/config.go index 4d55d7152..b2bece788 100644 --- a/taco/internal/query/config.go +++ b/taco/internal/query/config.go @@ -16,8 +16,8 @@ type SQLiteConfig struct { Path string `envconfig:"DB_PATH" default:"./data/taco.db"`// if we call it PATH at the struct level, it will pick up the terminal path Cache string `envconfig:"CACHE" default:"shared"` BusyTimeout time.Duration `envconfig:"BUSY_TIMEOUT" default:"5s"` - MaxOpenConns int `envconfig:"MAX_OPEN_CONNS" default:"1"` - MaxIdleConns int `envconfig:"MAX_IDLE_CONNS" default:"1"` + MaxOpenConns int `envconfig:"MAX_OPEN_CONNS" default:"25"` + MaxIdleConns int `envconfig:"MAX_IDLE_CONNS" default:"10"` PragmaJournalMode string `envconfig:"PRAGMA_JOURNAL_MODE" default:"WAL"` PragmaForeignKeys string `envconfig:"PRAGMA_FOREIGN_KEYS" default:"ON"` PragmaBusyTimeout string `envconfig:"PRAGMA_BUSY_TIMEOUT" default:"5000"` diff --git a/taco/internal/query/sqlite/store.go b/taco/internal/query/sqlite/store.go index 2467ce0ea..05a13d8d2 100644 --- a/taco/internal/query/sqlite/store.go +++ b/taco/internal/query/sqlite/store.go @@ -38,6 +38,14 @@ func NewSQLiteQueryStore(cfg query.SQLiteConfig) (query.Store, error) { return nil, fmt.Errorf("apply busy_timeout: %w", err) } + // Configure connection pool settings + sqlDB, err := db.DB() + if err != nil { + return nil, fmt.Errorf("get underlying sql.DB: %w", err) + } + sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) + sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) + // Create the common SQLStore with our configured DB object, breaking the cycle. return common.NewSQLStore(db) } diff --git a/taco/internal/rbac/handler.go b/taco/internal/rbac/handler.go index 076737bb0..ef70ed9c8 100644 --- a/taco/internal/rbac/handler.go +++ b/taco/internal/rbac/handler.go @@ -335,6 +335,12 @@ func (h *Handler) DeleteRole(c echo.Context) error { // Helper functions func (h *Handler) getPrincipalFromToken(c echo.Context) (Principal, error) { + // First check if principal is already in context (webhook auth sets this) + if principal, ok := PrincipalFromContext(c.Request().Context()); ok { + return principal, nil + } + + // Fall back to JWT token verification for public API routes authz := c.Request().Header.Get("Authorization") if !strings.HasPrefix(authz, "Bearer ") { return Principal{}, echo.NewHTTPError(http.StatusUnauthorized, "missing bearer token") @@ -345,19 +351,8 @@ func (h *Handler) getPrincipalFromToken(c echo.Context) (Principal, error) { return Principal{}, echo.NewHTTPError(http.StatusInternalServerError, "auth not configured") } - // Debug: check signer state - fmt.Printf("[RBAC DEBUG] Signer nil? %t\n", h.signer == nil) - fmt.Printf("[RBAC DEBUG] Signer addr: %p\n", h.signer) - claims, err := h.signer.VerifyAccess(token) if err != nil { - // Debug: log the verification failure - fmt.Printf("[RBAC DEBUG] Token verification failed: %v\n", err) - tokenPreview := token - if len(token) > 50 { - tokenPreview = token[:50] + "..." - } - fmt.Printf("[RBAC DEBUG] Token preview: %s\n", tokenPreview) return Principal{}, echo.NewHTTPError(http.StatusUnauthorized, "invalid token") }