# frozen_string_literal: true module Api module V1 # API para formulario de contacto class ContactController < BaseController # Permitir conexiones desde varios sitios, estamos chequeando más # adelante. skip_forgery_protection # Aplicar algunos chequeos básicos. Deberíamos registrar de # alguna forma los errores pero tampoco queremos que nos usen # recursos. # # Devolvemos un error 428: Precondition Required dando la # oportunidad a les visitantes de reintentar en el caso de falsos # positivos. No devolvemos contenido para que el servidor web # capture y muestra la página de error específica de cada sitio o # la genérica de Sutty. # # XXX: Ordenar en orden ascendiente según uso de recursos. before_action :cookie_is_valid?, unless: :performed? before_action :valid_authenticity_token_in_cookie?, unless: :performed? before_action :site_exists?, unless: :performed? before_action :site_is_origin?, unless: :performed? before_action :form_exists?, unless: :performed? before_action :from_is_address?, unless: :performed? before_action :gave_consent?, unless: :performed? # Recibe un mensaje a través del formulario de contacto y lo envía # a les usuaries del sitio. # # Tenemos que verificar que el sitio exista y que algunos campos # estén llenos para detener spambots o DDOS. También nos vamos a # estar apoyando en la limitación de peticiones en el servidor web. def receive # No hacer nada si no se pasaron los chequeos return if performed? # TODO: Verificar que los campos obligatorios hayan llegado! # Si todo salió bien, enviar los correos y redirigir al sitio. # El sitio nos dice a dónde tenemos que ir. ContactJob.perform_async site_id: site.id, form_name: params[:form], form: contact_params.to_h.symbolize_keys redirect_to contact_params[:redirect] || origin.to_s end private def site_cookie @site_cookie ||= cookies.encrypted[site_id] end # Comprueba que no se haya reutilizado una cookie vencida # # XXX: Si el navegador envió una cookie vencida es porque la está # reutilizando, probablemente de forma maliciosa? Pero también # puede ser que haya tardado más de media hora en enviar el # formulario. def cookie_is_valid? head :precondition_required unless (site_cookie.try(:[], 'expires') || 0) > Time.now.to_i end # Queremos comprobar que la cookie corresponda con la sesión. La # cookie puede haber vencido, así que es uno de los chequeos más # simples que hacemos. # # TODO: Pensar una forma de redirigir al origen sin vaciar el # formulario para que le usuarie recargue la cookie. def valid_authenticity_token_in_cookie? return if valid_authenticity_token? session, site_cookie['csrf'] head :precondition_required end # Comprueba que el sitio existe # # TODO: Responder con una zip bomb! def site_exists? head :precondition_required if site.nil? end # Comprueba que el mensaje fue enviado desde el sitio o uno # de los sitios permitidos. # # XXX: Este header se puede falsificar de todas formas pero al # menos es una trampa. def site_is_origin? return if site.urls(slash: false).any? { |u| origin.to_s.start_with? u } head :precondition_required end # Detecta si la dirección de contacto es válida. Además es # opcional. def from_is_address? return if contact_params[:from].blank? return if EmailAddress.valid? contact_params[:from] head :precondition_required end # No aceptar nada si no dió su consentimiento def gave_consent? return if contact_params[:consent].present? head :precondition_required end # Los campos que se envían tienen que corresponder con un # formulario de contacto. def form_exists? return if site.form? params[:form] head :precondition_required end # Encuentra el sitio o devuelve nulo def site @site ||= Site.find_by(name: site_id) end # Parámetros limpios def contact_params @contact_params ||= params.permit(site.form(params[:form]).params) end # Para poder testear, enviamos un mensaje en el cuerpo de la # respuesta # # @param [Any] el mensaje def body(message) return message.to_s if Rails.env.test? end # No queremos informar nada a los spammers, pero en testeo # queremos saber por qué. :no_content oculta el cuerpo. def status Rails.env.test? ? :unprocessable_entity : :no_content end end end end