Metadata-Version: 2.4
Name: django-username-guard
Version: 0.2.0
Summary: Block deceptive look-alike usernames (admin/support impersonation, brand homoglyphs, leet, typosquatting) at registration time.
Author-email: Vivien Giraud <claude@viviengiraud.fr>
License: MIT
Project-URL: Homepage, https://github.com/viviengiraud/django-username-guard
Project-URL: Repository, https://github.com/viviengiraud/django-username-guard
Project-URL: Issues, https://github.com/viviengiraud/django-username-guard/issues
Project-URL: Changelog, https://github.com/viviengiraud/django-username-guard/blob/main/CHANGELOG.md
Keywords: django,username,validation,homoglyph,typosquatting,phishing,impersonation,security
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=4.2
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-django>=4; extra == "dev"
Dynamic: license-file

# django-username-guard

[![PyPI version](https://img.shields.io/pypi/v/django-username-guard.svg)](https://pypi.org/project/django-username-guard/)
[![Python versions](https://img.shields.io/pypi/pyversions/django-username-guard.svg)](https://pypi.org/project/django-username-guard/)
[![Django versions](https://img.shields.io/pypi/djversions/django-username-guard.svg)](https://pypi.org/project/django-username-guard/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![CI](https://github.com/viviengiraud/django-username-guard/actions/workflows/ci.yml/badge.svg)](https://github.com/viviengiraud/django-username-guard/actions/workflows/ci.yml)

Bloque la création de comptes Django avec des noms d'utilisateur **trompeurs** :
mots réservés (`admin`, `support`, `noreply`...), **et** leurs variantes
typosquattées : `admin1strateur`, `adminiistrateur`, `admlnistrateur`,
`аdmin` (cyrillique), `4dmin`, etc. — **et** l'imitation de marques
(`leboncoin-support`, `lebоncoin`, `service_leboncoin`...).

## Pourquoi ?

Sur les places de marché type Leboncoin / Vinted, une partie des arnaques
([exemple Numerama : « arnaque à l'IBAN »](https://www.numerama.com/cyberguerre/2238445-tout-portait-a-croire-que-cetait-reglo-on-vous-raconte-larnaque-a-liban-qui-piege-les-vendeurs-leboncoin.html))
repose sur des comptes qui se font passer pour le **support officiel** de
la plateforme — souvent en exploitant des homoglyphes Unicode, du leet, ou
des fautes de frappe sur la marque. Cet addon empêche la création initiale
de tels comptes.

> ⚠️ Ce n'est **pas** une solution complète anti-phishing — c'est une
> couche parmi d'autres (badges officiels, vérification d'identité,
> détection de comportement, modération…). Mais bloquer ces noms à
> l'inscription élimine un vecteur d'ingénierie sociale très bon marché
> pour l'attaquant.

## Comment ça marche

Trois couches de défense, toutes appliquées sur la forme **normalisée** du
username :

1. **Normalisation** — NFKC → casefold → mappage des homoglyphes
   (cyrillique, grec, IPA) → suppression des accents → dé-leet
   (`4→a`, `1→i`, `0→o`, `@→a`...) → suppression des non-alphanumériques →
   collapse des lettres répétées (`admiiin` → `admin`).
2. **Match exact / sous-chaîne / fuzzy** contre la **blocklist**
   (`admin`, `support`...) : Damerau-Levenshtein ≤ 1, sous-chaîne avec
   budget de longueur (`admin42` bloqué, `adminlovesmusic` autorisé).
3. **Protection de marque** — pour les noms que tu déclares dans `BRANDS`,
   match plus strict : **toute sous-chaîne** ou variante fuzzy est bloquée
   (pas de budget de longueur). `leboncoin_helper`, `support-leboncoin`,
   `lebоncoin` (cyrillique), `leb0ncoin`, tous bloqués.

## Installation

```bash
pip install django-username-guard
```

```python
# settings.py
INSTALLED_APPS = [
    ...,
    "username_guard",
]
```

## Utilisation

### Sur ton modèle `User` personnalisé

```python
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from username_guard import DeceptiveUsernameValidator


class User(AbstractUser):
    username_validators = [
        *AbstractUser.username_validators,
        DeceptiveUsernameValidator(),
    ]
```

### Sur ton formulaire d'inscription

```python
from username_guard.forms import GuardedUserCreationForm
# Drop-in replacement de UserCreationForm
```

### Manuel

```python
from django.core.exceptions import ValidationError
from username_guard import DeceptiveUsernameValidator

validate = DeceptiveUsernameValidator()
try:
    validate("admin1strateur")
except ValidationError as e:
    print(e.code, e.message)  # deceptive_username | brand_impersonation
```

### CLI (auditer une base existante)

```bash
python manage.py check_username admin1strateur leboncoin-support alice --show-normalized
```

## Configuration

Tout est optionnel (`settings.py`) :

```python
USERNAME_GUARD = {
    # Termes réservés. Override complet :
    # "BLOCKLIST": ("admin", "support", ...),

    # Plus simple — ajouter à la liste par défaut EN/FR :
    "EXTRA_BLOCKLIST": ("ceo", "founder", "monequipe"),

    # Marques que tu veux protéger d'usurpation. RÈGLE STRICTE :
    # toute sous-chaîne ou typo proche est rejetée.
    "BRANDS": ("leboncoin", "monsite", "monapp"),

    # Distance d'édition tolérée (0 = match exact uniquement)
    "MAX_DISTANCE": 1,

    # Rejeter les sous-chaînes (admin1, admin_, xadmin) ?
    "REJECT_SUBSTRING": True,

    # Budget de longueur pour la règle sous-chaîne (BLOCKLIST uniquement)
    # admin (5) + 3 = 8 caractères max -> "admin42" bloqué, "adminlover" non
    "SUBSTRING_PADDING": 3,

    # Taille minimale d'un terme blocklist pour activer la règle sous-chaîne
    "MIN_TERM_LEN_FOR_SUBSTRING": 4,
}
```

## Exemples couverts

| Username                  | Bloqué ? | Code                  | Pourquoi |
|---------------------------|----------|-----------------------|----------|
| `admin`                   | ✅ | `deceptive_username`     | match exact |
| `Administrateur`          | ✅ | `deceptive_username`     | casefold |
| `admin1strateur`          | ✅ | `deceptive_username`     | leet `1→i` |
| `adminiistrateur`         | ✅ | `deceptive_username`     | `ii→i` |
| `admlnistrateur`          | ✅ | `deceptive_username`     | DL=1 |
| `аdmin` (cyrillique)      | ✅ | `deceptive_username`     | homoglyphe |
| `4dmin`                   | ✅ | `deceptive_username`     | leet `4→a` |
| `r00t`                    | ✅ | `deceptive_username`     | leet `0→o` |
| `support1`                | ✅ | `deceptive_username`     | sous-chaîne bornée |
| `leboncoin` (BRANDS)      | ✅ | `brand_impersonation`    | match exact |
| `leboncoin-support`       | ✅ | `brand_impersonation`    | sous-chaîne marque |
| `lebоncoin` (cyrillique)  | ✅ | `brand_impersonation`    | homoglyphe `о→o` |
| `leb0ncoin`               | ✅ | `brand_impersonation`    | leet sur la marque |
| `service_leboncoin`       | ✅ | `brand_impersonation`    | sous-chaîne marque |
| `adminlovesmusic`         | ❌ |                          | trop long, plausible |
| `vivien`, `alice`         | ❌ |                          | légitime |

## Threat model

**Couvert** :
- Imitation de comptes admin / support génériques
- Imitation de marque via homoglyphes Unicode (cyrillique, grec, IPA)
- Leet-speak (`4`, `1`, `0`, `@`, `$`...)
- Lettres dupliquées (`adminiistrateur`)
- Fautes de frappe à 1 édition (`admlnistrateur`)
- Bruit de ponctuation (`ad-min_42`)

**Non couvert (par design)** :
- Les noms parfaitement légitimes qui contiennent un mot-clé en sous-chaîne
  longue (`adminlovesmusic`). Active `MIN_TERM_LEN_FOR_SUBSTRING` plus bas
  ou ajoute le terme dans `BRANDS` si ton domaine l'exige.
- Les attaques runtime (account takeover, changement de pseudo après
  vérification, badge officiel falsifié dans l'UI) — c'est hors-scope d'un
  validateur de création.
- La couverture complète d'UTS #39. Branche [`confusable_homoglyphs`](https://pypi.org/project/confusable-homoglyphs/)
  si tu en as besoin (PR bienvenue pour rendre ça optionnel).

## Performance

DL distance pure-Python lancée contre chaque terme : O(n × m) par username.
Avec une blocklist de < 200 termes c'est négligeable (< 0.5 ms).
Si tu pousses au-delà, on peut ajouter un index BK-tree — ouvre une issue.

## Développement

```bash
git clone https://github.com/viviengiraud/django-username-guard
cd django-username-guard
uv sync --all-extras
uv run pytest
```

Voir [CONTRIBUTING.md](CONTRIBUTING.md).

## Licence

[MIT](LICENSE).
