Saltar al contenido

Modelo de usuario y controlador de autenticación para un simple servicio de almacenamiento de archivos usando VueJS, Flask y RethinkDB

Añadiremos un código para nuestras modelos. Para esta aplicación, sólo necesitamos dos modelos para empezar. Para este paso, estaremos creando sólo el modelo de usuario.

Empezamos conectando con RethinkDB y creando un objeto de conexión a nuestra base de datos.

Modelo de usuario y controlador de autenticación para un simple servicio de almacenamiento de archivos usando VueJS, Flask y RethinkDB
Modelo de usuario y controlador de autenticación para un simple servicio de almacenamiento de archivos usando VueJS, Flask y RethinkDB
12345678import rethinkdb as rfrom flask import current_appconn = r.connect(db="papers")classRethinkDBModel(object):pass

pitón

Hemos hecho referencia al nombre de la base de datos desde la configuración de la aplicación. En flask, tenemos la variable current_app que contiene una referencia a la instancia de la aplicación que se está ejecutando actualmente.

¿Por qué creé una clase en blanco de RethinkDBModel? Bueno, puede que haya un par de cosas que queramos compartir entre las clases modelo; esta clase está aquí en caso de que sea necesario compartir modelos cruzados.

Nuestra clase de usuario heredará de esta clase base vacía. En User, tendremos algunas funciones que usaremos para interactuar con la base de datos de los controladores.

Empezamos con la función create(). Esta función será llamada cuando necesitemos crear un documento de usuario en la tabla.

1234567891011121314151617181920classUser(RethinkDBModel): _table =$0027users$0027@classmethoddefcreate(cls,**kwargs): fullname = kwargs.get($0027fullname$0027) email = kwargs.get($0027email$0027) password = kwargs.get($0027password$0027) password_conf = kwargs.get($0027password_conf$0027)if password != password_conf:raise ValidationError("La contraseña y la confirmación de la contraseña deben tener el mismo valor") password = cls. hash_password(password) doc ={$0027fullname$0027: nombre completo,$0027email$0027: email,$0027password$0027: contraseña,$0027date_created$0027: datetime.now(r.make_timezone($0027+01:00$0027)),$0027date_modified$0027: datetime.now(r.make_timezone($0027+01:00$0027))} r.table(cls._table).insert(doc).run(conn)

pitón

Aquí, estamos haciendo uso del método de decoración de la clase. Este decorador nos permite tener acceso a la instancia de la clase desde el cuerpo del método. Usaremos la instancia de la clase para acceder a la propiedad _table desde dentro del método. La mesa guarda el nombre de la mesa para ese modelo.

También hemos añadido código aquí para asegurarnos de que los campos password y password_conf son los mismos. Cuando esto ocurra, se lanzará un ValidationError. Las excepciones se almacenarán en el módulo /api/utils/errors.py. Encuentra la definición de ValidationError a continuación:

12classValidationError(Excepción):pass

pitón

Estamos usando excepciones nombradas porque son más fáciles de rastrear.

¿Notan cómo usamos datetime.now(r.make_timezone($0027+01:00$0027)) aquí? Me enfrenté a algunos problemas cuando usé datetime.now() sin la zona horaria. RethinkDB requiere que la información de la zona horaria se establezca en los campos de fecha de los documentos. La función Python no nos proporciona esto por defecto a menos que lo especifiquemos como un parámetro de la función now() (Ver aquí para más). Utilizando la zona horaria r.make_timezone($0027+01:00$0027) podemos crear un objeto de zona horaria que podemos utilizar para la función datetime.now().

Si todo va bien y no se encuentran excepciones, llamamos al método insert() en el objeto de la tabla que devuelve r.table(nombre_de_la_tabla). Este método toma un diccionario que contiene los datos. Estos datos se almacenarán como un nuevo documento en la tabla seleccionada.

Hemos hecho una llamada al método hash_password() en nuestra clase. Este método hace uso del módulo hash.pbkdf2_sha256 en el paquete passlib para obtener la contraseña de forma bastante segura. Además de eso, necesitaremos crear un método para verificar las contraseñas.

123456789101112131415161718192021222324252627282930de passlib.hashimport pbkdf2_sha256classUser(RethinkDBModel): _table =$0027users$0027@classmethoddefcreate(cls,**kwargs): fullname = kwargs.get($0027fullname$0027) email = kwargs.get($0027email$0027) password = kwargs.get($0027password$0027) password_conf = kwargs.get($0027password_conf$0027)if password ! = password_conf:raise ValidationError("La contraseña y la confirmación de la contraseña deben tener el mismo valor") password = cls.hash_password(password) doc ={$0027fullname$0027: fullname,$0027email$0027: email,$0027password$0027: password,$0027date_created$0027: datetime. now(r.make_timezone($0027+01:00$0027)),$0027date_modified$0027: datetime.now(r.make_timezone($0027+01:00$0027))} r.table(cls._table).insert(doc).run(conn) @staticmethoddefhash_password(password):return pbkdf2_sha256. encrypt(password, rounds=200000, salt_size=16) @staticmethoddefverify_password(password, _hash):return pbkdf2_sha256.verify(password, _hash)

pitón

El método pbkdf2_sha256.encrypt() se llama con la contraseña y los valores para las rondas y el tamaño de la sal. Vea aquí para detalles sobre cómo puede personalizar su encriptación y cómo funciona la biblioteca. Sólo para dar un poco de contexto sobre la decisión de usar PBKDF2:

Citas de la documentación de passlib

El método verify_password será llamado usando la cadena de la contraseña y un hash. Devolverá true o false si la contraseña es válida.

Ahora pasaremos a la función de validación. Esta función será llamada en el método de acceso con la dirección de correo electrónico y la contraseña. La función comprobará que el documento existe usando el campo de correo electrónico como índice y luego comparará el hash de la contraseña con la contraseña suministrada.

Además, como vamos a utilizar el JWT (JSON Web Token) para la autenticación basada en tokens, generaremos un token si el usuario proporciona información válida. Así es como se verá todo el models.py cuando terminemos de agregar la lógica.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162importar osimport rethinkdb como rfrom jose import jwtfrom datetime import datetimefrom passlib. hashimport pbkdf2_sha256from flask import current_appfrom api.utils.errors import ValidationErrorconn = r.connect(db="papers")classRethinkDBModel(object):passclassUser(RethinkDBModel): tabla =$0027users$0027@classmethoddefcreate(cls,**kwargs): fullname = kwargs.get($0027fullname$0027) email = kwargs.get($0027email$0027) password = kwargs.get($0027password$0027) password_conf = kwargs.get($0027password_conf$0027)if password != password_conf:raise ValidationError("La contraseña y la confirmación de la contraseña deben tener el mismo valor") password = cls. hash_password(password) doc ={$0027fullname$0027: nombre completo,$0027email$0027: email,$0027password$0027: contraseña,$0027date_created$0027: datetime.now(r.make_timezone($0027+01:00$0027)),$0027date_modified$0027: datetime.now(r. make_timezone($0027+01:00$0027))} r.table(cls._table).insert(doc).run(conn) @classmethoddefvalidate(cls, email, password): docs =list(r.table(cls._table).filter({$0027email$0027: email}). run(conn))ifnotlen(docs):raise ValidationError("No se pudo encontrar la dirección de correo electrónico que especificaste") _hash = docs[0][$0027password$0027]if cls.verify_password(password, _hash):try: token = jwt. encode({$0027id$0027: docs[0][$0027id$0027]}, current_app.config[$0027SECRET_KEY$0027], algorithm=$0027HS256$0027)return token except JWTError:raise ValidationError("Hubo un problema al intentar crear un token JWT. ")else:raise ValidationError("La contraseña que introdujo era incorrecta.") @staticmethoddefhash_password(password):return pbkdf2_sha256.encrypt(password, rounds=200000, salt_size=16) @staticmethoddefverify_password(password, _hash):return pbkdf2_sha256.verify(password, _hash)

pitón

Un par de cosas a tener en cuenta en el método validate(). En primer lugar, la función filter() fue usada aquí en el objeto de la tabla. Este comando toma un diccionario que se utiliza para buscar en la tabla. Esta función también puede tomar un predicado, en algunos casos. Este predicado puede ser una función lambda y se usará de forma similar a lo que se hace con la función de filtro de pitón o cualquier otra función que tome una función como argumento. La función devuelve un cursor que puede utilizarse para acceder a todos los documentos que se devuelven por la consulta. El cursor es iterable y como tal podemos iterar el objeto cursor usando el bucle for… in. En este caso, hemos optado por convertir el objeto iterable en una lista utilizando la función de lista de Python.

Como es de esperar, básicamente hacemos dos cosas aquí. Queremos saber si la dirección de correo electrónico existe y si la contraseña es correcta. Para la primera parte, básicamente contamos la colección. Si esta está vacía, planteamos un error. Para la segunda parte, llamamos a la función verify_password() para comparar la contraseña suministrada con el hash de la base de datos. Levantamos una excepción si no coinciden.

También cabe destacar cómo hemos utilizado jwt.encode() para crear un token JWT y devolverlo al controlador. Este método es bastante sencillo y puedes ver la documentación aquí.

Eso es todo para el modelo. Pasemos a los controladores. Hemos tratado de obedecer el principio de tener modelos gordos y controladores delgados , en este modelo. La mayor parte de la lógica está en los modelos. De esta manera, nuestros controladores sólo se centran en el enrutamiento y el informe de errores al usuario final de la API.