Skip to content

Funcionamiento lógico.

san2 edited this page Feb 24, 2025 · 6 revisions

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.

Diagrama de flujo

Proceso lógico general de la aplicación.

Registro y Almacenamiento de Contraseñas

Proceso:

Registro de Usuario

  • 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.

Registro de una contraseña (entrada)

  • 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 users en la base de datos. Esto será necesario para cuando el usuario quiera acceder a sus contraseñas registradas.

Acceso a "Mis Contraseñas"

  • 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.

Conexión de Frontend y Backend

Interacción:

Login.

  • Al iniciar sesión, el backend autentica al usuario y genera un token JWT que se almacena en el navegador del usuario.

Solicitudes de Contraseñas.

  • 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
    
    });

Ejemplo de Flujo.

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 passwords relacionándolo a su user _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.

Base de datos.

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()

Estructura de la base de datos.

Estructura BD

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 el id del usuario en la tabla users.

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)