Authentication
The Municipal Permits System implements secure user authentication using industry-standard practices.
Password Hashing
All passwords are hashed using bcryptjs with a salt factor of 10 rounds before storage.
File: src/routes/usuarios.js:137
const bcrypt = require('bcryptjs');
// Hash password during user creation
let salt = bcrypt.genSaltSync(10);
let hash = bcrypt.hashSync(cedula, salt);
// Store hash in database
await pool.query(
'INSERT INTO usuarios (idDocument, nombre, apellido, cargo, tipo_usuario, username, password) VALUES (?, ?, ?, ?, ?, ?, ?)',
[cedula, nombre, apellido, cargo, typeUser, username, hash]
);
File: src/lib/createAdmin.js:8
// Admin user creation with bcrypt
bcrypt.hashSync('ADMINS', bcrypt.genSaltSync(10))
bcrypt automatically generates a unique salt for each password, making rainbow table attacks ineffective.
Password Verification
File: src/routes/usuarios.js:403
// Compare submitted password with stored hash
if (bcrypt.compareSync(password, result[0].password) == true) {
req.session.usuario = {
indicador: result[0].username,
nombre: result[0].nombre,
apellido: result[0].apellido,
cargo: result[0].cargo,
tipo_usuario: result[0].tipo_usuario,
};
res.send({ success: '¡Usuario Correcto!' });
} else {
errors.password = '¡Contraseña incorrecta!';
res.send(errors);
}
Password Requirements
File: src/routes/usuarios.js:379-388
// Password validation rules
if (password.length < 1) {
errors.password = '¡Debe introducir la contraseña!';
valid = false;
} else if (password.length < 6) {
errors.password = '¡La contraseña debe contener, por lo menos, 6 caracteres!';
valid = false;
} else if (password.length > 30) {
errors.password = '¡La contraseña no debe contener más de 30 caracteres!';
valid = false;
}
The current password requirements are minimal. For production, consider implementing:
- Minimum 8-12 characters
- Mixed case letters
- Numbers and special characters
- Password strength meter
- Password history (prevent reuse)
Session Management
Sessions are managed using express-session with server-side storage.
File: src/index.js:41-46
const session = require('express-session');
app.use(session({
key: 'permisos_municipales_pass',
secret: 'permisos_municipales',
resave: false,
saveUninitialized: false
}));
Session cookie name sent to the client
Secret used to sign the session ID cookie (MUST be changed in production)
Prevents saving session on every request
Prevents creating session until something is stored
Session Security Best Practices
For production deployment:
Use Strong Session Secret
Generate a cryptographically secure random string:const crypto = require('crypto');
const secret = crypto.randomBytes(32).toString('hex');
Configure Cookie Security
Add secure cookie settings:app.use(session({
key: 'permisos_municipales_pass',
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // Prevent XSS
maxAge: 3600000, // 1 hour
sameSite: 'strict' // CSRF protection
}
}));
Implement Session Store
Use Redis or MySQL session store for production:const MySQLStore = require('express-mysql-session')(session);
const sessionStore = new MySQLStore({
host: process.env.DB_HOST,
port: 3306,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
app.use(session({
store: sessionStore,
// ... other options
}));
Role-Based Access Control
The application implements role-based authorization using custom middleware.
User Roles
Three user roles are defined in the system:
Desarrollador
Full system access, including user management
Administrador
Manage users and approve permits
Analista
Create and view permits (limited access)
Session Verification Middleware
File: src/middlewares/verifySession.js
module.exports = function(req, res, next) {
if (!req.session.usuario) {
return res.status(401, '¡No ha iniciado sessión!');
}
return next();
};
This middleware ensures users are authenticated before accessing protected routes.
Role Verification Middleware
File: src/middlewares/verifyRoles.js
module.exports = function verifyRoles(...roles) {
return (req, res, next) => {
if (!roles.some(role => role === req.session.usuario.tipo_usuario)) {
return res.status(403).json({
message: '¡Usted no tiene los permisos necesarios para realizar ésta operación!'
});
}
return next();
};
};
Usage Example
File: src/routes/bebidas.js:13-14
const verifySession = require('../middlewares/verifySession');
const verifyRoles = require('../middlewares/verifyRoles');
// Protect routes with role-based access
router.get('/admin',
verifySession,
verifyRoles('Desarrollador', 'Administrador'),
(req, res) => {
// Only Desarrollador and Administrador can access
}
);
File Upload Security
File uploads are handled securely using Multer with configurable storage and validation.
File: src/routes/bebidas.js:17-24
const multer = require('multer');
const storageProyect = multer.diskStorage({
destination: path.join(__dirname, '../public/server-files/temps/'),
filename: (req, file, funcion) => {
if ('usuario' in req.session) {
funcion(null, file.fieldname + '_de_pago_' + Date.now() +
file.originalname.substr(file.originalname.lastIndexOf('.')));
}
}
});
const uploadProyect = multer({
storage: storageProyect
}).single('comprobante');
File Upload Best Practices
Implement additional file upload security measures:
Validate File Types
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
const allowedTypes = /jpeg|jpg|png|pdf/;
const extname = allowedTypes.test(
path.extname(file.originalname).toLowerCase()
);
const mimetype = allowedTypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
}
cb(new Error('Invalid file type. Only JPEG, PNG and PDF allowed.'));
}
});
Limit File Size
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024 // 5MB limit
}
});
Sanitize Filenames
filename: (req, file, cb) => {
const sanitized = file.originalname.replace(/[^a-zA-Z0-9.]/g, '_');
cb(null, Date.now() + '-' + sanitized);
}
Store Outside Web Root
Store uploaded files outside the public directory and serve them through controlled routes:app.get('/files/:filename', verifySession, (req, res) => {
const filePath = path.join(__dirname, 'private/uploads', req.params.filename);
res.sendFile(filePath);
});
SQL Injection Prevention
The application uses parameterized queries to prevent SQL injection attacks.
Safe Query Pattern
File: src/routes/usuarios.js:394
// ✅ SAFE: Using parameterized queries
await pool.query(
'SELECT * FROM usuarios WHERE username=?',
[indicador],
(err, result) => {
// Handle result
}
);
Never concatenate user input directly into SQL queries:
// ❌ UNSAFE: String concatenation
const query = 'SELECT * FROM usuarios WHERE username="' + username + '"';
// ✅ SAFE: Parameterized query
const query = 'SELECT * FROM usuarios WHERE username=?';
await pool.query(query, [username]);
User inputs are sanitized before processing:
File: src/routes/usuarios.js:363
let indicador = gf.sanitizeString(indicador).toUpperCase();
The gf.sanitizeString() function removes potentially dangerous characters.
CSRF Protection
The current implementation does not include CSRF protection. For production:
Implement CSRF tokens using the csurf package:
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/submit', (req, res) => {
// CSRF token automatically validated
});
Include the token in forms:
<form method="POST" action="/submit">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<!-- other fields -->
</form>
Implement security headers using Helmet:
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
Rate Limiting
Protect against brute force attacks with rate limiting:
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: 'Too many login attempts, please try again later'
});
app.post('/usuarios/login', loginLimiter, (req, res) => {
// Login logic
});
Production Security Checklist
Environment & Configuration
Authentication & Authorization
Security Updates
Regularly update dependencies to patch security vulnerabilities:
# Check for outdated packages
npm outdated
# Update packages
npm update
# Check for security vulnerabilities
npm audit
# Fix vulnerabilities automatically
npm audit fix
Reporting Security Issues
If you discover a security vulnerability:
- Do not disclose publicly
- Contact the development team immediately
- Provide detailed information about the vulnerability
- Allow time for the issue to be addressed
Need Help?
Contact the development team for security concerns