updated
This commit is contained in:
@@ -554,15 +554,14 @@ const SC_actions = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
// Listado precargado de personas:
|
|
||||||
function TS_decrypt(input, secret, callback, table, id) {
|
function TS_decrypt(input, secret, callback, table, id) {
|
||||||
// Accept objects or plaintext strings. Also support legacy RSA{...} AES-encrypted entries.
|
// Accept objects or plaintext strings. Support AES-encrypted entries wrapped as RSA{...}.
|
||||||
if (typeof input !== "string") {
|
if (typeof input !== "string") {
|
||||||
try { callback(input, false); } catch (e) { console.error(e); }
|
try { callback(input, false); } catch (e) { console.error(e); }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy encrypted format: RSA{...}
|
// Encrypted format marker: RSA{<ciphertext>} where <ciphertext> is CryptoJS AES output
|
||||||
if (input.startsWith("RSA{") && input.endsWith("}") && typeof CryptoJS !== 'undefined') {
|
if (input.startsWith("RSA{") && input.endsWith("}") && typeof CryptoJS !== 'undefined') {
|
||||||
try {
|
try {
|
||||||
var data = input.slice(4, -1);
|
var data = input.slice(4, -1);
|
||||||
@@ -571,7 +570,6 @@ function TS_decrypt(input, secret, callback, table, id) {
|
|||||||
try {
|
try {
|
||||||
decryptedUtf8 = words.toString(CryptoJS.enc.Utf8);
|
decryptedUtf8 = words.toString(CryptoJS.enc.Utf8);
|
||||||
} catch (utfErr) {
|
} catch (utfErr) {
|
||||||
// Malformed UTF-8 — try Latin1 fallback
|
|
||||||
try {
|
try {
|
||||||
decryptedUtf8 = words.toString(CryptoJS.enc.Latin1);
|
decryptedUtf8 = words.toString(CryptoJS.enc.Latin1);
|
||||||
} catch (latinErr) {
|
} catch (latinErr) {
|
||||||
@@ -583,9 +581,9 @@ function TS_decrypt(input, secret, callback, table, id) {
|
|||||||
var parsed = null;
|
var parsed = null;
|
||||||
try { parsed = JSON.parse(decryptedUtf8); } catch (pe) { parsed = decryptedUtf8; }
|
try { parsed = JSON.parse(decryptedUtf8); } catch (pe) { parsed = decryptedUtf8; }
|
||||||
try { callback(parsed, true); } catch (e) { console.error(e); }
|
try { callback(parsed, true); } catch (e) { console.error(e); }
|
||||||
// Migrate to plaintext in DB if table/id provided
|
// Keep encrypted at-rest: if table/id provided, ensure DB stores encrypted payload (input)
|
||||||
if (table && id && window.DB && DB.put && typeof parsed !== 'string') {
|
if (table && id && window.DB && DB.put) {
|
||||||
DB.put(table, id, parsed).catch(() => {});
|
DB.put(table, id, input).catch(() => {});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -595,12 +593,14 @@ function TS_decrypt(input, secret, callback, table, id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to parse JSON strings and migrate to object
|
// Plain JSON stored as text -> parse and return, then re-encrypt in DB for at-rest protection
|
||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(input);
|
var parsed = JSON.parse(input);
|
||||||
try { callback(parsed, false); } catch (e) { console.error(e); }
|
try { callback(parsed, false); } catch (e) { console.error(e); }
|
||||||
if (table && id && window.DB && DB.put) {
|
if (table && id && window.DB && DB.put && typeof SECRET !== 'undefined') {
|
||||||
DB.put(table, id, parsed).catch(() => {});
|
TS_encrypt(parsed, SECRET, function (enc) {
|
||||||
|
DB.put(table, id, enc).catch(() => {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Not JSON, return raw string
|
// Not JSON, return raw string
|
||||||
@@ -608,10 +608,27 @@ function TS_decrypt(input, secret, callback, table, id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function TS_encrypt(input, secret, callback, mode = "RSA") {
|
function TS_encrypt(input, secret, callback, mode = "RSA") {
|
||||||
// Encryption removed: store plaintext objects directly
|
// Encrypt given value for at-rest storage using CryptoJS AES.
|
||||||
|
// Always return string of form RSA{<ciphertext>} via callback.
|
||||||
|
try {
|
||||||
|
if (typeof CryptoJS === 'undefined') {
|
||||||
|
// CryptoJS not available — return plaintext
|
||||||
try { callback(input); } catch (e) { console.error(e); }
|
try { callback(input); } catch (e) { console.error(e); }
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// Populate SC_Personas from DB (PouchDB)
|
var payload = input;
|
||||||
|
if (typeof input !== 'string') {
|
||||||
|
try { payload = JSON.stringify(input); } catch (e) { payload = String(input); }
|
||||||
|
}
|
||||||
|
var encrypted = CryptoJS.AES.encrypt(payload, secret).toString();
|
||||||
|
var out = 'RSA{' + encrypted + '}';
|
||||||
|
try { callback(out); } catch (e) { console.error(e); }
|
||||||
|
} catch (e) {
|
||||||
|
console.error('TS_encrypt: encryption failed', e);
|
||||||
|
try { callback(input); } catch (err) { console.error(err); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Listado precargado de personas:
|
||||||
DB.map('personas', (data, key) => {
|
DB.map('personas', (data, key) => {
|
||||||
function add_row(data, key) {
|
function add_row(data, key) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
@@ -771,7 +788,7 @@ function TS_IndexElement(
|
|||||||
var tablebody_EL = document.getElementById(tablebody);
|
var tablebody_EL = document.getElementById(tablebody);
|
||||||
var rows = {};
|
var rows = {};
|
||||||
config.forEach((key) => {
|
config.forEach((key) => {
|
||||||
tablehead_EL.innerHTML += `<th>${key.label}</th>`;
|
tablehead_EL.innerHTML += `<th>${key.label || ""}</th>`;
|
||||||
});
|
});
|
||||||
// Add search functionality
|
// Add search functionality
|
||||||
const searchKeyEl = document.getElementById(searchKeyInput);
|
const searchKeyEl = document.getElementById(searchKeyInput);
|
||||||
@@ -874,6 +891,14 @@ function TS_IndexElement(
|
|||||||
}
|
}
|
||||||
config.forEach((key) => {
|
config.forEach((key) => {
|
||||||
switch (key.type) {
|
switch (key.type) {
|
||||||
|
case "_encrypted": {
|
||||||
|
const tdEncrypted = document.createElement("td");
|
||||||
|
tdEncrypted.innerText = data["_encrypted__"]
|
||||||
|
? "🔒"
|
||||||
|
: "";
|
||||||
|
new_tr.appendChild(tdEncrypted);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "raw":
|
case "raw":
|
||||||
case "text": {
|
case "text": {
|
||||||
const tdRaw = document.createElement("td");
|
const tdRaw = document.createElement("td");
|
||||||
@@ -1170,9 +1195,11 @@ function TS_IndexElement(
|
|||||||
}
|
}
|
||||||
if (typeof data == "string") {
|
if (typeof data == "string") {
|
||||||
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
|
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
|
||||||
|
data["_encrypted__"] = wasEncrypted;
|
||||||
add_row(data, key);
|
add_row(data, key);
|
||||||
}, ref, key);
|
}, ref, key);
|
||||||
} else {
|
} else {
|
||||||
|
data["_encrypted__"] = false;
|
||||||
add_row(data, key);
|
add_row(data, key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1191,9 +1218,11 @@ function TS_IndexElement(
|
|||||||
}
|
}
|
||||||
if (typeof data == "string") {
|
if (typeof data == "string") {
|
||||||
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
|
TS_decrypt(data, SECRET, (data, wasEncrypted) => {
|
||||||
|
data["_encrypted__"] = wasEncrypted;
|
||||||
add_row(data, key);
|
add_row(data, key);
|
||||||
}, undefined, undefined);
|
}, undefined, undefined);
|
||||||
} else {
|
} else {
|
||||||
|
data["_encrypted__"] = false;
|
||||||
add_row(data, key);
|
add_row(data, key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
32
src/db.js
32
src/db.js
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
var DB = (function () {
|
var DB = (function () {
|
||||||
let local = null;
|
let local = null;
|
||||||
let secret = null;
|
|
||||||
let remote = null;
|
let remote = null;
|
||||||
let changes = null;
|
let changes = null;
|
||||||
let repPush = null;
|
let repPush = null;
|
||||||
@@ -31,9 +30,15 @@ var DB = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init(opts) {
|
function init(opts) {
|
||||||
// opts: { secret, remoteServer, username, password, dbname }
|
// opts: { remoteServer, username, password, dbname }
|
||||||
secret = opts.secret || '';
|
|
||||||
const localName = 'telesec';
|
const localName = 'telesec';
|
||||||
|
// Allow passing encryption secret via opts
|
||||||
|
try {
|
||||||
|
if (opts && opts.secret) {
|
||||||
|
SECRET = opts.secret;
|
||||||
|
try { localStorage.setItem('TELESEC_SECRET', SECRET); } catch (e) {}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
local = new PouchDB(localName);
|
local = new PouchDB(localName);
|
||||||
|
|
||||||
if (opts.remoteServer) {
|
if (opts.remoteServer) {
|
||||||
@@ -109,7 +114,21 @@ var DB = (function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const doc = existing || { _id: _id };
|
const doc = existing || { _id: _id };
|
||||||
doc.data = data;
|
// If TS_encrypt is available and a SECRET is configured, encrypt non-encrypted payloads
|
||||||
|
var toStore = data;
|
||||||
|
try {
|
||||||
|
var isEncryptedString = (typeof data === 'string' && data.startsWith('RSA{') && data.endsWith('}'));
|
||||||
|
if (!isEncryptedString && typeof TS_encrypt === 'function' && typeof SECRET !== 'undefined' && SECRET) {
|
||||||
|
toStore = await new Promise((resolve) => {
|
||||||
|
try {
|
||||||
|
TS_encrypt(data, SECRET, function (enc) {
|
||||||
|
resolve(enc);
|
||||||
|
});
|
||||||
|
} catch (e) { resolve(data); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) { toStore = data; }
|
||||||
|
doc.data = toStore;
|
||||||
doc.table = table;
|
doc.table = table;
|
||||||
doc.ts = new Date().toISOString();
|
doc.ts = new Date().toISOString();
|
||||||
if (existing) doc._rev = existing._rev;
|
if (existing) doc._rev = existing._rev;
|
||||||
@@ -234,9 +253,10 @@ window.DB = DB;
|
|||||||
const username = localStorage.getItem('TELESEC_COUCH_USER') || '';
|
const username = localStorage.getItem('TELESEC_COUCH_USER') || '';
|
||||||
const password = localStorage.getItem('TELESEC_COUCH_PASS') || '';
|
const password = localStorage.getItem('TELESEC_COUCH_PASS') || '';
|
||||||
const dbname = localStorage.getItem('TELESEC_COUCH_DBNAME') || undefined;
|
const dbname = localStorage.getItem('TELESEC_COUCH_DBNAME') || undefined;
|
||||||
const secret = localStorage.getItem('TELESEC_secret') || '';
|
// Load saved secret into global SECRET for encryption/decryption
|
||||||
|
try { SECRET = localStorage.getItem('TELESEC_SECRET') || ''; } catch (e) { SECRET = ''; }
|
||||||
// Call init but don't await; DB functions are safe-guarded with ensureLocal()
|
// Call init but don't await; DB functions are safe-guarded with ensureLocal()
|
||||||
DB.init({ secret, remoteServer, username, password, dbname }).catch((e) => {
|
DB.init({ remoteServer, username, password, dbname }).catch((e) => {
|
||||||
console.warn('DB.autoInit error', e);
|
console.warn('DB.autoInit error', e);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ PAGES.aulas = {
|
|||||||
TS_IndexElement(
|
TS_IndexElement(
|
||||||
"aulas,solicitudes",
|
"aulas,solicitudes",
|
||||||
[
|
[
|
||||||
|
{"type": "_encrypted"},
|
||||||
{
|
{
|
||||||
key: "Solicitante",
|
key: "Solicitante",
|
||||||
type: "persona",
|
type: "persona",
|
||||||
@@ -243,6 +244,7 @@ PAGES.aulas = {
|
|||||||
TS_IndexElement(
|
TS_IndexElement(
|
||||||
"aulas,informes",
|
"aulas,informes",
|
||||||
[
|
[
|
||||||
|
{"type": "_encrypted"},
|
||||||
{
|
{
|
||||||
key: "Autor",
|
key: "Autor",
|
||||||
type: "persona",
|
type: "persona",
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ PAGES.avisos = {
|
|||||||
TS_IndexElement(
|
TS_IndexElement(
|
||||||
"avisos",
|
"avisos",
|
||||||
[
|
[
|
||||||
|
{"type": "_encrypted"},
|
||||||
{
|
{
|
||||||
key: "Origen",
|
key: "Origen",
|
||||||
type: "persona",
|
type: "persona",
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ PAGES.comedor = {
|
|||||||
TS_IndexElement(
|
TS_IndexElement(
|
||||||
"comedor",
|
"comedor",
|
||||||
[
|
[
|
||||||
|
{"type": "_encrypted"},
|
||||||
{
|
{
|
||||||
key: "Fecha",
|
key: "Fecha",
|
||||||
type: "raw",
|
type: "raw",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ PAGES.login = {
|
|||||||
var field_couch_dbname = safeuuid();
|
var field_couch_dbname = safeuuid();
|
||||||
var field_couch_user = safeuuid();
|
var field_couch_user = safeuuid();
|
||||||
var field_couch_pass = safeuuid();
|
var field_couch_pass = safeuuid();
|
||||||
|
var field_secret = safeuuid();
|
||||||
var btn_save = safeuuid();
|
var btn_save = safeuuid();
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<h1>Configuración del servidor CouchDB</h1>
|
<h1>Configuración del servidor CouchDB</h1>
|
||||||
@@ -23,6 +24,9 @@ PAGES.login = {
|
|||||||
<label>Contraseña
|
<label>Contraseña
|
||||||
<input type="password" id="${field_couch_pass}" value="${localStorage.getItem('TELESEC_COUCH_PASS') || ''}"><br><br>
|
<input type="password" id="${field_couch_pass}" value="${localStorage.getItem('TELESEC_COUCH_PASS') || ''}"><br><br>
|
||||||
</label>
|
</label>
|
||||||
|
<label>Clave de encriptación (opcional) - usada para cifrar datos en reposo
|
||||||
|
<input type="password" id="${field_secret}" value="${localStorage.getItem('TELESEC_SECRET') || ''}"><br><br>
|
||||||
|
</label>
|
||||||
<button id="${btn_save}" class="btn5">Guardar y Conectar</button>
|
<button id="${btn_save}" class="btn5">Guardar y Conectar</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<p>Después de guardar, el navegador intentará sincronizar en segundo plano con el servidor.</p>
|
<p>Después de guardar, el navegador intentará sincronizar en segundo plano con el servidor.</p>
|
||||||
@@ -32,10 +36,13 @@ PAGES.login = {
|
|||||||
var dbname = document.getElementById(field_couch_dbname).value.trim();
|
var dbname = document.getElementById(field_couch_dbname).value.trim();
|
||||||
var user = document.getElementById(field_couch_user).value.trim();
|
var user = document.getElementById(field_couch_user).value.trim();
|
||||||
var pass = document.getElementById(field_couch_pass).value;
|
var pass = document.getElementById(field_couch_pass).value;
|
||||||
|
var secret = document.getElementById(field_secret).value || '';
|
||||||
localStorage.setItem('TELESEC_COUCH_URL', "https://" + url);
|
localStorage.setItem('TELESEC_COUCH_URL', "https://" + url);
|
||||||
localStorage.setItem('TELESEC_COUCH_DBNAME', dbname);
|
localStorage.setItem('TELESEC_COUCH_DBNAME', dbname);
|
||||||
localStorage.setItem('TELESEC_COUCH_USER', user);
|
localStorage.setItem('TELESEC_COUCH_USER', user);
|
||||||
localStorage.setItem('TELESEC_COUCH_PASS', pass);
|
localStorage.setItem('TELESEC_COUCH_PASS', pass);
|
||||||
|
localStorage.setItem('TELESEC_SECRET', secret);
|
||||||
|
SECRET = secret;
|
||||||
try {
|
try {
|
||||||
DB.init({ secret: SECRET, remoteServer: "https://" + url, username: user, password: pass, dbname: dbname || undefined });
|
DB.init({ secret: SECRET, remoteServer: "https://" + url, username: user, password: pass, dbname: dbname || undefined });
|
||||||
toastr.success('Iniciando sincronización con CouchDB');
|
toastr.success('Iniciando sincronización con CouchDB');
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ PAGES.materiales = {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const config = [
|
const config = [
|
||||||
|
{"type": "_encrypted"},
|
||||||
{ key: "Revision", label: "Ult. Revisión", type: "fecha-diff", default: "" },
|
{ key: "Revision", label: "Ult. Revisión", type: "fecha-diff", default: "" },
|
||||||
{ key: "Nombre", label: "Nombre", type: "text", default: "" },
|
{ key: "Nombre", label: "Nombre", type: "text", default: "" },
|
||||||
{ key: "Ubicacion", label: "Ubicación", type: "text", default: "--" },
|
{ key: "Ubicacion", label: "Ubicación", type: "text", default: "--" },
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ PAGES.notas = {
|
|||||||
TS_IndexElement(
|
TS_IndexElement(
|
||||||
"notas",
|
"notas",
|
||||||
[
|
[
|
||||||
|
{"type": "_encrypted"},
|
||||||
{
|
{
|
||||||
key: "Autor",
|
key: "Autor",
|
||||||
type: "persona-nombre",
|
type: "persona-nombre",
|
||||||
|
|||||||
@@ -900,6 +900,7 @@ PAGES.pagos = {
|
|||||||
var totals = { ingresos: 0, gastos: 0 };
|
var totals = { ingresos: 0, gastos: 0 };
|
||||||
|
|
||||||
const config = [
|
const config = [
|
||||||
|
{"type": "_encrypted"},
|
||||||
{
|
{
|
||||||
key: "Fecha",
|
key: "Fecha",
|
||||||
label: "Fecha/Hora",
|
label: "Fecha/Hora",
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ PAGES.personas = {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const config = [
|
const config = [
|
||||||
|
{"type": "_encrypted"},
|
||||||
// {
|
// {
|
||||||
// label: "Persona",
|
// label: "Persona",
|
||||||
// type: "persona",
|
// type: "persona",
|
||||||
|
|||||||
Reference in New Issue
Block a user