ENRE_WEB/gestor/config.go
SITO 7d5dbeec60 Cuenta de externas, arreglos de seguridad, títulos arriba y despliegue
- Rol «externas» para la asamblea de externas: pide sin cupo, siempre con el
  colectivo de fuera y su contacto, y sus solicitudes salen en naranja hasta
  que la asamblea de la Enre las confirma.
- /pedir/ explica en una línea que los colectivos externos piden fecha en la
  asamblea de externas, los lunes de 19:00 a 20:00.
- Seguridad: argon2 limitado a dos cálculos a la vez y el intento se cuenta
  antes de calcular (60 accesos simultáneos llevaban el proceso a 3,8 GB);
  bloqueo por cuenta a 20 fallos para que no se pueda dejar fuera a la
  asamblea; avisos por sesión en vez de por URL; sig sin «..»; eventos de
  31 días como mucho; parámetros de argon2 acotados.
- Títulos pegados arriba en todas las páginas; la franja de la enredadera
  recupera ese alto para seguir llenando la pantalla.
- despliegue/: instalar.sh, cuenta.sh, servicio de systemd con el proceso
  encerrado, fragmento de nginx que además deja de servir .git y las notas
  internas, y configuración de ejemplo. Ninguna contraseña en el repositorio.
2026-09-23 12:15:33 +02:00

99 lines
2.8 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"time"
)
type Config struct {
// Red
Escucha string `json:"escucha"` // siempre local: nginx hace de puerta
BaseURL string `json:"base_url"` // https://www.laenre.net, para comprobar el Origen
// Rutas
DirDatos string `json:"dir_datos"` // registro.jsonl
DirPlantillas string `json:"dir_plantillas"` // plantillas de /pedir/
DirEstatico string `json:"dir_estatico"` // raiz de la web, solo en modo local
LogAuth string `json:"log_auth"` // el registro que lee fail2ban
// Comportamiento
CookieSegura bool `json:"cookie_segura"` // false solo para probar sin TLS
Local bool `json:"local"` // sirve tambien la web estatica
MesesVista int `json:"meses_vista"` // meses que pinta /pedir/
Reglas Reglas `json:"reglas"`
}
func ConfigPorDefecto() Config {
return Config{
Escucha: "127.0.0.1:8090",
BaseURL: "https://www.laenre.net",
DirDatos: "/var/lib/enrecal",
DirPlantillas: "/opt/enrecal/plantillas",
DirEstatico: "/var/www/laenre",
LogAuth: "/var/log/enrecal/auth.log",
CookieSegura: true,
MesesVista: 4,
Reglas: ReglasPorDefecto(),
}
}
func CargarConfig(ruta string) (Config, error) {
c := ConfigPorDefecto()
if ruta == "" {
return c, nil
}
b, err := os.ReadFile(ruta)
if err != nil {
return c, err
}
dec := json.NewDecoder(bytes.NewReader(b))
dec.DisallowUnknownFields()
if err := dec.Decode(&c); err != nil {
return c, fmt.Errorf("%s: %w", ruta, err)
}
return c, c.Validar()
}
func (c Config) Validar() error {
if c.Reglas.CupoDias < 1 {
return fmt.Errorf("cupo_dias tiene que ser 1 o mas")
}
if c.Reglas.MesesVentana < 1 {
return fmt.Errorf("meses_ventana tiene que ser 1 o mas")
}
if c.Reglas.DiasMinimoConfirmar < 0 {
return fmt.Errorf("dias_minimo_confirmar no puede ser negativo")
}
if c.Reglas.DiasMaximoPendiente < 1 {
return fmt.Errorf("dias_maximo_pendiente tiene que ser 1 o mas")
}
if len(c.Reglas.DiasReservables) == 0 {
return fmt.Errorf("dias_reservables no puede estar vacio")
}
if _, err := c.Reglas.AnclaFinde.Time(); err != nil {
return fmt.Errorf("ancla_finde: %w", err)
}
if c.Reglas.TurnoAncla != Tranqui && c.Reglas.TurnoAncla != Ruido {
return fmt.Errorf("turno_ancla solo puede ser %q o %q", Tranqui, Ruido)
}
if c.MesesVista < 1 {
return fmt.Errorf("meses_vista tiene que ser 1 o mas")
}
return nil
}
const (
inactividadSesion = 8 * time.Hour
maxIntentos = 5
// Por cuenta el tope es más alto que por IP: si fuera 5, cualquiera podría
// dejar a la asamblea sin entrar fallando 5 veces cada cuarto de hora. Con
// argon2 y claves de 12 caracteres, 20 intentos cada 10 minutos no bastan
// para adivinar nada.
maxIntentosCuenta = 20
ventanaIntentos = 10 * time.Minute
esperaBloqueo = 15 * time.Minute
)