Minimisation des données
Nous ne collectons que les données strictement nécessaires au fonctionnement du service.
Mindlet manipule des données sensibles (contenus éducatifs personnels, données d’apprentissage) et doit garantir :
Toutes les communications sont chiffrées :
┌──────────────┐ TLS 1.3 ┌──────────────┐│ Mobile │ ◄═══════════════════════►│ API ││ App │ Certificat Let's │ Backend ││ │ Encrypt │ │└──────────────┘ └──────────────┘Configuration :
max-age=31536000// Middleware de sécurité LaravelRoute::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});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 tokenawait SecureStore.setItemAsync('auth_token', token, { keychainAccessible: SecureStore.WHEN_UNLOCKED,});
// Récupération du tokenconst 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 :
// Hashage bcrypt avec cost factor 12$hashedPassword = Hash::make($password, [ 'rounds' => 12,]);
// Vérificationif (Hash::check($password, $hashedPassword)) { // Authentification réussie}Politique de mots de passe :
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 ], ]; }}| Attaque | Protection |
|---|---|
| SQL Injection | Eloquent ORM, requêtes préparées |
| XSS | Échappement automatique, CSP headers |
| CSRF | Tokens CSRF (sessions web) |
| Brute Force | Rate limiting, captcha après 5 échecs |
| Mass Assignment | $fillable explicite sur les modèles |
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égorie | Données | Finalité | Durée de conservation |
|---|---|---|---|
| Compte | Email, nom, mot de passe (hashé) | Authentification | Jusqu’à suppression du compte |
| Contenu | Documents, cartes, collections | Service | Jusqu’à suppression par l’utilisateur |
| Apprentissage | Progression, scores, temps | Personnalisation | 2 ans après dernière connexion |
| Technique | Logs, IP, device | Sécurité, debug | 1 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']); }}| Service | Fournisseur | Localisation | Données traitées |
|---|---|---|---|
| Hébergement | OVHcloud | France 🇫🇷 | Toutes les données |
| Serveurs | Hetzner | Allemagne 🇩🇪 | Données de traitement |
| - | UE | Emails transactionnels | |
| LLM | OpenAI | Accord DPA | Contenu pour génération |
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 sensiblesLog::channel('security')->info('Sensitive action', [ 'action' => 'data_export', 'user_id' => $user->id, 'ip' => $request->ip(), 'user_agent' => $request->userAgent(), 'timestamp' => now(),]);| Événement | Alerte | Action |
|---|---|---|
| 5 tentatives de login échouées | Email à l’utilisateur | Blocage temporaire |
| Connexion depuis nouveau pays | Email de vérification | Validation requise |
| Export de données | Email de confirmation | Notification |
| Changement de mot de passe | Email de notification | - |
La sécurité n’est pas une option, c’est une fondation.