package middleware import ( "github.com/gofiber/fiber/v2" "wucher/internal/service" sharedconst "wucher/internal/shared/const" "wucher/internal/shared/pkg/apperrorsx" ) const permissionPinVerificationHeader = "X-PIN-Verification-Token" func PermissionRequired(svc *service.AuthService, permissionKey string) fiber.Handler { return permissionRequired(svc, permissionKey, true) } func PermissionRequiredReusable(svc *service.AuthService, permissionKey string) fiber.Handler { return permissionRequired(svc, permissionKey, false) } func permissionRequired(svc *service.AuthService, permissionKey string, consume bool) fiber.Handler { return func(c *fiber.Ctx) error { if svc == nil { e := apperrorsx.New(apperrorsx.ErrUserUnauthorized) e.Message = "auth service not configured" return apperrorsx.Write(c, e) } raw := c.Locals("user_id") userID, ok := raw.([]byte) if !ok || len(userID) == 0 { e := apperrorsx.New(apperrorsx.ErrUserUnauthorized) e.Message = "missing auth context" return apperrorsx.Write(c, e) } allowed, err := svc.HasPermission(c.UserContext(), userID, permissionKey) if err != nil { e := apperrorsx.New(apperrorsx.ErrForbiddenAccess) e.Message = "failed to verify permission" return apperrorsx.Write(c, e) } if !allowed { e := apperrorsx.New(apperrorsx.ErrForbiddenAccess) e.Message = "insufficient permission" return apperrorsx.Write(c, e) } required, err := svc.PermissionRequiresPIN(c.UserContext(), permissionKey) if err != nil { e := apperrorsx.New(apperrorsx.ErrForbiddenAccess) e.Message = "failed to verify PIN requirement" return apperrorsx.Write(c, e) } if required { action := sharedconst.PinActionForPermissionKey(permissionKey) if action == "" { action = permissionKey } token := c.Get(permissionPinVerificationHeader) sessionID := "" for _, accessToken := range service.CookieValues(c.Get(fiber.HeaderCookie), svc.AccessCookieName()) { sid, err := svc.ParseAccessTokenSessionID(c.UserContext(), accessToken) if err == nil { sessionID = sid break } } if sessionID == "" { return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrTokenInvalid)) } var verifyErr error if consume { verifyErr = svc.ConsumeSecurityPINActionTokenInSession(c.UserContext(), userID, action, token, sessionID) } else { verifyErr = svc.ValidateSecurityPINActionTokenInSession(c.UserContext(), userID, action, token, sessionID) } if verifyErr != nil { return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrPINVerificationRequired202)) } } return c.Next() } }