diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index cb74695..90478d2 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -43,7 +43,7 @@ jobs: - name: Lancer les tests avec couverture env: - # Clé Fernet éphémère : les tests chiffrent des mots de passe de sites. + # Clé Fernet éphémère : les tests chiffrent des clés API IA. CANDITRACK_FERNET_KEY: ${{ secrets.SONAR_FERNET_KEY }} run: | # Génère une clé Fernet jetable si aucun secret n'est fourni. diff --git a/CHANGELOG.md b/CHANGELOG.md index b054f16..cee4fbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ release du même nom. ## [Non publié] — 1.2.0 +- #43 — Sites d'emploi : suppression de la gestion des **identifiants et mots de + passe**. Seuls le nom, l'URL et le logo d'un site sont désormais conservés ; + les champs `username`/`password` (et leur stockage chiffré) sont retirés du + modèle, du formulaire et de la liste. - #41 — Ajout d'un skill Claude Code `web-development` (`.claude/skills/web-development/`) qui formalise les principes de conception de CandiTrack (thème clair/sombre, connexion aux IA, présentation UI, diff --git a/CLAUDE.md b/CLAUDE.md index 4f74aed..10b50ca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,8 @@ docker compose up -d --build # → http://127.0.0.1:53487/ ## Modèles (`tracking/models.py`) -`JobSite` (mot de passe chiffré, `is_builtin`, `logo_url`), `Candidature` (cœur +`JobSite` (nom, URL, `is_builtin`, `logo_url` — plus d'identifiants depuis +l'issue #43), `Candidature` (cœur du suivi, étapes de progression + `motif_cloture` = clôture), `StatusHistory`, `Reminder`, `Interview`, `Contact`, `ApiToken`, `CV`, `AIConfig` (singleton de config du coaching IA, clé Gemini chiffrée — issue #33). Énumérations diff --git a/README.md b/README.md index c31e12c..8ad3eae 100644 --- a/README.md +++ b/README.md @@ -186,9 +186,8 @@ chacun renseigne **sa propre clé API**. **Options → IA**, à côté des infos de quota du tier gratuit). 2. Dans **Options → IA**, choisir le **fournisseur** et coller la clé (et, facultativement, le modèle dans le menu déroulant). Chaque fournisseur garde - sa propre clé, son modèle et sa limite, stockés **chiffrés** en base (Fernet, - comme les mots de passe des sites) ; on bascule de l'un à l'autre sans - ressaisie. + sa propre clé, son modèle et sa limite, stockés **chiffrés** en base + (Fernet) ; on bascule de l'un à l'autre sans ressaisie. 3. Depuis la liste des candidatures, **« ✨ Coaching IA »** ouvre une fenêtre modale : à partir du dernier CV chargé (analysé par Gemini) et des statistiques (volume, motifs de refus, délais…), l'IA propose un positionnement et des diff --git a/templates/tracking/site_list.html b/templates/tracking/site_list.html index f56303f..7379d9f 100644 --- a/templates/tracking/site_list.html +++ b/templates/tracking/site_list.html @@ -5,13 +5,12 @@

Sites d'emploi

+ Ajouter un site -

Les identifiants sont stockés avec le mot de passe chiffré au repos. - Le logo (favicon du site) est récupéré automatiquement à l'enregistrement.

+

Le logo (favicon du site) est récupéré automatiquement à l'enregistrement.

{% if sites %} - + {% for s in sites %} @@ -20,8 +19,6 @@

Sites d'emploi

{% if s.is_builtin %} défaut{% endif %} {% if not s.actif %} désactivé{% endif %}
- -
LogoNomURLIdentifiantMot de passeActions
LogoNomURLActions
{% if s.url %}{{ s.url }}{% else %}—{% endif %}{{ s.username|default:"—" }}{% if s.password %}••••••{% else %}{% endif %} Modifier
diff --git a/tracking/forms.py b/tracking/forms.py index 0656b01..165f776 100644 --- a/tracking/forms.py +++ b/tracking/forms.py @@ -69,20 +69,13 @@ def save(self, commit=True): class JobSiteForm(forms.ModelForm): """Manual create/edit form for a job site (issue #366). - - The password is never pre-filled; leaving it blank on edit keeps the - stored (encrypted) value. - - When ``auto_logo`` is checked the logo is (re)fetched from the URL. + Les identifiants/mots de passe ne sont plus gérés (issue #43). Le logo est + récupéré depuis le favicon du site quand l'utilisateur n'en saisit pas. """ - password = forms.CharField( - label="Mot de passe", - required=False, - widget=forms.PasswordInput(render_value=False), - help_text="Laisser vide pour conserver le mot de passe actuel.", - ) class Meta: model = JobSite - fields = ["name", "url", "username", "password", "logo_url"] + fields = ["name", "url", "logo_url"] widgets = { "url": forms.URLInput(attrs={"placeholder": "https://www.exemple.fr/"}), "logo_url": forms.URLInput( @@ -90,17 +83,9 @@ class Meta: ), } - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Capture the current (decrypted) password so a blank submission keeps it. - self._original_password = self.instance.password if self.instance.pk else "" - def save(self, commit=True): instance = super().save(commit=False) - if not self.cleaned_data.get("password"): - instance.password = self._original_password - # Logo par défaut : le favicon du site (issue #27). On ne l'impose que si # l'utilisateur n'a pas saisi de logo manuel. if not instance.logo_url and instance.url: diff --git a/tracking/migrations/0018_remove_jobsite_password_remove_jobsite_username.py b/tracking/migrations/0018_remove_jobsite_password_remove_jobsite_username.py new file mode 100644 index 0000000..96c89ed --- /dev/null +++ b/tracking/migrations/0018_remove_jobsite_password_remove_jobsite_username.py @@ -0,0 +1,21 @@ +# Generated by Django 6.0.1 on 2026-06-14 12:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tracking', '0017_aiconfig_anthropic_api_key_aiconfig_anthropic_model_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='jobsite', + name='password', + ), + migrations.RemoveField( + model_name='jobsite', + name='username', + ), + ] diff --git a/tracking/models.py b/tracking/models.py index 0cb4e24..3219cf5 100644 --- a/tracking/models.py +++ b/tracking/models.py @@ -2,7 +2,7 @@ The schema anticipates the four board issues: - #365 master plan: Candidature, StatusHistory, Reminder, Interview, Contact -- #366 job sites: JobSite (with an encrypted password) +- #366 job sites: JobSite (name, URL, logo) - #367 statistics: built on top of Candidature aggregates - #368 CV upload: CV """ @@ -23,14 +23,12 @@ class JobSite(models.Model): """A job board where applications are submitted (issue #366). - Credentials are optional; when a password is provided it is stored - encrypted at rest via :class:`EncryptedCharField`. + Les identifiants/mots de passe ne sont plus stockés (issue #43) : on ne + conserve que l'identité du site (nom, URL, logo) et son état. """ name = models.CharField("nom", max_length=100, unique=True) url = models.URLField("URL", blank=True) - username = models.CharField("identifiant", max_length=200, blank=True) - password = EncryptedCharField("mot de passe", blank=True, default="") logo_url = models.URLField("URL du logo", blank=True) is_builtin = models.BooleanField("site par défaut", default=False) # Un site désactivé reste en base mais n'est plus proposé pour de nouvelles