-
Notifications
You must be signed in to change notification settings - Fork 0
Funcionamiento lógico.
Este es un simple diagrama de flujo que se hizo en la fase de planeación del proyecto y que permite dar una visión general del funcionamiento de seguramente en su conjunto.

Proceso:
-
Un nuevo usuario se registra ingresando un nombre de usuario y una contraseña.
-
La contraseña se hashea y se guarda en la base de datos.
-
Después de iniciar sesión, el usuario puede registrar/agregar una nueva entrada de contraseña.
-
La información para cada entrada incluye:
-
Título de la entrada
-
Nombre de usuario
-
E-mail / correo electrónico
-
Contraseña
-
-
La contraseña se cifra antes de guardarse en la base de datos lo que otorga una capa más de seguridad en caso de que la misma sea intervenida.
-
Cada entrada de contraseña se asocia con el usuario que la creó. Esto se hace utilizando una clave foránea (foreign key) que vincula las entradas de contraseña con el id del usuario en la tabla
usersen la base de datos. Esto será necesario para cuando el usuario quiera acceder a sus contraseñas registradas.
-
Cuando el usuario accede a la sección "Mis Contraseñas", se realiza una petición al backend (la API) para que devuelva las contraseñas cifradas asociadas con el usuario.
-
El backend descifra las contraseñas utilizando el key y el iv asociado al usuario.
-
El frontend muestra las contraseñas en forma de lista.
Seguridad:
- Las contraseñas siempre deben almacenarse cifradas y solo descifrarse cuando se muestran al usuario. Esto ayuda a proteger los datos en caso de que la base de datos sea comprometida.
Interacción:
- Al iniciar sesión, el backend autentica al usuario y genera un token JWT que se almacena en el navegador del usuario.
-
El frontend realiza solicitudes al backend para obtener contraseñas usando el token JWT para autenticar las solicitudes.
-
Ejemplo de solicitud:
fetch('api/passwords', { method: 'GET', headers: { 'Authorization': `Bearer ${token} ` } }) .then(response => response.json()) .then(data => { // Manejo de datos de contraseñas });
1 . Registro:
- Usuario se registra -> Hashear contraseña -> Guardar credenciales de usuario en
users.
2 . Agregar Contraseña:
- Usuario inicia sesión -> Cifra contraseña -> Se guarda en
passwordsrelacionándolo a suuser _id.
3 . Ver Contraseñas:
- Usuario accede a "Mis Contraseñas" -> Token JWT enviado en la solicitud -> Backend verifica token -> Devuelve contraseñas cifradas -> Frontend descifra y muestra.
Como base de datos utilizamos PostgreSQL por su simplicidad y potencia. La usaré sobre Vercel, en este 'Web as a Service' subí la API y junto a ella cree una base de datos postgres.
- Puntos importantes de funcionamiento: Para la facilidad de uso de la base de datos recomiendo utilizar un SQL manager como Dbeaver o Beekeeper Studio. Debes crear una nueva conexion > importar desde URL > pegas la POSTGRES_URL (te la proporciona Vercel en Environment Variables)
Vercel te proporciona automaticamente las variables de entorno y no pueden editarse. Tendras problemas con la POSTGRES_URL porque empieza con "postgres://" una convencion ya descontinuada por el ORM que utilizamos que es SQLAlchemy. Esto creo yo que es porque Vercel esta hecho para integrarse con Node.js no con Python. La solución que encontre para esto fue desde database.py esperar el valor de la variable de entorno y decirle que reemplace el "postgres://" por "postgresql://", de esta manera:
DATABASE_URL = os.getenv("POSTGRES_URL") # La URL original de Vercel
if DATABASE_URL.startswith("postgres://"):
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
engine = create_engine(DATABASE_URL)Otro punto importante a tener en cuenta es que en el requirements.txt solo debe estar psycopg-binary, si esta también psycopg generará errores. Por lo que si cargas automaticamente las dependencias fijate que no cargue los dos paquetes juntos.
import os
from sqlalchemy import create_engine, MetaData
from databases import Database
from dotenv import load_dotenv
from sqlalchemy.orm import sessionmaker
load_dotenv()
DATABASE_URL = os.getenv("POSTGRES_URL") # La URL original de Vercel
if DATABASE_URL.startswith("postgres://"):
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
elif not DATABASE_URL:
raise ValueError("POSTGRES_SQL no está configurada en el entorno.")
engine = create_engine(DATABASE_URL) #creando motor de base de datos
database = Database(DATABASE_URL)
metadata = MetaData()
# Crear un SessionLocal para usarlo en las dependencias y endpoints
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# MetaData permite definir la estructura de la base de datos, mediante metodos como Table, Column, Index, ForeignKey etc
metadata.create_all(bind=engine)
# Función para obtener la sesión de la base de datos
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Tabla users:
Es importante primero crear la tabla users ya que en la siguiente tabla se define la foreign key hacia el id del usuario para relacionar las contraseñas a un usuario.
Por otro lado, hashed_password esta en TEXT ya que la funcion encrypt_password devuelve la cadena en base64 (str).
Importante: Entiendo que no es el metodo más seguro, y que otro campos como el username y el email también deberían encriptarse pero no lo hice para facilitar la comprensión del flujo de datos para alguien que recién está iniciándose en las APIs y Bases de datos como yo cuando comencé este proyecto.
Además, es importante que iv, secret_key sean BYTEA y en models se definan como LargeBinary ya que son binarios, no pueden armacenarse en un campo varchar o text.
Campos:
-
id: Identificador único para cada usuario. -
username: Nombre de usuario único. -
hashed _password: Contraseña del usuario cifrada y hasheada -
key -
iv: Iniciatization Vector.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
hashed_password TEXT NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
secret_key BYTEA NOT NULL,
iv BYTEA NOT NULL
);Tabla passwords:
Acá lo importante es establecer correctamente la referencia del user_id hacia la primaryKey 'id' de la tabla 'users'.
Campos:
-
id: Identificador único para cada entrada de contraseña. -
title: Título o nombre descriptivo de la cuenta. -
username: Nombre de usuario asociado con la cuenta. -
password: Contraseña cifrada. -
user_id: Clave foránea que referencia eliddel usuario en la tablausers.
CREATE TABLE passwords (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
title VARCHAR(100) NOT NULL,
username VARCHAR(100) NOT NULL,
password BYTEA NOT NULL
);Models.py:
En este archivo definimos las clases que representan las tablas en la base de datos. Estas clases son modelos ORM (Object-Relational Mapping) que SQLAlchemy utiliza para interactuar con la base de datos. Propósito:
- Definir la estructura de la base de datos: Describe cómo se ven las tablas y sus columnas.
- Mapear clases a tablas: Permite que SQLAlchemy convierta objetos Python en registros de base de datos y viceversa.
from sqlalchemy import Column, Integer, String, ForeignKey, MetaData, LargeBinary
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
metadata = MetaData()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
username = Column(String(50), unique=True, nullable=False)
hashed_password = Column(String, nullable=False)
email = Column(String(254), unique=True, nullable=False)
secret_key = Column(LargeBinary, nullable=False) # Clave secreta para encriptación
iv = Column(LargeBinary, nullable=False) # Vector de inicialización (IV)
class Password(Base):
__tablename__ = 'passwords'
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True)
title = Column(String(100), nullable=False)
username = Column(String(50), nullable=False)
password = Column(LargeBinary, nullable=False)- seguramente.project@gmail.com
- seguramente_project (Instagram)
- santynicosanchez@gmail.com (correo de contacto Santiago Sánchez)
- seguramentetheproject (Canal de Youtube)