559 lines
19 KiB
HTML
559 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Forge - Générateur de Mots de Passe</title>
|
|
|
|
<style>
|
|
/* Variables de Thème */
|
|
:root {
|
|
--bg-color: #121212;
|
|
--primary-color: #1e1e1e;
|
|
--text-color: #f0f0f0;
|
|
--accent-color: #00ff7f; /* Vert "Forge" */
|
|
--accent-dark: #00aa55;
|
|
--border-color: #333;
|
|
--code-bg: #2a2a2a;
|
|
|
|
--strength-weak: #d9534f;
|
|
--strength-medium: #f0ad4e;
|
|
--strength-strong: #5cb85c;
|
|
--strength-vstrong: #00ff7f;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-color);
|
|
line-height: 1.6;
|
|
display: grid;
|
|
place-items: center;
|
|
min-height: 100vh;
|
|
padding: 1rem;
|
|
}
|
|
|
|
/* Conteneur principal du générateur */
|
|
.generator-container {
|
|
width: 100%;
|
|
max-width: 500px;
|
|
background-color: var(--primary-color);
|
|
border-radius: 12px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
|
border: 1px solid var(--border-color);
|
|
overflow: hidden;
|
|
animation: fadeIn 0.8s ease-out;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.95);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
header {
|
|
padding: 1.5rem 2rem;
|
|
text-align: center;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
header h1 {
|
|
color: var(--accent-color);
|
|
font-size: 2rem;
|
|
text-shadow: 0 0 10px rgba(0, 255, 127, 0.7);
|
|
}
|
|
|
|
main {
|
|
padding: 2rem;
|
|
}
|
|
|
|
/* Conteneur du résultat (mot de passe + copie) */
|
|
.result-container {
|
|
display: flex;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
#password-display {
|
|
flex-grow: 1;
|
|
font-family: "Courier New", Courier, monospace;
|
|
font-size: 1.25rem;
|
|
padding: 0.75rem 1rem;
|
|
background-color: var(--code-bg);
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-color);
|
|
border-radius: 8px 0 0 8px;
|
|
outline: none;
|
|
}
|
|
|
|
#copy-btn {
|
|
padding: 0 1rem;
|
|
background-color: var(--accent-color);
|
|
color: var(--bg-color);
|
|
border: none;
|
|
cursor: pointer;
|
|
font-size: 1.5rem; /* Emoji size */
|
|
border-radius: 0 8px 8px 0;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
|
|
#copy-btn:hover {
|
|
background-color: var(--accent-dark);
|
|
}
|
|
|
|
#copy-btn.copied {
|
|
background-color: var(--strength-strong);
|
|
}
|
|
|
|
/* Indicateur de force */
|
|
.strength-container {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
#strength-bar {
|
|
width: 100%;
|
|
height: 10px;
|
|
background-color: var(--code-bg);
|
|
border-radius: 5px;
|
|
overflow: hidden;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
#strength-bar-inner {
|
|
height: 100%;
|
|
width: 0%;
|
|
background-color: var(--strength-weak);
|
|
border-radius: 5px;
|
|
transition: width 0.4s ease, background-color 0.4s ease;
|
|
}
|
|
|
|
#strength-text {
|
|
font-size: 0.9rem;
|
|
text-align: right;
|
|
display: block;
|
|
color: #aaa;
|
|
}
|
|
|
|
/* Conteneur des options */
|
|
.options-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.25rem;
|
|
}
|
|
|
|
.option {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto;
|
|
align-items: center;
|
|
}
|
|
|
|
.option-checkbox {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Slider de longueur */
|
|
label[for="length"] {
|
|
font-size: 1.1rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
#length-value {
|
|
font-size: 1.1rem;
|
|
font-weight: 500;
|
|
color: var(--accent-color);
|
|
font-family: "Courier New", Courier, monospace;
|
|
}
|
|
|
|
input[type="range"] {
|
|
-webkit-appearance: none;
|
|
width: 100%;
|
|
height: 8px;
|
|
background: var(--code-bg);
|
|
border-radius: 5px;
|
|
outline: none;
|
|
margin-top: 0.5rem;
|
|
grid-column: 1 / -1; /* S'étend sur toute la largeur */
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
background: var(--accent-color);
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb:hover {
|
|
background: var(--accent-dark);
|
|
}
|
|
|
|
input[type="range"]::-moz-range-thumb {
|
|
width: 20px;
|
|
height: 20px;
|
|
background: var(--accent-color);
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Cases à cocher personnalisées */
|
|
input[type="checkbox"] {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
background-color: var(--code-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
|
|
input[type="checkbox"]:checked {
|
|
background-color: var(--accent-color);
|
|
border-color: var(--accent-color);
|
|
}
|
|
|
|
input[type="checkbox"]:checked::before {
|
|
content: '✔';
|
|
color: var(--bg-color);
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.option-checkbox label {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
/* Bouton de génération */
|
|
.cta-button {
|
|
width: 100%;
|
|
padding: 1rem;
|
|
font-size: 1.1rem;
|
|
font-weight: bold;
|
|
background-color: var(--accent-color);
|
|
color: var(--bg-color);
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
margin-top: 1rem;
|
|
transition: background-color 0.3s ease, transform 0.1s ease;
|
|
}
|
|
|
|
.cta-button:hover {
|
|
background-color: var(--accent-dark);
|
|
}
|
|
|
|
.cta-button:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
footer {
|
|
text-align: center;
|
|
font-size: 0.8rem;
|
|
color: #555;
|
|
padding-top: 1.5rem;
|
|
border-top: 1px solid var(--border-color);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="generator-container">
|
|
<header>
|
|
<h1><span id="forge-icon">⛓️</span> Forge</h1>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="result-container">
|
|
<input type="text" id="password-display" readonly placeholder="Générez un mot de passe...">
|
|
<button id="copy-btn" title="Copier dans le presse-papiers">📋</button>
|
|
</div>
|
|
|
|
<div class="strength-container">
|
|
<div id="strength-bar">
|
|
<div id="strength-bar-inner"></div>
|
|
</div>
|
|
<span id="strength-text"></span>
|
|
</div>
|
|
|
|
<div class="options-container">
|
|
|
|
<div class="option">
|
|
<label for="length">Longueur du mot de passe</label>
|
|
<span id="length-value">16</span>
|
|
<input type="range" id="length" min="8" max="64" value="16">
|
|
</div>
|
|
|
|
<div class="option option-checkbox">
|
|
<input type="checkbox" id="uppercase" checked>
|
|
<label for="uppercase">Inclure des Majuscules (A-Z)</label>
|
|
</div>
|
|
|
|
<div class="option option-checkbox">
|
|
<input type="checkbox" id="lowercase" checked>
|
|
<label for="lowercase">Inclure des Minuscules (a-z)</label>
|
|
</div>
|
|
|
|
<div class="option option-checkbox">
|
|
<input type="checkbox" id="numbers" checked>
|
|
<label for="numbers">Inclure des Nombres (0-9)</label>
|
|
</div>
|
|
|
|
<div class="option option-checkbox">
|
|
<input type="checkbox" id="symbols" checked>
|
|
<label for="symbols">Inclure des Symboles (!@#$)</label>
|
|
</div>
|
|
|
|
<div class="option option-checkbox">
|
|
<input type="checkbox" id="exclude-ambiguous">
|
|
<label for="exclude-ambiguous">Exclure les caractères ambigus</label>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<button id="generate-btn" class="cta-button">Générer le Mot de Passe</button>
|
|
</main>
|
|
|
|
<footer>
|
|
<p>Forge v1.0 - La sécurité avant tout.</p>
|
|
</footer>
|
|
</div>
|
|
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
// Sélection des éléments DOM
|
|
const lengthSlider = document.getElementById('length');
|
|
const lengthValue = document.getElementById('length-value');
|
|
const passwordDisplay = document.getElementById('password-display');
|
|
const generateBtn = document.getElementById('generate-btn');
|
|
const copyBtn = document.getElementById('copy-btn');
|
|
|
|
const options = {
|
|
uppercase: document.getElementById('uppercase'),
|
|
lowercase: document.getElementById('lowercase'),
|
|
numbers: document.getElementById('numbers'),
|
|
symbols: document.getElementById('symbols'),
|
|
excludeAmbiguous: document.getElementById('exclude-ambiguous')
|
|
};
|
|
|
|
const strengthBar = document.getElementById('strength-bar-inner');
|
|
const strengthText = document.getElementById('strength-text');
|
|
|
|
// Jeux de caractères
|
|
const charsets = {
|
|
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
lowercase: 'abcdefghijklmnopqrstuvwxyz',
|
|
numbers: '0123456789',
|
|
symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?'
|
|
};
|
|
|
|
const ambiguous = 'oO0l1I|';
|
|
|
|
// Mettre à jour la valeur du slider
|
|
lengthSlider.addEventListener('input', (e) => {
|
|
lengthValue.textContent = e.target.value;
|
|
updateStrength();
|
|
});
|
|
|
|
// Écouteurs pour les options
|
|
Object.values(options).forEach(option => {
|
|
option.addEventListener('change', updateStrength);
|
|
});
|
|
generateBtn.addEventListener('click', generatePassword);
|
|
copyBtn.addEventListener('click', copyToClipboard);
|
|
|
|
/**
|
|
* Génère un nombre aléatoire sécurisé (si possible)
|
|
*/
|
|
function getRandomInt(max) {
|
|
// Utilise crypto.getRandomValues pour une meilleure entropie
|
|
const randomBuffer = new Uint32Array(1);
|
|
window.crypto.getRandomValues(randomBuffer);
|
|
let randomNumber = randomBuffer[0] / (0xFFFFFFFF + 1);
|
|
return Math.floor(randomNumber * max);
|
|
}
|
|
|
|
/**
|
|
* Mélange un tableau (algorithme Fisher-Yates)
|
|
*/
|
|
function shuffleArray(array) {
|
|
for (let i = array.length - 1; i > 0; i--) {
|
|
const j = getRandomInt(i + 1);
|
|
[array[i], array[j]] = [array[j], array[i]];
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* Fonction principale de génération
|
|
*/
|
|
function generatePassword() {
|
|
const length = parseInt(lengthSlider.value, 10);
|
|
let charPool = '';
|
|
let guaranteedChars = [];
|
|
let password = [];
|
|
|
|
// 1. Construire le pool de caractères et garantir un de chaque type coché
|
|
if (options.uppercase.checked) {
|
|
charPool += charsets.uppercase;
|
|
guaranteedChars.push(charsets.uppercase[getRandomInt(charsets.uppercase.length)]);
|
|
}
|
|
if (options.lowercase.checked) {
|
|
charPool += charsets.lowercase;
|
|
guaranteedChars.push(charsets.lowercase[getRandomInt(charsets.lowercase.length)]);
|
|
}
|
|
if (options.numbers.checked) {
|
|
charPool += charsets.numbers;
|
|
guaranteedChars.push(charsets.numbers[getRandomInt(charsets.numbers.length)]);
|
|
}
|
|
if (options.symbols.checked) {
|
|
charPool += charsets.symbols;
|
|
guaranteedChars.push(charsets.symbols[getRandomInt(charsets.symbols.length)]);
|
|
}
|
|
|
|
// 2. Gérer le cas où rien n'est coché
|
|
if (charPool === '') {
|
|
alert('Veuillez sélectionner au moins un jeu de caractères.');
|
|
passwordDisplay.value = '';
|
|
updateStrength(); // Réinitialise la barre
|
|
return;
|
|
}
|
|
|
|
// 3. Exclure les caractères ambigus
|
|
if (options.excludeAmbiguous.checked) {
|
|
charPool = charPool.split('').filter(char => !ambiguous.includes(char)).join('');
|
|
guaranteedChars = guaranteedChars.filter(char => !ambiguous.includes(char));
|
|
|
|
// S'assurer que le pool n'est pas vide après filtrage
|
|
if (charPool === '') {
|
|
alert('Impossible de générer un mot de passe, tous les caractères disponibles sont ambigus.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 4. Remplir le reste du mot de passe
|
|
const remainingLength = length - guaranteedChars.length;
|
|
for (let i = 0; i < remainingLength; i++) {
|
|
password.push(charPool[getRandomInt(charPool.length)]);
|
|
}
|
|
|
|
// 5. Combiner les caractères garantis et le reste, puis mélanger
|
|
password = password.concat(guaranteedChars);
|
|
password = shuffleArray(password);
|
|
|
|
// 6. Afficher
|
|
passwordDisplay.value = password.slice(0, length).join(''); // Assure la bonne longueur
|
|
updateStrength(); // Mettre à jour la force après la génération
|
|
|
|
// Réinitialiser le bouton "Copier"
|
|
copyBtn.innerHTML = '📋';
|
|
copyBtn.classList.remove('copied');
|
|
}
|
|
|
|
/**
|
|
* Met à jour l'indicateur de force
|
|
*/
|
|
function updateStrength() {
|
|
const length = parseInt(lengthSlider.value, 10);
|
|
let score = 0;
|
|
let text = '';
|
|
let color = 'var(--strength-weak)';
|
|
let width = '0%';
|
|
|
|
const categories =
|
|
(options.uppercase.checked ? 1 : 0) +
|
|
(options.lowercase.checked ? 1 : 0) +
|
|
(options.numbers.checked ? 1 : 0) +
|
|
(options.symbols.checked ? 1 : 0);
|
|
|
|
if (categories === 0 || passwordDisplay.value === '') {
|
|
strengthText.textContent = '';
|
|
strengthBar.style.width = '0%';
|
|
return;
|
|
}
|
|
|
|
// Calcul basique du score (entropie simplifiée)
|
|
score = length * categories * (options.symbols.checked ? 1.5 : 1);
|
|
|
|
if (length < 8) {
|
|
score = 0; // Trop court
|
|
}
|
|
|
|
if (score < 25) {
|
|
text = 'Très Faible';
|
|
color = 'var(--strength-weak)';
|
|
width = '25%';
|
|
} else if (score < 40) {
|
|
text = 'Moyen';
|
|
color = 'var(--strength-medium)';
|
|
width = '50%';
|
|
} else if (score < 60) {
|
|
text = 'Fort';
|
|
color = 'var(--strength-strong)';
|
|
width = '75%';
|
|
} else {
|
|
text = 'Très Fort';
|
|
color = 'var(--strength-vstrong)';
|
|
width = '100%';
|
|
}
|
|
|
|
strengthText.textContent = text;
|
|
strengthBar.style.width = width;
|
|
strengthBar.style.backgroundColor = color;
|
|
}
|
|
|
|
/**
|
|
* Copie le mot de passe dans le presse-papiers
|
|
*/
|
|
function copyToClipboard() {
|
|
const password = passwordDisplay.value;
|
|
if (!password) return;
|
|
|
|
navigator.clipboard.writeText(password).then(() => {
|
|
// Succès
|
|
copyBtn.innerHTML = '✅';
|
|
copyBtn.classList.add('copied');
|
|
|
|
// Revenir à l'icône de base après 2 secondes
|
|
setTimeout(() => {
|
|
copyBtn.innerHTML = '📋';
|
|
copyBtn.classList.remove('copied');
|
|
}, 2000);
|
|
}).catch(err => {
|
|
// Erreur (rare, mais possible)
|
|
alert("Erreur lors de la copie : " + err);
|
|
});
|
|
}
|
|
|
|
// Générer un premier mot de passe au chargement
|
|
generatePassword();
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|