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 )