From 8214df62ae159c49af5ded4777060934e7bc9641 Mon Sep 17 00:00:00 2001 From: Milton Gonzalez Date: Thu, 1 Oct 2026 14:16:31 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20version=202.8.1=20--=20catalogo=20de=20?= =?UTF-8?q?logos=20de=20marca=20en=20Contrase=C3=B1as=20(simple-icons,=20C?= =?UTF-8?q?C0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Amplia el selector de icono de una credencial con ~70 logos reales de marcas conocidas (Google, GitHub, Cloudflare, cPanel, MikroTik, WordPress, etc.), empaquetados localmente desde el paquete simple-icons (dominio publico) y tree-shakeados por Vite. Se mantiene el criterio de privacidad ya establecido: ninguna consulta externa de favicon por entrada. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 7 +++ VERSION | 2 +- backend/package.json | 2 +- backend/src/modules/vault/service.js | 20 +++++++- frontend/package-lock.json | 44 +++++++++++++----- frontend/package.json | 5 +- frontend/src/data/brandIcons.js | 46 +++++++++++++++++++ .../src/pages/Contrasenas/ContrasenasPage.jsx | 32 +++++++++++-- frontend/src/styles/app.css | 3 ++ 9 files changed, 139 insertions(+), 22 deletions(-) create mode 100644 frontend/src/data/brandIcons.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 21a0097..7749939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog - project.linkedcorp.net +## [2.8.1] - 2026-10-01 +### Added +- Catálogo de íconos de "Contraseñas" ampliado con ~70 logos de marcas conocidas (Google, GitHub, + Cloudflare, cPanel, MikroTik, WordPress, etc.), empaquetados localmente desde `simple-icons` + (licencia CC0, dominio público) — sin ninguna consulta externa por entrada, mismo criterio de + privacidad que el catálogo genérico anterior. + ## [2.8.0] - 2026-10-01 ### Added - Buscador en "Contraseñas": filtra las tarjetas mientras escribís, por nombre, usuario, enlace diff --git a/VERSION b/VERSION index 834f262..dbe5900 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.0 +2.8.1 diff --git a/backend/package.json b/backend/package.json index 586433f..86aea9a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "project-linkedcorp-backend", - "version": "2.8.0", + "version": "2.8.1", "private": true, "type": "module", "main": "src/server.js", diff --git a/backend/src/modules/vault/service.js b/backend/src/modules/vault/service.js index cfac715..0eee280 100644 --- a/backend/src/modules/vault/service.js +++ b/backend/src/modules/vault/service.js @@ -11,11 +11,27 @@ import { sendMail } from '../../utils/mailer.js'; const PIN_RE = /^\d{4}$/; const VAULT_TOKEN_EXPIRES = '30m'; -// Catalogo fijo (debe coincidir con el del frontend, ContrasenasPage.jsx#ICONS) -- nunca se -// acepta un valor arbitrario, para no guardar basura ni abrir una via de inyeccion rara. +// Catalogo fijo (debe coincidir con el del frontend: ContrasenasPage.jsx#ICONS y +// frontend/src/data/brandIcons.js#BRAND_ICONS) -- nunca se acepta un valor arbitrario, para no +// guardar basura ni abrir una via de inyeccion rara. const ICON_KEYS = new Set([ 'mail', 'globe', 'server', 'database', 'cloud', 'shield', 'lock', 'wifi', 'card', 'building', 'printer', 'phone', 'key', 'disk', 'router', 'user', + 'b_google', 'b_gmail', 'b_googledrive', 'b_googlecloud', 'b_protonmail', 'b_zoho', + 'b_github', 'b_gitlab', 'b_digitalocean', 'b_cloudflare', 'b_namecheap', 'b_godaddy', + 'b_ovh', 'b_vultr', 'b_netlify', 'b_vercel', 'b_cpanel', 'b_plesk', 'b_webmin', + 'b_nginx', 'b_apache', 'b_mysql', 'b_mariadb', 'b_postgresql', 'b_docker', + 'b_cisco', 'b_fortinet', 'b_mikrotik', 'b_ubiquiti', 'b_tplink', 'b_openvpn', 'b_wireguard', + 'b_pfsense', 'b_opnsense', 'b_qnap', 'b_synology', 'b_nordvpn', 'b_bitdefender', + 'b_trendmicro', 'b_avast', 'b_kaspersky', + 'b_whatsapp', 'b_telegram', 'b_zoom', 'b_discord', 'b_jitsi', + 'b_facebook', 'b_instagram', 'b_x', + 'b_paypal', 'b_stripe', + 'b_teamviewer', 'b_anydesk', + 'b_dropbox', 'b_backblaze', 'b_mega', 'b_veeam', 'b_nextcloud', + 'b_bitwarden', 'b_lastpass', + 'b_linux', 'b_ubuntu', 'b_apple', 'b_raspberrypi', + 'b_wordpress', 'b_mailchimp', 'b_firebase', ]); function sanitizeIconKey(key) { return ICON_KEYS.has(key) ? key : null; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a6291e4..ef6416a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,18 +1,19 @@ { "name": "project-linkedcorp-frontend", - "version": "1.21.1", + "version": "2.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "project-linkedcorp-frontend", - "version": "1.21.1", + "version": "2.8.0", "dependencies": { "lucide-react": "^0.462.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.18.3", - "serve": "^14.2.6" + "serve": "^14.2.6", + "simple-icons": "^16.33.0" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.4", @@ -3064,9 +3065,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3903,9 +3904,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "funding": [ { "type": "github", @@ -5802,9 +5803,9 @@ } }, "node_modules/serialize-javascript": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.1.tgz", - "integrity": "sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.2.tgz", + "integrity": "sha512-GL2BWwVa6JydKO6l/ljVgjAZF4QJ3S7dWDWi53s5GZT8PJzD2fNxi67HANQGKAqPrwEpL5ba7gUBGi0Ls/sEoQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6018,6 +6019,25 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/simple-icons": { + "version": "16.33.0", + "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.33.0.tgz", + "integrity": "sha512-z14u6A8ngAg9mgERRsNO8YkjIMefehIpBk7A8Dz3Gd91945qFP19MLPSHgV4z8P5u/MRYd5e07OTzaDoB4d6qA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/simple-icons" + }, + { + "type": "github", + "url": "https://github.com/sponsors/simple-icons" + } + ], + "license": "CC0-1.0", + "engines": { + "node": ">=0.12.18" + } + }, "node_modules/smob": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 21396a1..82a92b1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "project-linkedcorp-frontend", "private": true, - "version": "2.8.0", + "version": "2.8.1", "type": "module", "scripts": { "dev": "vite", @@ -13,7 +13,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.18.3", - "serve": "^14.2.6" + "serve": "^14.2.6", + "simple-icons": "^16.33.0" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.4", diff --git a/frontend/src/data/brandIcons.js b/frontend/src/data/brandIcons.js new file mode 100644 index 0000000..f2ceea5 --- /dev/null +++ b/frontend/src/data/brandIcons.js @@ -0,0 +1,46 @@ +// Catalogo estatico de logos de marca para el icono de una entrada de Contraseñas. +// Fuente: paquete npm "simple-icons" (CC0-1.0 / dominio publico) -- se empaqueta localmente, +// NUNCA se consulta un servicio externo de favicon por entrada (eso filtraria a terceros que +// urls internas guarda cada usuario). simple-icons ya excluye marcas con politicas de marca mas +// estrictas (ej. Microsoft, LinkedIn, AWS, Windows no estan incluidas en el paquete). +// Debe coincidir con backend/src/modules/vault/service.js#BRAND_ICON_KEYS. +import { + siGoogle, siGmail, siGoogledrive, siGooglecloud, siProtonmail, siZoho, + siGithub, siGitlab, siDigitalocean, siCloudflare, siNamecheap, siGodaddy, + siOvh, siVultr, siNetlify, siVercel, siCpanel, siPlesk, siWebmin, + siNginx, siApache, siMysql, siMariadb, siPostgresql, siDocker, + siCisco, siFortinet, siMikrotik, siUbiquiti, siTplink, siOpenvpn, siWireguard, + siPfsense, siOpnsense, siQnap, siSynology, siNordvpn, siBitdefender, + siTrendmicro, siAvast, siKaspersky, + siWhatsapp, siTelegram, siZoom, siDiscord, siJitsi, + siFacebook, siInstagram, siX, + siPaypal, siStripe, + siTeamviewer, siAnydesk, + siDropbox, siBackblaze, siMega, siVeeam, siNextcloud, + siBitwarden, siLastpass, + siLinux, siUbuntu, siApple, siRaspberrypi, + siWordpress, siMailchimp, siFirebase, +} from 'simple-icons'; + +export const BRAND_ICONS = { + b_google: siGoogle, b_gmail: siGmail, b_googledrive: siGoogledrive, b_googlecloud: siGooglecloud, + b_protonmail: siProtonmail, b_zoho: siZoho, + b_github: siGithub, b_gitlab: siGitlab, b_digitalocean: siDigitalocean, b_cloudflare: siCloudflare, + b_namecheap: siNamecheap, b_godaddy: siGodaddy, b_ovh: siOvh, b_vultr: siVultr, + b_netlify: siNetlify, b_vercel: siVercel, b_cpanel: siCpanel, b_plesk: siPlesk, b_webmin: siWebmin, + b_nginx: siNginx, b_apache: siApache, b_mysql: siMysql, b_mariadb: siMariadb, + b_postgresql: siPostgresql, b_docker: siDocker, + b_cisco: siCisco, b_fortinet: siFortinet, b_mikrotik: siMikrotik, b_ubiquiti: siUbiquiti, + b_tplink: siTplink, b_openvpn: siOpenvpn, b_wireguard: siWireguard, b_pfsense: siPfsense, + b_opnsense: siOpnsense, b_qnap: siQnap, b_synology: siSynology, + b_nordvpn: siNordvpn, b_bitdefender: siBitdefender, b_trendmicro: siTrendmicro, + b_avast: siAvast, b_kaspersky: siKaspersky, + b_whatsapp: siWhatsapp, b_telegram: siTelegram, b_zoom: siZoom, b_discord: siDiscord, b_jitsi: siJitsi, + b_facebook: siFacebook, b_instagram: siInstagram, b_x: siX, + b_paypal: siPaypal, b_stripe: siStripe, + b_teamviewer: siTeamviewer, b_anydesk: siAnydesk, + b_dropbox: siDropbox, b_backblaze: siBackblaze, b_mega: siMega, b_veeam: siVeeam, b_nextcloud: siNextcloud, + b_bitwarden: siBitwarden, b_lastpass: siLastpass, + b_linux: siLinux, b_ubuntu: siUbuntu, b_apple: siApple, b_raspberrypi: siRaspberrypi, + b_wordpress: siWordpress, b_mailchimp: siMailchimp, b_firebase: siFirebase, +}; diff --git a/frontend/src/pages/Contrasenas/ContrasenasPage.jsx b/frontend/src/pages/Contrasenas/ContrasenasPage.jsx index 900ea8c..a52aadb 100644 --- a/frontend/src/pages/Contrasenas/ContrasenasPage.jsx +++ b/frontend/src/pages/Contrasenas/ContrasenasPage.jsx @@ -18,25 +18,36 @@ import { PasswordInput } from '../../components/PasswordInput.jsx'; import { DetailPopup } from '../../components/DetailPopup.jsx'; import { Flyout, FlyoutItem } from '../../components/Flyout.jsx'; import { SearchInput } from '../../components/SearchInput.jsx'; +import { BRAND_ICONS } from '../../data/brandIcons.js'; const EMPTY_FORM = { title: '', url: '', username: '', password: '', notes: '', iconKey: '' }; const AUTO_LOCK_MS = 3 * 60 * 1000; // 3 minutos: salir solo del menu si queda abierto sin atencion -// Catalogo fijo de iconos (debe coincidir con backend/src/modules/vault/service.js#ICON_KEYS) -- +// Catalogo fijo de iconos genericos (debe coincidir con backend/src/modules/vault/service.js#ICON_KEYS) -- // a proposito NO se usa ningun servicio externo de favicon (ej. google/s2/favicons): revelaria a // terceros que urls internas tiene guardadas cada usuario, inaceptable para un repositorio de -// credenciales. El usuario elige uno de esta lista, o se deja "Auto" (inicial del nombre). +// credenciales. El usuario elige uno de esta lista, o uno de marca (BRAND_ICONS), o se deja +// "Auto" (inicial del nombre). const ICONS = { mail: Mail, globe: Globe, server: Server, database: Database, cloud: Cloud, shield: Shield, lock: Lock, wifi: Wifi, card: CreditCard, building: Building2, printer: Printer, phone: Smartphone, key: KeyRound, disk: HardDrive, router: Router, user: UserCog, }; +function BrandSvg({ icon, size = 22 }) { + return ( + + + + ); +} + function EntryIcon({ iconKey, title, size = 22 }) { const Icon = ICONS[iconKey]; + const brand = BRAND_ICONS[iconKey]; return ( -
- {Icon ? : {(title || '?').trim().charAt(0).toUpperCase()}} +
+ {Icon ? : brand ? : {(title || '?').trim().charAt(0).toUpperCase()}}
); } @@ -344,6 +355,19 @@ export default function ContrasenasPage() { ))}
+
+ {Object.entries(BRAND_ICONS).map(([key, icon]) => ( + + ))} +
)} diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 212681e..c4bd737 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -379,6 +379,7 @@ table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; } text-overflow: ellipsis; white-space: nowrap; } .vault-icon-picker { display: flex; flex-wrap: wrap; gap: 8px; } +.vault-icon-picker-brands { max-height: 150px; overflow-y: auto; padding-top: 8px; margin-top: 4px; border-top: 1px dashed rgb(var(--line)); } .vault-icon-option { width: 36px; height: 36px; border-radius: 999px; border: 1px solid rgb(var(--line)); background: rgb(var(--field)); color: rgb(var(--body)); cursor: pointer; @@ -386,6 +387,8 @@ table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; } } .vault-icon-option:hover { border-color: rgb(var(--accent) / .6); } .vault-icon-option.selected { border-color: rgb(var(--accent)); background: rgb(var(--accent) / .15); color: rgb(var(--accent)); } +.vault-icon-option.brand, .vault-card-icon.brand { background: #fff; } +.vault-icon-option.brand.selected { border-color: rgb(var(--accent)); } /* --- DetailPopup (ver DetailPopup.jsx) --- Al elegir un item de un listado, la lista queda inactiva detras del overlay y el popup