chore: add new routes for addresses

This commit is contained in:
2026-02-25 09:55:18 +01:00
parent 03cb05f3a5
commit 1376ce3583
8 changed files with 292 additions and 4 deletions
+142
View File
@@ -0,0 +1,142 @@
---
# ============================================
# Fail2Ban Installation et configuration
# ============================================
- name: Installation et configuration de Fail2Ban
hosts: uber-stup
become: true
gather_facts: true
vars:
fail2ban_bantime: "1h"
fail2ban_findtime: "10m"
fail2ban_maxretry: 5
fail2ban_ignoreip: "127.0.0.1/8 ::1"
tasks:
# ============================================================
# Installation
# ============================================================
- name: Installer Fail2Ban
ansible.builtin.apt:
name: fail2ban
state: present
update_cache: yes
# ============================================================
# Configuration globale (jail.local)
# ============================================================
- name: Créer jail.local avec la configuration globale
ansible.builtin.copy:
dest: /etc/fail2ban/jail.local
owner: root
group: root
mode: "0644"
content: |
[DEFAULT]
ignoreip = {{ fail2ban_ignoreip }}
bantime = {{ fail2ban_bantime }}
findtime = {{ fail2ban_findtime }}
maxretry = {{ fail2ban_maxretry }}
banaction = iptables-multiport
backend = systemd
loglevel = INFO
# ============================================================
# SSH
# ============================================================
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
bantime = 24h
# ============================================================
# Nginx - requêtes trop fréquentes (rate limit)
# ============================================================
[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
bantime = 1h
# ============================================================
# Nginx - 4xx répétés (scan, bot)
# ============================================================
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 1h
# ============================================================
# Nginx - bad bots / scans (404 répétés)
# ============================================================
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 10
bantime = 2h
# ============================================================
# API Backend - brute force login (POST /api/v2/login)
# ============================================================
[backend-login]
enabled = true
port = http,https
filter = backend-login
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 5m
bantime = 2h
notify: Restart fail2ban
# ============================================================
# Filtre personnalisé - brute force login API
# ============================================================
- name: Créer le filtre fail2ban pour le login API
ansible.builtin.copy:
dest: /etc/fail2ban/filter.d/backend-login.conf
owner: root
group: root
mode: "0644"
content: |
[Definition]
failregex = ^<HOST> .* "POST /api/v2/login HTTP/.*" (401|403) .*$
ignoreregex =
notify: Restart fail2ban
# ============================================================
# Démarrage et activation
# ============================================================
- name: Démarrer et activer Fail2Ban
ansible.builtin.systemd:
name: fail2ban
state: started
enabled: yes
# ============================================================
# Vérification
# ============================================================
- name: Vérifier le statut de Fail2Ban
ansible.builtin.command: fail2ban-client status
register: fail2ban_status
changed_when: false
- name: Afficher le statut des jails
ansible.builtin.debug:
msg: "{{ fail2ban_status.stdout_lines }}"
handlers:
- name: Restart fail2ban
ansible.builtin.systemd:
name: fail2ban
state: restarted
+74
View File
@@ -120,6 +120,80 @@
privs: ALL privs: ALL
state: present state: present
# ============================================================
# Données initiales - corrections d'adresses
# ============================================================
- name: Insérer les corrections d'adresses
become_user: postgres
ansible.builtin.postgresql_query:
db: "{{ db_name }}"
query: |
INSERT INTO adresse_correction (invalid_address, correct_address) VALUES
('6 Place Alfred Lallié 44000 Nantes', '13 Rue Saint-Rogatien 44000 Nantes'),
('26 boulevard Vincent gâche 44200', '62 Rue des Français Libres 44200'),
('41 quai Malakoff 44000', '41 rue du pré Gauchet 44000'),
('au 6 rue du Rhône 44100', '106 rue du bois Hardy 44100'),
('10 allée de l''île gloriette 44000', '8 rue Deurbroucq 44000'),
('1 Ter Boulevard de Launay 44100', '5 Rue Lavoisier 44100'),
('6 Boulevard de la prairie au duc 44200', '10 Rue Alain Barbe Torte 44200'),
('4 rue de mauves , 44470 thouaré sur Loire', '4 Rue de la Halbarderie 44470 Thouaré-sur-Loire'),
('26 rue Louis blanc, 44200 Nantes', '6 Rue de Hercé 44200 Nantes'),
('4 rue la tour d''Auvergne 44200 nantes', '6 Rue de Hercé 44200 Nantes'),
('12 rue d''Orléans 44000', '10 rue de l''industrie 44000'),
('3 rue Bertrand Geslin 44000 Nantes', '10 Rue de Sévigné 44000 Nantes'),
('33 boulevard de doulon 44300', '3 Avenue du Forgeron 44300 Nantes'),
('2 rue Stanislas Baudry 44000 Nantes', '9 Rue de Richebourg 44000 Nantes'),
('8 Place Saint-Pierre, 44000 Nantes', '2 Rue Saint-Denis 44000 Nantes'),
('20 boulevard du petit port 44300', '3 Rue du Transvaal 44300 Nantes'),
('5 cours Sully 44000 Nantes', '75 Rue Préfet Bonnefoy 44000'),
('50 rue Paul Bellamy 44000', '2 rue du docteur Heurteaux 44000'),
('33 rue de la petite sensive 44300', '8 Rue du Docteur Chenantais 44300'),
('45 rue Louis guiotton 44300 Nantes', '59 Rue de Nancy 44300 Nantes'),
('6 rue Meuris 44000 Nantes', '3 Rue Daubenton 44100 Nantes'),
('26 rue Félix Faure 44000', '12 rue bonaparte 44000'),
('1 rue Charles Dullin 44100 nantes', '12 Avenue de l''Églantine 44800 Saint-Herblain'),
('16 rue Anatole de monzie 44200', '1 Rue Georges Leygues 44200 Nantes'),
('52 boulevard de l''Estuaire 44200', '1 Rue Ruth First 44200 Nantes'),
('186 Route de Saint-Joseph 44300 Nantes', 'Rue de Pontecorvo 44300 Nantes'),
('16 Rue Jules Launey 44100 Nantes', '5 Chemin du Pressoir Chênaie 44100 Nantes'),
('1 rue maréchal Lyautey 44000', '9 rue Louis Mékarski 44000'),
('74 boulevard de la prairie au duc 44200', '7 Rue René Siegfried 44200'),
('1 impasse du baut 44300 Nantes', '8 Avenue d''Assise 44300 Nantes'),
('5 rue moquechien, 44000 Nantes', '4 Rue Adolphe Moitié 44000 Nantes'),
('21 chaussée de la madeleine 44000', '4 bis Rue Perrault 44000 Nantes'),
('31 boulevard Michelet 44000', '26 Rue de la Haute Forêt 44000 Nantes'),
('95 Boulevard longchamp 44300', '7 Petite Avenue de Longchamp 44300'),
('7 Quai Marcel Boissard, 44400 Rezé', '6 Rue Jean Jouneau 44400 Rezé'),
('43 avenue de la libération 44400', '10 rue de l''Erdronnière 44400'),
('165 route de rennes 44700 orvault', '5 Rue de l''Ascension 44700 Orvault'),
('17rue yves bodiguel 44000', '8 Rue Frédureau 44000 Nantes'),
('176 boulevard Jules verne 44300', '44 Rue des Platanes 44300 Nantes'),
('133 Boulevard Jules Verne 44300', '28 Rue Moriceau Thébault 44300'),
('53 Boulevard Jules Verne 44300', '66 Rue de la Moisdonnière 44300'),
('7 chaussé de la madeleine, 44000', '3 Rue Perrault 44000 Nantes'),
('LA CHEVROLIÈRE', '12 Rue des Ajoncs 44400 Rezé'),
('9 rue Delattre de tassigny 44000 nantes', '1 Rue des Cadeniers 44000 Nantes'),
('14 place de l''Oratoire 44000 Nantes', '2 Rue Élie Delaunay 44000 Nantes'),
('19 allée Baco, 44000 Nantes', '23 Rue Crucy 44000 Nantes'),
('21bd babin chevaye 44200', '7 Rue de Hercé 44200 Nantes'),
('49 boulevard dalby 44000', '51 Rue de la Ville en Pierre 44000'),
('96 route de Vannes 44100', '14 Avenue des Coucous 44300'),
('1 rue du Luxembourg 44000 Nantes', '5 Rue de l''Indre 44000 Nantes'),
('3 rue de bretagne 44880 Sautron', '9 Rue de Flore 44880 Sautron'),
('42 rue paul bellamy 44000 nantes', '5 Rue de Bouillé 44000 Nantes'),
('125 Rue Paul Bellamy 44000 Nantes', '14 Rue Anatole le Braz 44000 Nantes'),
('214 Rue Paul Bellamy 44000 Nantes', '17 Rue de la Saulzinière 44000 Nantes'),
('49 Boulevard Robert Schuman 44300 Nantes', '47 Rue Georges Bizot 44300 Nantes'),
('142 Boulevard Robert Schuman 44300', '10 Rue St-Mihiel 44300 Nantes'),
('23 route de la chapelle sur erdre 44300', '14 Rue de la Boulonnerie 44300 Nantes'),
('27 Rue Pierre Blard 44800 Saint-Herblain', '2 Rue Louis Boutin 44800 Saint-Herblain'),
('109 Boulevard Ernest Dalby 44000 Nantes', '53 bis rue de la ville en pierre 44000'),
('13 rue pitre chevalier 44000', '10 Rue Jean-Émile Laboureur 44000'),
('Rue lesage - 44000 - Nantes', '4 rue Maurice Sibille 44000'),
('86 Rue de la Bourgeonnière 44300', '6 Avenue de l''Alverne 44300'),
('8 boulevard Émile romanet 44100', '33 Route de St-Herblain 44100 Nantes')
ON CONFLICT (invalid_address) DO NOTHING;
# ============================================================ # ============================================================
# Authentification pg_hba.conf # Authentification pg_hba.conf
# ============================================================ # ============================================================
+18
View File
@@ -47,3 +47,21 @@ func (d *Database) DeleteAddress(InvalidAddressByAdmin string, CorrectAddressByA
} }
return nil return nil
} }
func (d *Database) AllAddress() ([]models.Address, error) {
rows, err := d.DB.Query("SELECT invalid_address, correct_address FROM adresse_correction")
if err != nil {
return nil, fmt.Errorf("getAllAddress: %w", err)
}
defer rows.Close()
var addresses []models.Address
for rows.Next() {
var a models.Address
if err := rows.Scan(&a.InvalidAddress, &a.CorrectAddress); err != nil {
return nil, fmt.Errorf("getAllAddress scan: %w", err)
}
addresses = append(addresses, a)
}
return addresses, rows.Err()
}
+1 -1
View File
@@ -255,7 +255,7 @@ func (db *Database) createTables() error {
`CREATE TABLE IF NOT EXISTS adresse_correction ( `CREATE TABLE IF NOT EXISTS adresse_correction (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
invalid_address VARCHAR(255) NOT NULL UNIQUE, invalid_address VARCHAR(255) NOT NULL UNIQUE,
correct_address VARCHAR(255) NOT NULL UNIQUE, correct_address VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);`, );`,
+17
View File
@@ -57,3 +57,20 @@ func DeleteAddress(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Adresses supprimées avec succès"}) c.JSON(http.StatusOK, gin.H{"message": "Adresses supprimées avec succès"})
} }
func GetAllAddress(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
userRole := c.GetString("role")
if userRole != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Accès réservé aux administrateurs"})
return
}
getAddress, err := database.AllAddress()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"addresses": getAddress})
}
+1
View File
@@ -135,6 +135,7 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
// ============================================ // ============================================
adminGroupV2.POST("/add/address", handlers.AddAddress) adminGroupV2.POST("/add/address", handlers.AddAddress)
adminGroupV2.DELETE("/delete/address", handlers.DeleteAddress) adminGroupV2.DELETE("/delete/address", handlers.DeleteAddress)
adminGroupV2.GET("/addresses", handlers.GetAllAddress)
// ============================================ // ============================================
// PRODUITS - GESTION // PRODUITS - GESTION
// ============================================ // ============================================
+7
View File
@@ -503,6 +503,13 @@ export const deleteAddress = async (
return { success: true, message: data.message }; return { success: true, message: data.message };
}; };
export const getAllAddresses = async (): Promise<
{ invalid_address: string; correct_address: string }[]
> => {
const { data } = await apiClient.get(`${V2}/admin/protected/addresses`);
return data.addresses ?? [];
};
// ============================================ // ============================================
// STATS HELPERS // STATS HELPERS
// ============================================ // ============================================
@@ -1,15 +1,16 @@
import React, { useState, useCallback, useMemo } from "react"; import React, { useState, useEffect, useCallback, useMemo } from "react";
import { import {
View, View,
Text, Text,
StyleSheet, StyleSheet,
FlatList, FlatList,
TouchableOpacity, TouchableOpacity,
RefreshControl,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize } from "../../theme"; import { spacing, fontSize } from "../../theme";
import { useTheme } from "../../context/ThemeContext"; import { useTheme } from "../../context/ThemeContext";
import { addAddress, deleteAddress } from "../../api/api_admin"; import { addAddress, deleteAddress, getAllAddresses } from "../../api/api_admin";
import { useAlert } from "../../hooks/useAlert"; import { useAlert } from "../../hooks/useAlert";
import Card from "../../components/ui/Card"; import Card from "../../components/ui/Card";
import Modal from "../../components/ui/Modal"; import Modal from "../../components/ui/Modal";
@@ -27,11 +28,34 @@ export default function AddressScreen() {
const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert(); const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert();
const [addresses, setAddresses] = useState<AddressEntry[]>([]); const [addresses, setAddresses] = useState<AddressEntry[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [addModal, setAddModal] = useState(false); const [addModal, setAddModal] = useState(false);
const [invalidInput, setInvalidInput] = useState(""); const [invalidInput, setInvalidInput] = useState("");
const [correctInput, setCorrectInput] = useState(""); const [correctInput, setCorrectInput] = useState("");
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const loadAddresses = useCallback(async () => {
try {
const result = await getAllAddresses();
setAddresses(result);
} catch (e: any) {
showError("Erreur", e.response?.data?.error ?? e.message);
} finally {
setLoading(false);
}
}, [showError]);
useEffect(() => {
loadAddresses();
}, [loadAddresses]);
const onRefresh = async () => {
setRefreshing(true);
await loadAddresses();
setRefreshing(false);
};
const openAddModal = () => { const openAddModal = () => {
setInvalidInput(""); setInvalidInput("");
setCorrectInput(""); setCorrectInput("");
@@ -167,9 +191,14 @@ export default function AddressScreen() {
keyExtractor={(item, i) => `${item.invalid_address}-${i}`} keyExtractor={(item, i) => `${item.invalid_address}-${i}`}
renderItem={renderItem} renderItem={renderItem}
contentContainerStyle={{ padding: spacing.l, paddingTop: spacing.s }} contentContainerStyle={{ padding: spacing.l, paddingTop: spacing.s }}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
ListEmptyComponent={ ListEmptyComponent={
<Text style={styles.empty}> <Text style={styles.empty}>
Aucune correction.{"\n"}Appuyez sur + pour en ajouter une. {loading
? "Chargement..."
: "Aucune correction.\nAppuyez sur + pour en ajouter une."}
</Text> </Text>
} }
/> />