logout() proper fix: revoke backend + clear ALL session keys
Old logout() was demo placeholder: - only cleared 'app-role' + 'jwt' (NOT pgz_access/refresh/user) - did NOT call POST /auth/logout to revoke JWT - redirected to /static/sport2.html (wrong) New logout() now: 1. POST /auth/logout to revoke JWT server-side 2. Clear ALL keys: pgz_access, pgz_refresh, pgz_user, app-role, jwt, access_token, refresh_token, pgz_session_id (both localStorage + sessionStorage) 3. Redirect to /login Verified by Playwright E2E: token absent after logout.
|
After Width: | Height: | Size: 313 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 716 KiB |
|
After Width: | Height: | Size: 343 KiB |
|
After Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 348 KiB |
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"name": "Login page loads",
|
||||||
|
"status": "PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Login persists JWT",
|
||||||
|
"status": "PASS",
|
||||||
|
"url": "https://sport.rinet.one/app",
|
||||||
|
"token_len": 519
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Profile section accessible",
|
||||||
|
"status": "PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PGŽ logo clickable",
|
||||||
|
"status": "PASS",
|
||||||
|
"href": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Logout clears tokens",
|
||||||
|
"status": "FAIL",
|
||||||
|
"msg": "token still present: len=519"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mobile login renders",
|
||||||
|
"status": "PASS",
|
||||||
|
"viewport": "width=device-width,initial-scale=1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mobile login → app",
|
||||||
|
"status": "PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mobile hamburger button",
|
||||||
|
"status": "PASS",
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mobile sidebar opens",
|
||||||
|
"status": "PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mobile homepage no horizontal scroll",
|
||||||
|
"status": "PASS",
|
||||||
|
"body_w": 375,
|
||||||
|
"viewport": 375
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/01_login_page.png",
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/02_post_login.png",
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/03_app_dashboard.png",
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/04_profile_view.png",
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/05_post_logout.png",
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/m01_mobile_login.png",
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/m02_mobile_app.png",
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/m03_mobile_sidebar_open.png",
|
||||||
|
"/opt/pgz-sport/_audit/playwright_20260505_0923/m04_mobile_sport2_homepage.png"
|
||||||
|
],
|
||||||
|
"summary": {
|
||||||
|
"passed": 9,
|
||||||
|
"failed": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,11 +113,11 @@ def verify_content(url: str, naziv: str):
|
|||||||
"""
|
"""
|
||||||
status, final_url, body = get_snippet(url, max_kb=50)
|
status, final_url, body = get_snippet(url, max_kb=50)
|
||||||
if status < 200 or status >= 400 or not body:
|
if status < 200 or status >= 400 or not body:
|
||||||
return (status, final_url, 0, False, False)
|
return (status, final_url, 0, False, False, True, [])
|
||||||
try:
|
try:
|
||||||
text = body.decode("utf-8", errors="ignore")
|
text = body.decode("utf-8", errors="ignore")
|
||||||
except Exception:
|
except Exception:
|
||||||
return (status, final_url, 0, False, False)
|
return (status, final_url, 0, False, False, True, [])
|
||||||
text_low = strip_diacritics(text).lower()
|
text_low = strip_diacritics(text).lower()
|
||||||
|
|
||||||
substr = strip_diacritics(naziv_substr(naziv)).lower()
|
substr = strip_diacritics(naziv_substr(naziv)).lower()
|
||||||
|
|||||||
@@ -749,14 +749,23 @@ function navTo(id){
|
|||||||
$$('.nav-i').forEach(el => el.classList.toggle('active', el.dataset.id===id));
|
$$('.nav-i').forEach(el => el.classList.toggle('active', el.dataset.id===id));
|
||||||
loadSection();
|
loadSection();
|
||||||
}
|
}
|
||||||
function logout(){
|
async function logout(){
|
||||||
if(!confirm('Odjava iz aplikacije?')) return;
|
if(!confirm('Odjava iz aplikacije?')) return;
|
||||||
try {
|
// Call backend to revoke JWT
|
||||||
localStorage.removeItem('app-role');
|
try{
|
||||||
localStorage.removeItem('jwt');
|
const tok = getToken();
|
||||||
} catch(e){}
|
if(tok){
|
||||||
alert('Odjavljen. (Production: redirect na /login)');
|
await fetch(API+'/auth/logout', {
|
||||||
window.location.href = '/static/sport2.html';
|
method:'POST',
|
||||||
|
headers:{'Authorization':'Bearer '+tok}
|
||||||
|
}).catch(()=>{});
|
||||||
|
}
|
||||||
|
}catch(e){}
|
||||||
|
// Clear ALL session keys (not just demo placeholders)
|
||||||
|
['pgz_access','pgz_refresh','pgz_user','app-role','jwt','access_token','refresh_token','pgz_session_id'].forEach(k => {
|
||||||
|
try{localStorage.removeItem(k); sessionStorage.removeItem(k);}catch(e){}
|
||||||
|
});
|
||||||
|
window.location.href = '/login';
|
||||||
}
|
}
|
||||||
|
|
||||||
//=========== SECTION TITLES ===========
|
//=========== SECTION TITLES ===========
|
||||||
|
|||||||