Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public PasswordEncoder passwordEncoder() {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/auth/**", "/css/**", "/js/**").permitAll()
.requestMatchers("/", "/privacy", "/auth/**", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated())
.formLogin(form -> form
.loginPage("/auth/login") // GET: show the login form
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/ericbouchut/learndev/legal/LegalController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.ericbouchut.learndev.legal;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
* <b>Web</b> endpoints for the legal pages.
* Currently the privacy policy (GDPR information duty, art. 13);
* future legal pages (terms of use, legal notice) belong here too.
*/
@Controller
public class LegalController {

/**
* Display the privacy policy page (in French, the language of the
* supervisory framework this policy addresses).
* @return the name of the Thymeleaf template for the privacy policy
*/
@GetMapping("/privacy")
public String privacy() {
return "privacy";
}
}
1 change: 1 addition & 0 deletions src/main/resources/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ <h1>learn-dev</h1>
<ul class="nav__auth">
<li><a th:href="@{/auth/login}">Login</a></li>
<li><a th:href="@{/auth/register}">Register</a></li>
<li><a th:href="@{/privacy}" hreflang="fr">Privacy policy (RGPD)</a></li>
</ul>
</body>
</html>
1 change: 1 addition & 0 deletions src/main/resources/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ <h1>Login</h1>
<button type="submit">Log in</button>
</form>
<p><a th:href="@{/auth/register}">Create an account</a></p>
<p><a th:href="@{/privacy}" hreflang="fr">Privacy policy (RGPD)</a></p>
</body>
</html>
128 changes: 128 additions & 0 deletions src/main/resources/templates/privacy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<!DOCTYPE html>
<html lang="fr" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Politique de confidentialité — learn-dev</title>
</head>
<body>
<main id="main">
<article>
<header>
<h1>Politique de confidentialité</h1>
<p>
Dernière mise à jour : <time datetime="2026-07-09">9 juillet 2026</time>
</p>
<p>
Cette page décrit comment <strong>learn-dev</strong> traite vos données
personnelles, conformément au
<abbr title="Règlement général sur la protection des données">RGPD</abbr>.
</p>
</header>

<section aria-labelledby="controller">
<h2 id="controller">Responsable du traitement</h2>
<address>
Eric Bouchut, contact :
<a href="mailto:ebouchut@gmail.com">ebouchut@gmail.com</a>
</address>
</section>

<section aria-labelledby="data">
<h2 id="data">Données collectées</h2>
<ul>
<li>Identifiant de connexion (nom d'utilisateur) et adresse e-mail&nbsp;;</li>
<li>Prénom et nom (facultatifs)&nbsp;;</li>
<li>Mot de passe, stocké exclusivement sous forme hachée (BCrypt), jamais en clair&nbsp;;</li>
<li>Cookie de session (<code>JSESSIONID</code>)&nbsp;;</li>
<li>Journaux techniques du serveur (adresses IP, horodatages).</li>
</ul>
</section>

<section aria-labelledby="purposes">
<h2 id="purposes">Finalités et bases légales</h2>
<table>
<caption>Pour quoi et sur quel fondement vos données sont traitées</caption>
<thead>
<tr>
<th scope="col">Finalité</th>
<th scope="col">Base légale (art. 6 <abbr title="Règlement général sur la protection des données">RGPD</abbr>)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Création et gestion de votre compte, authentification</td>
<td>Exécution du contrat</td>
</tr>
<tr>
<td>Sécurité de la plateforme et journalisation technique</td>
<td>Intérêt légitime</td>
</tr>
</tbody>
</table>
</section>

<section aria-labelledby="retention">
<h2 id="retention">Durées de conservation</h2>
<!-- TODO(ebouchut): valider ces durées avant la mise en production. -->
<table>
<caption>Combien de temps vos données sont conservées</caption>
<thead>
<tr>
<th scope="col">Données</th>
<th scope="col">Durée</th>
</tr>
</thead>
<tbody>
<tr>
<td>Données de compte</td>
<td>Jusqu'à la suppression du compte</td>
</tr>
<tr>
<td>Journaux techniques</td>
<td>12 mois au maximum</td>
</tr>
<tr>
<td>Jetons de vérification ou de réinitialisation</td>
<td>Leur durée de validité</td>
</tr>
</tbody>
</table>
</section>

<section aria-labelledby="rights">
<h2 id="rights">Vos droits</h2>
<p>
Vous disposez des droits d'accès, de rectification, d'effacement, de
limitation, d'opposition et de portabilité sur vos données. Pour les
exercer, écrivez à
<a href="mailto:ebouchut@gmail.com">ebouchut@gmail.com</a>.
Vous pouvez également introduire une réclamation auprès de la
<a href="https://www.cnil.fr/"><abbr title="Commission nationale de l'informatique et des libertés">CNIL</abbr></a>.
</p>
</section>

<section aria-labelledby="cookies">
<h2 id="cookies">Cookies</h2>
<p>
learn-dev n'utilise qu'un seul cookie, <code>JSESSIONID</code>,
strictement nécessaire au fonctionnement de la session authentifiée.
Il est exempté de consentement et n'est utilisé à aucune fin de suivi.
</p>
</section>

<section aria-labelledby="changes">
<h2 id="changes">Évolutions</h2>
<p>
Cette politique sera mise à jour lorsque de nouvelles fonctionnalités
traiteront des données supplémentaires (par exemple l'envoi d'e-mails
de réinitialisation de mot de passe).
</p>
</section>
</article>
</main>

<footer>
<p><a th:href="@{/}">Retour à l'accueil</a></p>
</footer>
</body>
</html>
1 change: 1 addition & 0 deletions src/main/resources/templates/register.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ <h1>Create your account</h1>
<button type="submit">Register</button>
</form>
<p><a th:href="@{/auth/login}">Already have an account? Log in</a></p>
<p><a th:href="@{/privacy}" hreflang="fr">Privacy policy (RGPD)</a></p>
</body>
</html>
40 changes: 40 additions & 0 deletions src/test/java/com/ericbouchut/learndev/legal/PrivacyPageTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.ericbouchut.learndev.legal;

import com.ericbouchut.learndev.support.AbstractPostgresIT;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
* The privacy policy is GDPR information (art. 13) and must be readable by
* anyone, in particular before registering: this test proves an anonymous
* visitor gets the page instead of a redirect to the login form.
*
* <p>Named with the {@code Test} suffix (not {@code IT}) so Surefire runs it
* as part of {@code mvn test}; this project does not use the Failsafe plugin.
*/
@SpringBootTest(properties = {
// This feature does not use MongoDB; keep the test context Postgres-only.
"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,"
+ "org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration"
})
@AutoConfigureMockMvc
class PrivacyPageTest extends AbstractPostgresIT {

@Autowired
MockMvc mvc;

@Test
void anonymous_visitor_can_read_the_privacy_policy() throws Exception {
mvc.perform(get("/privacy"))
.andExpect(status().isOk())
.andExpect(content().string(org.hamcrest.Matchers.containsString(
"Politique de confidentialité")));
}
}