fix(security & perf): implement bearer session tokens, salted pbkdf2, object pooling and background caching
This commit is contained in:
1 parent
258b1a9cb1
commit
7cd0792799
15 files changed
+1264
-105
No files matched your search
+56
-6
@@ -2,9 +2,26 @@
|
||||
// Falls back to localStorage if server is unavailable.
|
||||
window.PvZAuth = (function () {
|
||||
const CURRENT_USER_KEY = 'pvz_current_user';
|
||||
const TOKEN_KEY = 'pvz_token';
|
||||
const LOCAL_DATA_KEY = 'pvz_local_data'; // local fallback cache
|
||||
const API_BASE = '/api/auth';
|
||||
|
||||
/* ─── Session Token Helpers ─── */
|
||||
function getToken() {
|
||||
try { return localStorage.getItem(TOKEN_KEY) || null; } catch (_) { return null; }
|
||||
}
|
||||
|
||||
function setToken(token) {
|
||||
try {
|
||||
if (token) localStorage.setItem(TOKEN_KEY, token);
|
||||
else localStorage.removeItem(TOKEN_KEY);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
try { localStorage.removeItem(TOKEN_KEY); } catch (_) {}
|
||||
}
|
||||
|
||||
/* ─── Password hashing (djb2 for lightweight fast client hashing) ─── */
|
||||
function hashPassword(str) {
|
||||
if (!str) return '';
|
||||
@@ -83,14 +100,33 @@ window.PvZAuth = (function () {
|
||||
try { return JSON.parse(localStorage.getItem(LOCAL_DATA_KEY) || 'null'); } catch (_) { return null; }
|
||||
}
|
||||
|
||||
/* ─── Non-blocking server call ─── */
|
||||
async function apiPost(endpoint, body) {
|
||||
/* ─── Non-blocking server call with Bearer Token Authorization ─── */
|
||||
async function apiPost(endpoint, body = {}) {
|
||||
try {
|
||||
const headers = { 'Content-Type': 'application/json' };
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// Attach token in JSON body as well for maximum compatibility
|
||||
const payload = (typeof body === 'object' && body !== null && token)
|
||||
? { token, ...body }
|
||||
: body;
|
||||
|
||||
const resp = await fetch(`${API_BASE}/${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
headers,
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (resp.status === 401) {
|
||||
// Clear invalid or expired session token
|
||||
clearToken();
|
||||
const json = await resp.json().catch(() => ({}));
|
||||
return { success: false, status: 401, message: json.message || 'Сессия истекла или недействительна' };
|
||||
}
|
||||
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
} catch (e) {
|
||||
@@ -146,6 +182,9 @@ window.PvZAuth = (function () {
|
||||
return !u || u === 'Гость';
|
||||
},
|
||||
|
||||
getToken,
|
||||
setToken,
|
||||
clearToken,
|
||||
getDefaultData,
|
||||
validateUsername,
|
||||
validatePassword,
|
||||
@@ -175,6 +214,9 @@ window.PvZAuth = (function () {
|
||||
|
||||
if (res && res.success && res.data) {
|
||||
localStorage.setItem(CURRENT_USER_KEY, cleanUser);
|
||||
if (res.token) {
|
||||
setToken(res.token);
|
||||
}
|
||||
_cachedData = mergeUserData(res.data);
|
||||
localSave(_cachedData);
|
||||
|
||||
@@ -182,7 +224,7 @@ window.PvZAuth = (function () {
|
||||
window._pvzEngine.loadUserData();
|
||||
}
|
||||
if (typeof callback === 'function') callback(true, _cachedData, null);
|
||||
return { success: true, username: cleanUser, data: _cachedData };
|
||||
return { success: true, username: cleanUser, token: res.token, data: _cachedData };
|
||||
} else {
|
||||
const errorMsg = (res && res.message) ? res.message : 'Неверный логин или пароль';
|
||||
if (typeof callback === 'function') callback(false, null, errorMsg);
|
||||
@@ -214,6 +256,9 @@ window.PvZAuth = (function () {
|
||||
|
||||
if (res && res.success) {
|
||||
localStorage.setItem(CURRENT_USER_KEY, cleanUser);
|
||||
if (res.token) {
|
||||
setToken(res.token);
|
||||
}
|
||||
_cachedData = mergeUserData(res.data || initData);
|
||||
localSave(_cachedData);
|
||||
|
||||
@@ -221,7 +266,7 @@ window.PvZAuth = (function () {
|
||||
window._pvzEngine.loadUserData();
|
||||
}
|
||||
if (typeof callback === 'function') callback(true, _cachedData, null);
|
||||
return { success: true, username: cleanUser, data: _cachedData };
|
||||
return { success: true, username: cleanUser, token: res.token, data: _cachedData };
|
||||
} else {
|
||||
const errorMsg = (res && res.message) ? res.message : 'Ошибка при регистрации';
|
||||
if (typeof callback === 'function') callback(false, null, errorMsg);
|
||||
@@ -230,6 +275,7 @@ window.PvZAuth = (function () {
|
||||
},
|
||||
|
||||
loginAsGuest() {
|
||||
clearToken();
|
||||
localStorage.setItem(CURRENT_USER_KEY, 'Гость');
|
||||
_cachedData = null;
|
||||
localSave(null);
|
||||
@@ -239,9 +285,13 @@ window.PvZAuth = (function () {
|
||||
logout() {
|
||||
if (_saveTimer) { clearTimeout(_saveTimer); _saveTimer = null; }
|
||||
const user = this.getCurrentUser();
|
||||
const token = getToken();
|
||||
if (user !== 'Гость' && _cachedData) {
|
||||
apiPost('save', { username: user, data: _cachedData });
|
||||
}
|
||||
if (token) {
|
||||
apiPost('logout', { token });
|
||||
}
|
||||
this.loginAsGuest();
|
||||
},
|
||||
|
||||
|
||||
Reference in new issue
Block a user