Compare commits

6 Commits

Author SHA1 Message Date
22f58ff478 literowka 2025-11-21 18:30:24 +01:00
cf37859078 Merge branch 'feature/check-password-leaks'
# Conflicts:
#	src/main/java/iz/_11a/passmetric/PasswordController.java
#	src/main/resources/templates/password.html
2025-11-21 17:52:08 +01:00
409e4ae98f css migration 2025-11-21 17:25:30 +01:00
978974f012 Merge remote-tracking branch 'origin/password-feedback' 2025-11-21 10:54:45 +01:00
3588e67aff Pasek siły hasła oraz podpowiedzi do stworzenia silnego hasła 2025-11-19 21:10:56 +01:00
5b719d6e33 Pasek siły hasła oraz podpowiedzi do stworzenia silnego hasła 2025-11-11 21:18:38 +01:00
3 changed files with 115 additions and 22 deletions

View File

@@ -5,12 +5,7 @@ import iz._11a.passmetric.service.PasswordLeakService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
import org.springframework.web.client.RestTemplate;
import java.util.*;
@Controller
public class PasswordController {
@@ -27,23 +22,40 @@ public class PasswordController {
@PostMapping(path = "/api/password/strength")
@ResponseBody
public Map<String, String> checkPasswordLive(@RequestParam("password") String password) {
public Map<String, Object> checkPasswordLive(@RequestParam("password") String password) {
Map<String, Object> response = new HashMap<>();
int score = calculateScore(password);
String strengthText = strengthText(score);
List<String> tips = generateTips(password);
PasswordLeakResult leakResult = service.checkLeakWithCount(password);
return Map.of(
"message", analyze(password),
"leaked", leakResult.isLeaked() ? "Hasło wyciekło " + String.valueOf(leakResult.getCount()) +" razy" : "Hasło nie występuje w wyciekach"
);
response.put("strengthText", strengthText);
response.put("progress", score * 20); // pasek postępu 0100%
response.put("tips", tips);
response.put("leaked", leakResult.isLeaked() ? "Hasło wyciekło " + leakResult.getCount() +" razy" : "Hasło nie występuje w wyciekach");
return response;
}
private String analyze(String password) {
// ------------------------------------------
// Ocena siły hasła
// ------------------------------------------
private int calculateScore(String password) {
int score = 0;
if (password != null) {
if (password.matches(".*[a-z].*")) score++;
if (password.matches(".*[A-Z].*")) score++;
if (password.matches(".*[0-9].*")) score++;
if (password.matches(".*[@$!%*?&#].*")) score++;
if (password.length() >= 8) score++;
}
if (password == null || password.isEmpty()) return 0;
if (password.matches(".*[a-z].*")) score++;
if (password.matches(".*[A-Z].*")) score++;
if (password.matches(".*[0-9].*")) score++;
if (password.matches(".*[@$!%*?&#].*")) score++;
if (password.length() >= 12) score++;
return score;
}
private String strengthText(int score) {
return switch (score) {
case 5 -> "Bardzo silne hasło";
case 4 -> "Silne hasło";
@@ -52,4 +64,31 @@ public class PasswordController {
default -> "Bardzo słabe hasło";
};
}
// ------------------------------------------
// Stopniowe podpowiedzi
// ------------------------------------------
private List<String> generateTips(String password) {
List<String> tips = new ArrayList<>();
if (password == null || password.isEmpty()) {
tips.add("Wpisz hasło, aby rozpocząć analizę.");
return tips;
}
if (!password.matches(".*[A-Z].*"))
tips.add("Dodaj co najmniej jedną dużą litere.");
if (!password.matches(".*[0-9].*"))
tips.add("Dodaj co najmniej jedną cyfre.");
if (!password.matches(".*[@$!%*?&#].*"))
tips.add("Dodaj co najmniej jeden znak specjalny (np. ! @ # $).");
if (password.length() < 12)
tips.add("Wydłuż hasło do co najmniej 12 znaków.");
if (tips.isEmpty())
tips.add("Świetnie! Twoje hasło jest bardzo silne.");
return tips;
}
}

View File

@@ -182,3 +182,27 @@ input[type="password"]:focus {
color: #fca5a5;
}
}
#strengthBarContainer {
width: min(92vw, 480px);
margin: 10px auto 0;
height: 10px;
background: #e2e8f0;
border-radius: 8px;
overflow: hidden;
}
#strengthBarFill {
height: 100%;
width: 0;
background: var(--bad);
transition: width 200ms ease, background-color 200ms ease;
}
ul#tipsList {
width: min(92vw, 480px);
margin: 10px auto 0;
padding-left: 20px;
font-size: 14px;
color: var(--text);
}

View File

@@ -14,24 +14,38 @@
<input type="password" id="password" autocomplete="off">
</form>
<!-- Pasek siły hasła -->
<div id="strengthBarContainer">
<div id="strengthBarFill"></div>
</div>
<div class="message-wrapper">
<p id="liveMessage" class="message" aria-live="polite"></p>
<p id="leakMessage" class="message" aria-live="polite"></p>
</div>
<!-- Podpowiedzi krok po kroku -->
<ul id="tipsList"></ul>
<script>
const input = document.getElementById('password');
const out = document.getElementById('liveMessage');
const bar = document.getElementById('strengthBarFill');
const tipsList = document.getElementById('tipsList');
const leakOut = document.getElementById('leakMessage');
let t;
input.addEventListener('input', () => {
clearTimeout(t);
const val = input.value;
if (!val) {
out.textContent = '';
leakOut.textContent = '';
out.className = 'message';
leakOut.textContent = '';
leakOut.className = 'message';
bar.style.width = "0%";
tipsList.innerHTML = '';
return;
}
@@ -48,19 +62,23 @@
return;
}
const data = await resp.json();
out.textContent = data.message || '';
out.textContent = data.strengthText || '';
out.className = 'message';
switch (data.message) {
switch (data.strengthText) {
case "Bardzo słabe hasło":
out.classList.add("bad");
bar.style.background = "var(--bad)";
break;
case "Słabe hasło":
case "Średnie hasło":
out.classList.add("warn");
bar.style.background = "var(--warn)";
break;
case "Silne hasło":
case "Bardzo silne hasło":
out.classList.add("ok");
bar.style.background = "var(--ok)";
break;
}
@@ -76,6 +94,18 @@
break;
}
}
// Pasek postępu (0100%)
bar.style.width = (data.progress || 0) + "%";
// Podpowiedzi
tipsList.innerHTML = "";
(data.tips || []).forEach(tip => {
const li = document.createElement("li");
li.textContent = tip;
tipsList.appendChild(li);
});
} catch {
out.textContent = 'Błąd sieci';
leakOut.textContent = '';