chore: add new routes for addresses
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
);`,
|
);`,
|
||||||
|
|||||||
@@ -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})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user