feat(auth): block disabled user accounts at the subscription guard
RequireActiveTenantSubscription now optionally takes an AuthRepository and rejects the request with 40311/user_disabled when the actor's user row is anything other than active. Wire the repo through the tenant router, surface the new error code in admin-web's membershipBlockedErrors set, persist a "disabled" membership status when markStoredMembershipBlocked sees user_disabled, and add localized copy on the blocked screen and shell membership banner. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,7 +65,10 @@ func RegisterRoutes(app *bootstrap.App) {
|
||||
desktopAuth.POST("/monitoring/tasks/:id/cancel", desktopMonitoringHandler.Cancel)
|
||||
|
||||
tenantProtected := protected.Group("/tenant")
|
||||
tenantProtected.Use(RequireActiveTenantSubscription(repository.NewTenantPlanRepository(app.DB)))
|
||||
tenantProtected.Use(RequireActiveTenantSubscription(
|
||||
repository.NewTenantPlanRepository(app.DB),
|
||||
repository.NewAuthRepository(app.DB),
|
||||
))
|
||||
tenantProtected.GET("/accounts", desktopAccountHandler.TenantList)
|
||||
tenantProtected.POST("/accounts/:id/request-delete", desktopAccountHandler.RequestDelete)
|
||||
tenantProtected.POST("/tasks/:id/reconcile", desktopTaskHandler.Reconcile)
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/shared/auth"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/response"
|
||||
"github.com/geo-platform/tenant-api/internal/tenant/repository"
|
||||
)
|
||||
|
||||
func RequireActiveTenantSubscription(plans repository.TenantPlanRepository) gin.HandlerFunc {
|
||||
func RequireActiveTenantSubscription(
|
||||
plans repository.TenantPlanRepository,
|
||||
authRepos ...repository.AuthRepository,
|
||||
) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
actor, ok := auth.ActorFromCtx(c.Request.Context())
|
||||
if !ok {
|
||||
@@ -19,6 +25,28 @@ func RequireActiveTenantSubscription(plans repository.TenantPlanRepository) gin.
|
||||
return
|
||||
}
|
||||
|
||||
if len(authRepos) > 0 && authRepos[0] != nil {
|
||||
user, err := authRepos[0].GetUserByID(c.Request.Context(), actor.UserID)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
response.Error(c, response.ErrNotFound(40401, "user_not_found", "user not found"))
|
||||
} else {
|
||||
response.Error(c, response.ErrInternal(50092, "user_lookup_failed", "failed to load user"))
|
||||
}
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(user.Status), "active") {
|
||||
response.Error(c, response.ErrForbidden(
|
||||
40311,
|
||||
"user_disabled",
|
||||
"user account is disabled, please contact support",
|
||||
))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
access, err := plans.GetTenantPlanAccess(c.Request.Context(), actor.TenantID, now)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user