The Calculus Learning Platform implements a dual-mode authentication system supporting both student and professor accounts with role-based routing.
Overview
The authentication system is built with Vue 3 on the frontend (Login.vue) and FastAPI on the backend (main.py), featuring:
User registration and login
Role-based access control (student vs professor)
Session persistence with localStorage
Special admin account handling
User Registration Flow
The registration form collects four fields from new users:
<!-- Login.vue -->
< input v-model = " nombre " type = "text" placeholder = "Nombre" />
< input v-model = " apellido " type = "text" placeholder = "Apellidos" />
< input v-model = " email " type = "email" placeholder = "Correo electrónico" />
< input v-model = " password " type = "password" placeholder = "Crea una contraseña" />
User Submits Form
When the user clicks “Registrarse”, the form validates all fields are filled.
API Request Sent
Data is sent to /registrar endpoint: const respuesta = await fetch ( ` ${ urlBase } /registrar` , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({
email: email . value ,
password: password . value ,
nombre: nombre . value ,
apellidos: apellido . value
})
});
Database Record Created
The backend inserts the user into the usuarios table: # main.py:124-132
query = "INSERT INTO usuarios (email, password, nombre, apellidos) VALUES ( %s , %s , %s , %s )"
cursor.execute(query, (datos.email, datos.password, datos.nombre, datos.apellidos))
conexion.commit()
return { "mensaje" : "Usuario creado" , "exito" : True }
User Redirected to Login
After successful registration, the mode switches to login: // Login.vue:336-339
setTimeout (() => {
esRegistro . value = false ;
mensaje . value = "Cuenta creada, Ahora inicia sesión." ;
}, 1500 );
Duplicate email registration is prevented by the database constraint. The backend returns {"mensaje": "El usuario ya existe", "exito": false} if the email already exists.
Login Mechanism
Standard Login Process
// Login.vue:281-291
const endpoint = esRegistro . value ? "/registrar" : "/login" ;
const respuesta = await fetch ( ` ${ urlBase }${ endpoint } ` , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({
email: email . value ,
password: password . value
})
});
The backend validates credentials against the database:
# main.py:142-163
@app.post ( "/login" )
async def login ( datos : UsuarioRegistro):
cursor = conexion.cursor( cursor_factory = RealDictCursor)
query = "SELECT email, nombre, apellidos FROM usuarios WHERE email = %s AND password = %s "
cursor.execute(query, (datos.email, datos.password))
usuario = cursor.fetchone()
if usuario:
return {
"mensaje" : "Login correcto" ,
"exito" : True ,
"usuario" : usuario[ 'email' ],
"nombre" : usuario[ 'nombre' ],
"apellidos" : usuario[ 'apellidos' ]
}
else :
return { "mensaje" : "Credenciales incorrectas" , "exito" : False }
Passwords are stored in plain text in the database. This is a security vulnerability that should be addressed in production by implementing password hashing (e.g., bcrypt).
Role-Based Access Control
Professor Account Detection
The system identifies professors using a hardcoded email check:
// Login.vue:298-307
const CORREO_PROFE = "[email protected] " ;
if ( email . value . toLowerCase () === CORREO_PROFE . toLowerCase ()) {
localStorage . setItem ( "usuario" , datos . usuario );
mensaje . value = "Bienvenido, Profesor." ;
setTimeout (() => {
router . push ( "/panel-profesor" );
}, 1000 );
return ;
}
Student Account Routing
Non-professor accounts are routed to the student experience:
// Login.vue:309-331
if ( ! esRegistro . value ) {
localStorage . setItem ( "usuario" , datos . usuario );
localStorage . setItem ( "email" , email . value );
const yaVioBienvenida = localStorage . getItem ( `visto_bienvenida_ ${ datos . usuario } ` );
setTimeout (() => {
if ( yaVioBienvenida === 'true' ) {
router . push ( "/foro1" ); // Returning student
} else {
router . push ( "/bienvenida" ); // First-time student
}
}, 1000 );
}
Professor Role
Email: [email protected]
Route: /panel-profesor
Access: All student submissions
Capability: Provide feedback
Student Role
Email: Any other email
Route: /bienvenida or /foro1
Access: Own submissions only
Capability: Submit forum/exam responses
Session Management
localStorage Implementation
User sessions are maintained using browser localStorage:
Login Storage
Session Check Example
Welcome Flag
// Login.vue:301, 312-313
localStorage . setItem ( "usuario" , datos . usuario );
localStorage . setItem ( "email" , email . value );
Session Keys Used
Key Purpose Example Value usuarioUser’s email identifier "[email protected] "emailDuplicate email storage "[email protected] "visto_bienvenida_{email}First-time user flag "true"
The system uses the usuario key to identify the logged-in user throughout the application. All forum and exam submissions reference this value.
Client-side validation ensures data integrity:
// Login.vue:239-266
function validarCampos () {
if ( esRegistro . value ) {
if ( ! nombre . value ?. trim ()) {
mensaje . value = "Por favor ingresa tu nombre" ;
tipoMensaje . value = "error" ;
return false ;
}
if ( ! apellido . value ?. trim ()) {
mensaje . value = "Por favor ingresa tus apellidos" ;
tipoMensaje . value = "error" ;
return false ;
}
}
if ( ! email . value ?. trim ()) {
mensaje . value = "Por favor ingresa tu correo electrónico" ;
tipoMensaje . value = "error" ;
return false ;
}
if ( ! password . value ) {
mensaje . value = "Por favor ingresa tu contraseña" ;
tipoMensaje . value = "error" ;
return false ;
}
return true ;
}
Error Handling
The authentication system provides user-friendly error messages:
// Login.vue:294-351
const datos = await respuesta . json ();
if ( datos . exito ) {
tipoMensaje . value = "exito" ;
mensaje . value = datos . mensaje ;
// ... handle success
} else {
tipoMensaje . value = "error" ;
mensaje . value = datos . mensaje ; // "Credenciales incorrectas" or "El usuario ya existe"
}
“Por favor ingresa tu correo electrónico” - Email field is empty
“Por favor ingresa tu contraseña” - Password field is empty
“Por favor ingresa tu nombre” - Name field is empty (registration only)
“Por favor ingresa tus apellidos” - Last name field is empty (registration only)
“Credenciales incorrectas” - Invalid email/password combination
“El usuario ya existe” - Email already registered
“Error conectando con el servidor” - Backend connection failure
API Endpoints
Registration Endpoint
# main.py:123-140
@app.post ( "/registrar" )
async def registrar ( datos : UsuarioRegistro):
conexion = conectar_bd()
if not conexion: raise HTTPException( 500 , "Error BD" )
try :
cursor = conexion.cursor()
query = "INSERT INTO usuarios (email, password, nombre, apellidos) VALUES ( %s , %s , %s , %s )"
cursor.execute(query, (datos.email, datos.password, datos.nombre, datos.apellidos))
conexion.commit()
return { "mensaje" : "Usuario creado" , "exito" : True }
except psycopg2.IntegrityError:
conexion.rollback()
return { "mensaje" : "El usuario ya existe" , "exito" : False }
Login Endpoint
# main.py:142-163
@app.post ( "/login" )
async def login ( datos : UsuarioRegistro):
conexion = conectar_bd()
if not conexion: raise HTTPException( 500 , "Error BD" )
try :
cursor = conexion.cursor( cursor_factory = RealDictCursor)
query = "SELECT email, nombre, apellidos FROM usuarios WHERE email = %s AND password = %s "
cursor.execute(query, (datos.email, datos.password))
usuario = cursor.fetchone()
if usuario:
return {
"mensaje" : "Login correcto" ,
"exito" : True ,
"usuario" : usuario[ 'email' ],
"nombre" : usuario[ 'nombre' ],
"apellidos" : usuario[ 'apellidos' ]
}
else :
return { "mensaje" : "Credenciales incorrectas" , "exito" : False }
finally :
conexion.close()
Security Considerations
Security Vulnerabilities in Current Implementation:
No Password Hashing - Passwords stored in plain text
No HTTPS Enforcement - Credentials transmitted without encryption verification
No Session Expiration - localStorage tokens never expire
No CSRF Protection - No token-based request validation
Hardcoded Admin Email - Single point of failure for professor access
For production deployment, implement:
Password hashing (bcrypt, argon2)
JWT tokens with expiration
HTTPS enforcement
Multi-factor authentication
Role-based permissions in database