security: eliminar exposición de contraseñas en frontend y proteger endpoints
This commit is contained in:
+2
-2
@@ -191,7 +191,7 @@ app.delete('/api/vote/:voter', async (req: Request, res: Response): Promise<void
|
|||||||
const providedKey = req.headers['x-admin-key'] || req.body?.password || req.query?.key;
|
const providedKey = req.headers['x-admin-key'] || req.body?.password || req.query?.key;
|
||||||
|
|
||||||
if (!isValidAdminKey(providedKey)) {
|
if (!isValidAdminKey(providedKey)) {
|
||||||
res.status(401).json({ success: false, error: 'Contraseña de administración incorrecta. Use REMOVERVOTOE3.' });
|
res.status(401).json({ success: false, error: 'Contraseña de autorización incorrecta.' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ app.delete('/api/votes', async (req: Request, res: Response): Promise<void> => {
|
|||||||
const providedKey = req.headers['x-admin-key'] || req.body?.password || req.query?.key;
|
const providedKey = req.headers['x-admin-key'] || req.body?.password || req.query?.key;
|
||||||
|
|
||||||
if (!isValidAdminKey(providedKey)) {
|
if (!isValidAdminKey(providedKey)) {
|
||||||
res.status(401).json({ success: false, error: 'Contraseña de administración incorrecta. Use REMOVERVOTOE3.' });
|
res.status(401).json({ success: false, error: 'Contraseña de autorización incorrecta.' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ export const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trimmedPass = password.trim();
|
const trimmedPass = password.trim();
|
||||||
if (trimmedPass !== 'REMOVERVOTOE3' && trimmedPass !== 'RESETEOE3') {
|
if (!trimmedPass) {
|
||||||
setAdminMsg({ text: 'Contraseña incorrecta. Se requiere "REMOVERVOTOE3".', type: 'error' });
|
setAdminMsg({ text: 'Ingresa la contraseña de autorización.', type: 'error' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ export const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||||||
setPassword('');
|
setPassword('');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setAdminMsg({
|
setAdminMsg({
|
||||||
text: `Error al remover voto: ${err instanceof Error ? err.message : 'Error desconocido'}`,
|
text: err instanceof Error ? err.message : 'Error al remover voto',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -70,8 +70,8 @@ export const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||||||
|
|
||||||
const handleResetAllVotes = async () => {
|
const handleResetAllVotes = async () => {
|
||||||
const trimmedPass = password.trim();
|
const trimmedPass = password.trim();
|
||||||
if (trimmedPass !== 'REMOVERVOTOE3' && trimmedPass !== 'RESETEOE3') {
|
if (!trimmedPass) {
|
||||||
setAdminMsg({ text: 'Ingresa la contraseña "REMOVERVOTOE3" para vaciar todos los votos.', type: 'error' });
|
setAdminMsg({ text: 'Ingresa la contraseña de autorización para vaciar todos los votos.', type: 'error' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||||||
setPassword('');
|
setPassword('');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setAdminMsg({
|
setAdminMsg({
|
||||||
text: `Error al vaciar votos: ${err instanceof Error ? err.message : 'Error desconocido'}`,
|
text: err instanceof Error ? err.message : 'Error al vaciar votos',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -378,7 +378,7 @@ const VOTOS_EMBEBIDOS = ${JSON.stringify(rawVotes, null, 2)};`;
|
|||||||
Remover Voto Individual o de Pruebas (DELETE)
|
Remover Voto Individual o de Pruebas (DELETE)
|
||||||
</label>
|
</label>
|
||||||
<span className="text-[10px] font-mono text-amber-400/80 bg-amber-400/10 px-2 py-0.5 rounded">
|
<span className="text-[10px] font-mono text-amber-400/80 bg-amber-400/10 px-2 py-0.5 rounded">
|
||||||
Clave requerida: REMOVERVOTOE3
|
Requiere clave de autorización
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -403,7 +403,7 @@ const VOTOS_EMBEBIDOS = ${JSON.stringify(rawVotes, null, 2)};`;
|
|||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Clave: REMOVERVOTOE3"
|
placeholder="Contraseña de autorización"
|
||||||
className="w-full px-3 py-2.5 bg-white/[0.04] border border-white/10 rounded-xl text-white text-xs focus:border-[#00B7D4] focus:outline-none"
|
className="w-full px-3 py-2.5 bg-white/[0.04] border border-white/10 rounded-xl text-white text-xs focus:border-[#00B7D4] focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ export const VotingStep: React.FC<VotingStepProps> = ({
|
|||||||
const handleRemoveExistingVote = async () => {
|
const handleRemoveExistingVote = async () => {
|
||||||
if (!normalizedVoter) return;
|
if (!normalizedVoter) return;
|
||||||
const trimmedPass = removePassword.trim();
|
const trimmedPass = removePassword.trim();
|
||||||
if (trimmedPass !== 'REMOVERVOTOE3' && trimmedPass !== 'RESETEOE3') {
|
if (!trimmedPass) {
|
||||||
setRemoveError('Contraseña incorrecta. Se requiere "REMOVERVOTOE3".');
|
setRemoveError('Ingresa la contraseña de autorización.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ export const VotingStep: React.FC<VotingStepProps> = ({
|
|||||||
type="password"
|
type="password"
|
||||||
value={removePassword}
|
value={removePassword}
|
||||||
onChange={(e) => setRemovePassword(e.target.value)}
|
onChange={(e) => setRemovePassword(e.target.value)}
|
||||||
placeholder="Clave: REMOVERVOTOE3"
|
placeholder="Contraseña de autorización"
|
||||||
className="flex-1 px-3 py-2 bg-black/60 border border-red-500/30 rounded-lg text-white text-xs placeholder:text-gray-500 focus:outline-none focus:border-red-400"
|
className="flex-1 px-3 py-2 bg-black/60 border border-red-500/30 rounded-lg text-white text-xs placeholder:text-gray-500 focus:outline-none focus:border-red-400"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|||||||
+2
-2
@@ -80,7 +80,7 @@ export class ApiService {
|
|||||||
/**
|
/**
|
||||||
* Restablece el voto de un usuario específico (Administrador / Pruebas)
|
* Restablece el voto de un usuario específico (Administrador / Pruebas)
|
||||||
*/
|
*/
|
||||||
public static async deleteVote(voterName: string, adminKey: string = 'REMOVERVOTOE3'): Promise<boolean> {
|
public static async deleteVote(voterName: string, adminKey: string): Promise<boolean> {
|
||||||
const voter = voterName.trim().toUpperCase();
|
const voter = voterName.trim().toUpperCase();
|
||||||
if (!voter) {
|
if (!voter) {
|
||||||
throw new Error('Debes especificar el nombre del votante a restablecer.');
|
throw new Error('Debes especificar el nombre del votante a restablecer.');
|
||||||
@@ -105,7 +105,7 @@ export class ApiService {
|
|||||||
/**
|
/**
|
||||||
* Restablece / elimina TODOS los votos (Administrador / Limpieza de Pruebas)
|
* Restablece / elimina TODOS los votos (Administrador / Limpieza de Pruebas)
|
||||||
*/
|
*/
|
||||||
public static async deleteAllVotes(adminKey: string = 'REMOVERVOTOE3'): Promise<{ success: boolean; count: number }> {
|
public static async deleteAllVotes(adminKey: string): Promise<{ success: boolean; count: number }> {
|
||||||
const response = await fetch('/api/votes', {
|
const response = await fetch('/api/votes', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
Reference in New Issue
Block a user