Password stats implemented

password now passed in body instead of parametr
This commit is contained in:
2025-11-21 20:31:22 +01:00
parent 12aad793c6
commit a21693fd0b
4 changed files with 161 additions and 3 deletions

View File

@@ -3,6 +3,7 @@ package iz._11a.passmetric;
import iz._11a.passmetric.model.PasswordLeakResult; import iz._11a.passmetric.model.PasswordLeakResult;
import iz._11a.passmetric.service.BruteForceService; import iz._11a.passmetric.service.BruteForceService;
import iz._11a.passmetric.service.PasswordLeakService; import iz._11a.passmetric.service.PasswordLeakService;
import iz._11a.passmetric.service.PasswordStatisticsService;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -24,7 +25,7 @@ public class PasswordController {
@PostMapping(path = "/api/password/strength") @PostMapping(path = "/api/password/strength")
@ResponseBody @ResponseBody
public Map<String, Object> checkPasswordLive(@RequestParam("password") String password) { public Map<String, Object> checkPasswordLive(@RequestBody String password) {
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
@@ -38,6 +39,7 @@ public class PasswordController {
response.put("tips", tips); response.put("tips", tips);
response.put("leaked", leakResult.isLeaked() ? "Hasło wyciekło " + leakResult.getCount() +" razy" : "Hasło nie występuje w wyciekach"); response.put("leaked", leakResult.isLeaked() ? "Hasło wyciekło " + leakResult.getCount() +" razy" : "Hasło nie występuje w wyciekach");
response.put("timetohack", BruteForceService.estimateTimeToHackFormatted(password)); response.put("timetohack", BruteForceService.estimateTimeToHackFormatted(password));
response.put("stats", PasswordStatisticsService.getPasswordStatistics(password));
return response; return response;
} }

View File

@@ -0,0 +1,45 @@
package iz._11a.passmetric.service;
import java.util.HashMap;
import java.util.Map;
public class PasswordStatisticsService {
public static Map<String, Object> getPasswordStatistics(String password) {
Map<String, Object> stats = new HashMap<>();
if (password == null || password.isEmpty()) {
stats.put("length", 0);
stats.put("lowercaseCount", 0);
stats.put("uppercaseCount", 0);
stats.put("digitCount", 0);
stats.put("specialCount", 0);
return stats;
}
int lowercaseCount = 0;
int uppercaseCount = 0;
int digitCount = 0;
int specialCount = 0;
for (char c : password.toCharArray()) {
if (Character.isLowerCase(c)) {
lowercaseCount++;
} else if (Character.isUpperCase(c)) {
uppercaseCount++;
} else if (Character.isDigit(c)) {
digitCount++;
} else {
specialCount++;
}
}
stats.put("length", password.length());
stats.put("lowercaseCount", lowercaseCount);
stats.put("uppercaseCount", uppercaseCount);
stats.put("digitCount", digitCount);
stats.put("specialCount", specialCount);
return stats;
}
}

View File

@@ -219,3 +219,68 @@ ul#tipsList li {
margin: 0.3rem 0.5rem; margin: 0.3rem 0.5rem;
} }
/*stats*/
.stats-container {
width: min(92vw, 540px);
margin: 16px auto 0;
padding: 16px;
border-radius: 12px;
border: 1.5px solid var(--border);
background: var(--panel);
backdrop-filter: blur(12px);
display: none;
}
.stats-title {
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
color: var(--text);
text-align: center;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 10px;
}
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: rgba(148, 163, 184, 0.1);
border-radius: 8px;
transition: background 160ms ease;
}
@media (prefers-color-scheme: dark) {
.stat-item {
background: rgba(51, 65, 85, 0.4);
}
}
.stat-item:hover {
background: rgba(148, 163, 184, 0.15);
}
@media (prefers-color-scheme: dark) {
.stat-item:hover {
background: rgba(51, 65, 85, 0.6);
}
}
.stat-label {
font-size: 13px;
color: var(--muted);
font-weight: 500;
}
.stat-value {
font-size: 14px;
font-weight: 700;
color: var(--primary);
}

View File

@@ -19,6 +19,33 @@
<div id="strengthBarFill"></div> <div id="strengthBarFill"></div>
</div> </div>
<div id="statsContainer" class="stats-container">
<h3 class="stats-title">Statystyki hasła</h3>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">Długość:</span>
<span class="stat-value" id="statLength">-</span>
</div>
<div class="stat-item">
<span class="stat-label">Małe litery:</span>
<span class="stat-value" id="statLowercase">-</span>
</div>
<div class="stat-item">
<span class="stat-label">Wielkie litery:</span>
<span class="stat-value" id="statUppercase">-</span>
</div>
<div class="stat-item">
<span class="stat-label">Cyfry:</span>
<span class="stat-value" id="statDigits">-</span>
</div>
<div class="stat-item">
<span class="stat-label">Znaki specjalne:</span>
<span class="stat-value" id="statSpecial">-</span>
</div>
</div>
</div>
<div class="message-wrapper"> <div class="message-wrapper">
<p id="liveMessage" class="message" aria-live="polite"></p> <p id="liveMessage" class="message" aria-live="polite"></p>
<p id="leakMessage" class="message" aria-live="polite"></p> <p id="leakMessage" class="message" aria-live="polite"></p>
@@ -36,6 +63,7 @@
const tipsList = document.getElementById('tipsList'); const tipsList = document.getElementById('tipsList');
const leakOut = document.getElementById('leakMessage'); const leakOut = document.getElementById('leakMessage');
const timeToHackOut = document.getElementById('timeToHackMessage'); const timeToHackOut = document.getElementById('timeToHackMessage');
const statsContainer = document.getElementById('statsContainer');
// ukryj przy pierwszym załadowaniu strony // ukryj przy pierwszym załadowaniu strony
tipsList.style.display = 'none'; tipsList.style.display = 'none';
@@ -56,6 +84,7 @@
tipsList.innerHTML = ""; tipsList.innerHTML = "";
tipsList.style.display = 'none'; tipsList.style.display = 'none';
timeToHackOut.textContent = ''; timeToHackOut.textContent = '';
statsContainer.style.display = 'none';
return; return;
} }
@@ -63,8 +92,8 @@
try { try {
const resp = await fetch('/api/password/strength', { const resp = await fetch('/api/password/strength', {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'text/plain'},
body: new URLSearchParams({password: val}) body: val,
}); });
if (!resp.ok) { if (!resp.ok) {
out.textContent = 'Błąd sprawdzania'; out.textContent = 'Błąd sprawdzania';
@@ -140,6 +169,23 @@
timeToHackOut.classList.remove("ok"); timeToHackOut.classList.remove("ok");
} }
// stats
if (data.stats) {
document.getElementById('statLength').textContent = data.stats.length || 0;
document.getElementById('statLowercase').textContent =
data.stats.lowercaseCount || 0;
document.getElementById('statUppercase').textContent =
data.stats.uppercaseCount || 0;
document.getElementById('statDigits').textContent =
data.stats.digitCount || 0;
document.getElementById('statSpecial').textContent =
data.stats.specialCount || 0;
statsContainer.style.display = 'block';
} else {
statsContainer.style.display = 'none';
}
} catch { } catch {
out.textContent = 'Błąd sieci'; out.textContent = 'Błąd sieci';
leakOut.textContent = ''; leakOut.textContent = '';