Confirmación vía correo electrónico en web2py

Confirmación vía correo electrónico en web2py



Descargas: confirm.zip.

Las confirmaciones vía correo electrónico son una herramienta ampliamente explotada en el desarrollo de aplicaciones web. Al registrarse en un sitio web, realizar una compra online, suscribirse a una newsletter, en todas estas ocasiones operamos con un enlace de confirmación enviado a una casilla de correo electrónico para validar la autenticidad de los datos ingresados. Así, junto con otras verificaciones de seguridad, evitamos llenar nuestras bases de datos con usuarios apócrifos.

Veamos cómo llevar a cabo esto en web2py independientemente de lo que se quiera confirmar. El proceso es bastante repetitivo: crear un código de verificación, enviarlo por correo electrónico, validarlo y, por último, ejecutar la acción pertinente (registrar un usuario, efectuar una compra, etc.). Tomemos, por ejemplo, la suscripción a una newsletter: típicamente se realizará vía un formulario.

Primero importemos las funciones necesarias: una para generar el código y otra para verificarlo.

from confirm import create_confirmation_code, validate_code_from_request

Luego, en nuestro controlador:

# En un controlador (e.g., controllers/default.py).
def newsletter_subscribe():
    """
    Acción que presenta el formulario y genera el código de verificación.
    """
    form = FORM(
        LABEL("Email "),
        INPUT(_type="email", _name="email", requires=IS_EMAIL()),
        INPUT(_type="submit")
    )
    if form.process():
        # Crear código de verificación.
        code = create_confirmation_code(
            # Una cadena arbitraria para identificar el código.
            "newsletter",
            # Un objeto cualquier que se quiera asociar con el código.
            form.vars.email
        )
        # Enviarlo a la dirección ingresada.
        mail.send(
            to=[form.vars.email],
            subject="Confirma tu suscripción",
            message="Presiona en el siguiente enlace: {}.".format(
                URL("confirm_subscription", args=code, host=True, scheme=True)
            )
        )

Ahora bien, resta crear la función que recibirá el código, determinará si es válido y continuará con el proceso de suscripción.

def confirm_suscription():
    # Recuperamos el objeto pasado como segundo argumento al llamar
    # a create_confirmation_code(). Esta función genera un error 404
    # si el código es inválido.
    email = validate_code_from_request("newsletter")
    
    # Finalizar la suscripción
    subscribe_email(email)

¡Listo! El último paso es ubicar el archivo confirm.py dentro de la carpeta modules de nuestra aplicación de web2py.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from base64 import b64encode, b64decode
from cPickle import dumps, loads
from uuid import uuid4

from gluon import current
from gluon.http import HTTP


__all__ = ["create_confirmation_code", "validate_code",
           "validate_code_from_request"]


_CODE_KEY = "conf-{}-{}"
_VALUE_KEY = "value-{}-{}"
_EXPIRATION = 60*60  # One hour.


def create_confirmation_code(service, value):
    """
    `service` must be a string to identify the service where the code
    will be used.
    """
    # Create a random code.
    code = str(uuid4())
    # Store the value.
    current.cache.disk(
        _VALUE_KEY.format(service, code),
        lambda: b64encode(dumps(value)),
        _EXPIRATION
    )
    return code


def validate_code(service, code):
    """
    Check if `code` is valid. If it is, the associated value is returned
    and the code is invalidated. Otherwise, `None` is returned.
    """
    # Retrieve the associated value.
    value_key = _VALUE_KEY.format(service, code)
    value = current.cache.disk(value_key, lambda: None, _EXPIRATION)
    # Remove it once used.
    current.cache.disk(value_key, None)
    if value is not None:
        return loads(b64decode(value))


def validate_code_from_request(service):
    """
    Sames as `validate_code()`, but the code itself is take from
    request.args(0). If it is None or invalid, 404 is raised.
    """
    code = current.request.args(0)
    if code is None:
        raise HTTP(404)
    storage = validate_code(service, code)
    if storage is None:
        raise HTTP(404)
    return storage

Nótese que el código de confirmación es almacenado en el caché en disco ─por una hora─ para evitar el acceso a la base de datos. No obstante, aplicaciones que hagan uso de una base de datos Redis para el caché notarán una mejora en el rendimiento considerable, reemplazando current.cache.disk por current.cache.redis.



Deja un comentario