Password stats implemented
password now passed in body instead of parametr
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user