Skip to content

Sécurité et protection des données

🔒 Sécurité et protection des données

Section titled “🔒 Sécurité et protection des données”

Mindlet manipule des données sensibles (contenus éducatifs personnels, données d’apprentissage) et doit garantir :

  • Confidentialité : Les données ne sont accessibles qu’aux utilisateurs autorisés
  • Intégrité : Les données ne peuvent être modifiées sans autorisation
  • Disponibilité : Le service reste accessible de manière fiable
TLS 1.3

Toutes les communications sont chiffrées :

┌──────────────┐ TLS 1.3 ┌──────────────┐
│ Mobile │ ◄═══════════════════════►│ API │
│ App │ Certificat Let's │ Backend │
│ │ Encrypt │ │
└──────────────┘ └──────────────┘

Configuration :

  • Protocole : TLS 1.3 (minimum TLS 1.2)
  • Certificats : Let’s Encrypt (renouvellement automatique)
  • HSTS : Activé avec max-age=31536000
  • Cipher suites : Suites modernes uniquement
// Middleware de sécurité Laravel
Route::middleware([
'auth:sanctum', // Authentification JWT
'throttle:60,1', // Rate limiting
'verified', // Email vérifié
EnsureJsonResponse::class // Réponses JSON uniquement
])->group(function () {
// Routes protégées
});
  1. L’utilisateur envoie ses credentials (email/password)
  2. Le serveur vérifie et génère un JWT signé
  3. Le token contient l’ID utilisateur et l’expiration
  4. Le client stocke le token de manière sécurisée
  5. Chaque requête inclut le token dans le header Authorization
// Génération du token
$token = $user->createToken('mobile-app', [
'expires_at' => now()->addDays(7),
])->plainTextToken;
// Structure du token (décodé)
{
"sub": "user_123",
"iat": 1705536000,
"exp": 1706140800,
"scopes": ["read", "write"]
}
import * as SecureStore from 'expo-secure-store';
// Stockage du token
await SecureStore.setItemAsync('auth_token', token, {
keychainAccessible: SecureStore.WHEN_UNLOCKED,
});
// Récupération du token
const token = await SecureStore.getItemAsync('auth_token');
class CardPolicy
{
public function view(User $user, Card $card): bool
{
// Seul le propriétaire ou les membres du groupe peuvent voir
return $card->user_id === $user->id
|| $card->collection->isSharedWith($user);
}
public function update(User $user, Card $card): bool
{
// Seul le propriétaire peut modifier
return $card->user_id === $user->id;
}
public function delete(User $user, Card $card): bool
{
return $card->user_id === $user->id;
}
}
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Model
{
protected function personalNotes(): Attribute
{
return Attribute::make(
get: fn ($value) => decrypt($value),
set: fn ($value) => encrypt($value),
);
}
}

Données chiffrées :

  • Notes personnelles
  • Préférences d’apprentissage
  • Données médicales (troubles d’apprentissage)
class CreateCardRequest extends FormRequest
{
public function rules(): array
{
return [
'question' => [
'required',
'string',
'max:1000',
new NoHtmlContent(), // Empêche l'injection HTML
],
'answer' => [
'required',
'string',
'max:5000',
new NoHtmlContent(),
],
'collection_id' => [
'required',
'exists:collections,id',
new UserOwnsCollection(), // Vérifie l'ownership
],
];
}
}
AttaqueProtection
SQL InjectionEloquent ORM, requêtes préparées
XSSÉchappement automatique, CSP headers
CSRFTokens CSRF (sessions web)
Brute ForceRate limiting, captcha après 5 échecs
Mass Assignment$fillable explicite sur les modèles
RGPD Compliant

Minimisation des données

Nous ne collectons que les données strictement nécessaires au fonctionnement du service.

Consentement explicite

L’utilisateur donne son consentement clair avant toute collecte de données.

Droit d'accès

L’utilisateur peut consulter toutes ses données à tout moment.

Droit à l'effacement

L’utilisateur peut supprimer son compte et toutes ses données.

CatégorieDonnéesFinalitéDurée de conservation
CompteEmail, nom, mot de passe (hashé)AuthentificationJusqu’à suppression du compte
ContenuDocuments, cartes, collectionsServiceJusqu’à suppression par l’utilisateur
ApprentissageProgression, scores, tempsPersonnalisation2 ans après dernière connexion
TechniqueLogs, IP, deviceSécurité, debug1 an
class UserDataController extends Controller
{
// Droit d'accès - Export des données
public function exportData(Request $request): JsonResponse
{
$user = $request->user();
$data = [
'profile' => $user->only(['name', 'email', 'created_at']),
'collections' => $user->collections->load('cards'),
'progress' => $user->learningProgress,
'preferences' => $user->preferences,
];
return response()->json($data);
}
// Droit à l'effacement
public function deleteAccount(Request $request): JsonResponse
{
$user = $request->user();
// Suppression en cascade
$user->collections()->delete();
$user->documents()->delete();
$user->notifications()->delete();
// Anonymisation des logs
Log::info('User account deleted', ['user_id' => $user->id]);
// Suppression du compte
$user->delete();
return response()->json(['message' => 'Account deleted']);
}
}
ServiceFournisseurLocalisationDonnées traitées
HébergementOVHcloudFrance 🇫🇷Toutes les données
ServeursHetznerAllemagne 🇩🇪Données de traitement
Email-UEEmails transactionnels
LLMOpenAIAccord DPAContenu pour génération
  1. Transparence : L’utilisateur sait quand l’IA est utilisée
  2. Contrôle humain : L’utilisateur peut modifier/supprimer le contenu généré
  3. Non-discrimination : Pas de biais dans les contenus générés
  4. Respect de la propriété intellectuelle : Pas de copie de contenus protégés
class EthicalAIService:
async def generate_content(self, input: str) -> GeneratedContent:
# Vérification du contenu d'entrée
if self.contains_sensitive_content(input):
raise EthicalViolationError("Contenu sensible détecté")
# Génération avec garde-fous
content = await self.llm.generate(
input,
system_prompt=ETHICAL_SYSTEM_PROMPT,
max_tokens=1000,
)
# Vérification du contenu généré
if self.contains_harmful_content(content):
raise EthicalViolationError("Contenu généré inapproprié")
# Ajout de métadonnées de traçabilité
return GeneratedContent(
content=content,
generated_at=datetime.now(),
model_version=self.model_version,
is_ai_generated=True,
)
// Logging des actions sensibles
Log::channel('security')->info('Sensitive action', [
'action' => 'data_export',
'user_id' => $user->id,
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'timestamp' => now(),
]);
ÉvénementAlerteAction
5 tentatives de login échouéesEmail à l’utilisateurBlocage temporaire
Connexion depuis nouveau paysEmail de vérificationValidation requise
Export de donnéesEmail de confirmationNotification
Changement de mot de passeEmail de notification-

La sécurité n’est pas une option, c’est une fondation.