├── .gitignore ├── icon.png ├── options.js ├── manifest.json ├── options.html ├── background.js ├── readme.es.md ├── readme.md ├── icon.svg ├── options.css ├── templates.js ├── pwdmngr.css └── pwdmngr.js /.gitignore: -------------------------------------------------------------------------------- 1 | zip.sh 2 | 3 | *.zip 4 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/Password-Manager-Chrome/HEAD/icon.png -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | const $save = document.getElementById('save'); 2 | 3 | document.addEventListener('DOMContentLoaded', () => { 4 | chrome.storage.sync.get({ endpoint: '', key: '' }, items => { 5 | document.getElementById('endpoint').value = items.endpoint; 6 | document.getElementById('key').value = items.key; 7 | }); 8 | }); 9 | 10 | $save.addEventListener('click', () => { 11 | const endpoint = document.getElementById('endpoint').value.replace(/\/*$/, ''); 12 | const key = document.getElementById('key').value; 13 | 14 | chrome.storage.sync.set({ endpoint: endpoint, key: key }, () => { 15 | $save.classList.add('success'); 16 | setTimeout(() => $save.classList.remove('success'), 1000); 17 | }); 18 | }); -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EuSonLito - Password Manager", 3 | "description": "Easy Connect from Chrome with your own Password Manager Platform", 4 | "version": "0.0.2", 5 | "manifest_version": 3, 6 | "author": "Lito", 7 | "icons": { 8 | "128": "icon.png" 9 | }, 10 | "action": { 11 | "default_title": "Password Manager", 12 | "default_icon": "icon.png" 13 | }, 14 | "background": { 15 | "service_worker": "background.js" 16 | }, 17 | "options_ui": { 18 | "page": "options.html", 19 | "open_in_tab": false 20 | }, 21 | "permissions": [ 22 | "activeTab", 23 | "tabs", 24 | "scripting", 25 | "storage" 26 | ], 27 | "host_permissions": [ 28 | "http://*/*", 29 | "https://*/*" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
17 | 18 |
19 | 20 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | const MSG_INIT = 'init'; 2 | const MSG_ERROR = 'error'; 3 | 4 | let endpoint, key; 5 | 6 | function sendMessage(action, content) { 7 | return chrome.tabs.query({ active: true, lastFocusedWindow: true }, async tabs => { 8 | return chrome.tabs.sendMessage(tabs[0].id, { action: action, content: content }); 9 | }); 10 | } 11 | 12 | async function storage() { 13 | return new Promise(function(resolve, reject) { 14 | return chrome.storage.sync.get({ endpoint: null, key: null }, async items => { 15 | endpoint = items.endpoint; 16 | key = items.key; 17 | 18 | return resolve(); 19 | }); 20 | }); 21 | } 22 | 23 | async function load(tab) { 24 | await chrome.scripting.insertCSS({ 25 | target: { tabId: tab.id }, 26 | files: ['pwdmngr.css'] 27 | }).catch(error => console.log(error)); 28 | 29 | await chrome.scripting.executeScript({ 30 | target: { tabId: tab.id }, 31 | files: ['templates.js'] 32 | }).catch(error => console.log(error)); 33 | 34 | await chrome.scripting.executeScript({ 35 | target: { tabId: tab.id }, 36 | files: ['pwdmngr.js'] 37 | }).catch(error => console.log(error)); 38 | 39 | await storage(); 40 | } 41 | 42 | chrome.action.onClicked.addListener(async (tab) => { 43 | await load(tab); 44 | 45 | if (!endpoint || !key) { 46 | return sendMessage(MSG_ERROR, 'API KEY is not configured.
Go to Extensions → Settings → Options'); 47 | } 48 | 49 | sendMessage(MSG_INIT, { endpoint: endpoint, apikey: key, url: tab.url }); 50 | }); 51 | -------------------------------------------------------------------------------- /readme.es.md: -------------------------------------------------------------------------------- 1 | [English](readme.md) 2 | 3 | ### Extensión de Google Chrome para el Gestor de Contraseñas 4 | 5 | Publicada como Extensión en la Chrome Web Store: https://chrome.google.com/webstore/detail/eusonlito-password-manage/mkbgfbjaoibojobjpimpkaofckkgknhi 6 | 7 | Esta extensión se conecta a la aplicación https://github.com/eusonlito/Password-Manager para permitir el acceso a contraseñas directamente desde cualquier sitio web. 8 | 9 | Se debe instalar desde el modo desarrollador, ya que no está publicada en el store de extensiones de Chrome. 10 | 11 | 1. Clonamos el repositorio de Github en un directorio local: 12 | 13 | ```bash 14 | git clone https://github.com/eusonlito/Password-Manager-Chrome.git 15 | ``` 16 | 17 | 2. Vamos a la opción de Chrome `Menú` > `Mas Herramientas` > `Extensiones` 18 | 19 | 3. Arriba a la derecha activamos la opción de `Modo Desarrollador` 20 | 21 | 4. Accedemos al nuevo botón de `Cargar Descomprimida` 22 | 23 | 5. Seleccionamos el directorio que acabamos de clonar. 24 | 25 | 6. En `Endpoint` indicamos la raíz de la web donde tenemos instalado el gestor de contraseñas, por ejemplo `https://password.domain.com` 26 | 27 | 7. En `API KEY` pegamos la API KEY que podemos generar desde nuestro perfil del gestor de contraseñas. 28 | 29 | 8. Guardamos y probamos a pulsar el botón de la aplicación en cualquier web que tengamos dada de alta en el gestor de contraseñas. 30 | 31 | ### Capturas 32 | 33 | ![Password-Manager-Chrome](https://user-images.githubusercontent.com/644551/128035125-32c7521c-aac1-4727-9e1d-be33984b1ac5.png) 34 | 35 | ![Password-Manager-Chrome](https://user-images.githubusercontent.com/644551/128035231-d8bc1e19-13e5-42ff-bbce-0b20caa74be9.png) 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [Spanish](readme.es.md) 2 | 3 | ### Google Chrome Extension for Password Manager 4 | 5 | Published as Extension in Chrome Web Store: https://chrome.google.com/webstore/detail/eusonlito-password-manage/mkbgfbjaoibojobjpimpkaofckkgknhi 6 | 7 | This extension connects to the application https://github.com/eusonlito/Password-Manager to allow access to passwords directly from any website. 8 | 9 | It must be installed from developer mode, as it is not published in the Chrome extensions store. 10 | 11 | 1. Clone the Github repository in a local directory: 12 | 13 | ```bash 14 | git clone https://github.com/eusonlito/Password-Manager-Chrome.git 15 | ``` 16 | 17 | 2. Go to the Chrome option `Menu`>` More Tools`> `Extensions` 18 | 19 | 3. At the top right Activate the option of `Developer Mode` 20 | 21 | 4. Access the new button `Load Unpacked` 22 | 23 | 5. Select the directory that you have just cloned. 24 | 25 | 6. In `Endpoint` indicate the root of the web where you have installed the password manager, for example `https://password.domain.com` 26 | 27 | 7. In `API KEY` paste the API KEY that you can generate from your password manager profile. 28 | 29 | 8. Save and try to press the application button on any website that you have registered in the password manager. 30 | 31 | ### API KEY Secret 32 | 33 | If you have configured the API with `auth.api.secret_enabled` you need to fill the `API KEY Secret` on every IP change. 34 | 35 | ### Screenshots 36 | 37 | ![Password-Manager-Chrome](https://user-images.githubusercontent.com/644551/128035125-32c7521c-aac1-4727-9e1d-be33984b1ac5.png) 38 | 39 | ![Password-Manager-Chrome](https://user-images.githubusercontent.com/644551/128035231-d8bc1e19-13e5-42ff-bbce-0b20caa74be9.png) 40 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /options.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-size: 16px; 6 | } 7 | 8 | body { 9 | font-family: 'Roboto', sans-serif; 10 | background-color: white; 11 | -webkit-text-size-adjust: 100%; 12 | -webkit-font-smoothing: antialiased; 13 | width: 500px; 14 | padding: 0 20px 20px 20px; 15 | overflow: hidden; 16 | } 17 | 18 | a { 19 | text-decoration: none; 20 | } 21 | 22 | .input-group { 23 | position: relative; 24 | width: 100%; 25 | border-radius: 3px; 26 | overflow: hidden; 27 | } 28 | 29 | .input-group label { 30 | position: absolute; 31 | top: 20px; 32 | left: 12px; 33 | color: rgba(0, 0, 0, 0.5); 34 | font-weight: 500; 35 | transform-origin: 0 0; 36 | transform: translate3d(0, 0, 0); 37 | transition: all 0.2s ease; 38 | pointer-events: none; 39 | } 40 | 41 | .input-group input { 42 | -webkit-appearance: none; 43 | -moz-appearance: none; 44 | appearance: none; 45 | width: 100%; 46 | border: 0; 47 | font-family: inherit; 48 | padding: 16px 12px 0 12px; 49 | height: 56px; 50 | font-weight: 400; 51 | background: rgba(0, 0, 0, 0.02); 52 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.3); 53 | color: #000; 54 | transition: all 0.15s ease; 55 | } 56 | 57 | .input-group input:hover { 58 | background: rgba(0, 0, 0, 0.04); 59 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.5); 60 | } 61 | 62 | .input-group input:not(:-moz-placeholder-shown) + label { 63 | color: rgba(0, 0, 0, 0.5); 64 | transform: translate3d(0, -12px, 0) scale(0.75); 65 | } 66 | 67 | .input-group input:not(:-ms-input-placeholder) + label { 68 | color: rgba(0, 0, 0, 0.5); 69 | transform: translate3d(0, -12px, 0) scale(0.75); 70 | } 71 | 72 | .input-group input:not(:placeholder-shown) + label { 73 | color: rgba(0, 0, 0, 0.5); 74 | transform: translate3d(0, -12px, 0) scale(0.75); 75 | } 76 | 77 | .input-group input:focus { 78 | background: rgba(0, 0, 0, 0.05); 79 | outline: none; 80 | box-shadow: inset 0 -2px 0 #3369ff; 81 | } 82 | 83 | .input-group input:focus + label { 84 | color: #3369ff; 85 | transform: translate3d(0, -12px, 0) scale(0.75); 86 | } 87 | 88 | .submit { 89 | display: inline-block; 90 | padding: 0.8em 1.4em; 91 | margin: 0 0.3em 0.3em 0; 92 | border-radius: 0.15em; 93 | box-sizing: border-box; 94 | font-weight: 600; 95 | color: #FFFFFF; 96 | background-color: #3369ff; 97 | box-shadow: inset 0 -0.55em 0 -0.35em rgba(0, 0, 0, 0.17); 98 | text-align: center; 99 | border: none; 100 | cursor: pointer; 101 | } 102 | 103 | .submit:active { 104 | top: 0.1em; 105 | } 106 | 107 | .button-list { 108 | border: 0; 109 | background-color: transparent; 110 | color: #666; 111 | cursor: pointer; 112 | font-size: 1rem; 113 | line-height: 1; 114 | padding: 0.5em; 115 | text-decoration: none; 116 | transition: all 300ms ease; 117 | } 118 | 119 | .button-list:hover { 120 | color: #fff; 121 | outline: 0; 122 | background-color: #999; 123 | } 124 | 125 | table { 126 | width: 100%; 127 | border-spacing: 0; 128 | border-collapse: collapse; 129 | } 130 | 131 | table th { 132 | text-align: left; 133 | padding: 5px 0; 134 | } 135 | 136 | table td { 137 | padding: 5px 0; 138 | } 139 | 140 | .success { 141 | color: green; 142 | } 143 | 144 | button.success { 145 | background-color: green; 146 | color: white; 147 | } 148 | 149 | .error { 150 | color: red; 151 | } 152 | 153 | button.error { 154 | background-color: red; 155 | color: white; 156 | } 157 | 158 | .pb-20 { 159 | padding-bottom: 20px; 160 | } 161 | 162 | .m-10 { 163 | margin: 10px; 164 | } 165 | 166 | .mb-5 { 167 | margin-bottom: 5px; 168 | } 169 | 170 | .mb-20 { 171 | margin-bottom: 20px; 172 | } 173 | 174 | .my-10 { 175 | margin-top: 10px; 176 | margin-bottom: 10px; 177 | } 178 | 179 | .block { 180 | display: block; 181 | } 182 | 183 | .w-full { 184 | width: 100%; 185 | } 186 | 187 | .bold { 188 | font-weight: 700; 189 | } 190 | 191 | .text-center { 192 | text-align: center; 193 | } 194 | 195 | .hidden { 196 | display: none; 197 | } 198 | -------------------------------------------------------------------------------- /templates.js: -------------------------------------------------------------------------------- 1 | function pwdmngrTemplates(name) { 2 | switch (name) { 3 | case 'MODAL': 4 | return ` 5 |
6 |
7 |
8 | 9 | 10 |

Password Manager

11 | 12 | 13 | 14 | 15 |
16 | 17 | 20 | 21 |
...
22 | 23 |
24 | 25 |
26 | 27 | 32 |
33 |
34 | `; 35 | 36 | case 'ICON_COPY': 37 | return ` 38 | 39 | `; 40 | 41 | case 'ICON_WARNING': 42 | return ` 43 | 44 | `; 45 | 46 | case 'ICON_APIKEY': 47 | return ` 48 | 49 | `; 50 | 51 | case 'ICON_NO_RESULTS': 52 | return ` 53 | 54 | `; 55 | 56 | case 'PRELOADER': 57 | return ` 58 | 59 | 60 | 61 | 62 | 67 | 72 | 73 | 74 | 80 | 86 | 87 | 88 | 93 | 99 | 100 | 101 | 102 | 103 | `; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /pwdmngr.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap'); 2 | 3 | /* Reset */ 4 | 5 | .pwdmngr-modal *, 6 | .pwdmngr-modal *:before, 7 | .pwdmngr-modal *:after { 8 | box-sizing: border-box !important; 9 | } 10 | 11 | .pwdmngr-modal *::before, 12 | .pwdmngr-modal *::after { 13 | content: normal !important; 14 | } 15 | 16 | /* Remove default padding */ 17 | .pwdmngr-modal ul[class], 18 | .pwdmngr-modal ol[class] { 19 | padding: 0 !important; 20 | } 21 | 22 | /* Remove default margin */ 23 | .pwdmngr-modal body, 24 | .pwdmngr-modal h1, 25 | .pwdmngr-modal h2, 26 | .pwdmngr-modal h3, 27 | .pwdmngr-modal h4, 28 | .pwdmngr-modal p, 29 | .pwdmngr-modal ul, 30 | .pwdmngr-modal ol, 31 | .pwdmngr-modal li, 32 | .pwdmngr-modal figure, 33 | .pwdmngr-modal figcaption, 34 | .pwdmngr-modal blockquote, 35 | .pwdmngr-modal dl, 36 | .pwdmngr-modal dd, 37 | .pwdmngr-modal a { 38 | padding: 0 !important; 39 | margin: 0 !important; 40 | border: 0 !important; 41 | 42 | line-height: 1.5 !important; 43 | } 44 | 45 | /* Set core body defaults */ 46 | .pwdmngr-modal body { 47 | min-height: 100vh !important; 48 | scroll-behavior: smooth !important; 49 | text-rendering: optimizeSpeed !important; 50 | line-height: 1.5 !important; 51 | } 52 | 53 | /* Remove list styles on ul, ol elements with a class attribute */ 54 | .pwdmngr-modal ul[class], 55 | .pwdmngr-modal ol[class] { 56 | list-style: none !important; 57 | } 58 | 59 | /* A elements that don't have a class get default styles */ 60 | .pwdmngr-modal a:not([class]) { 61 | text-decoration-skip-ink: auto !important; 62 | } 63 | 64 | /* Make images easier to work with */ 65 | .pwdmngr-modal img { 66 | max-width: 100% !important; 67 | display: block !important; 68 | } 69 | 70 | /* Make SVG easier to work with */ 71 | .pwdmngr-modal svg { 72 | display: inline-block !important; 73 | } 74 | 75 | /* Inherit fonts for inputs and buttons */ 76 | .pwdmngr-modal input, 77 | .pwdmngr-modal button, 78 | .pwdmngr-modal textarea, 79 | .pwdmngr-modal select { 80 | font: inherit !important; 81 | } 82 | 83 | /* End reset */ 84 | 85 | body > div:is(.pwdmngr-modal) { 86 | -webkit-font-smoothing: antialiased; 87 | 88 | position: fixed; 89 | top: 24px; 90 | right: 24px; 91 | z-index: 9999999999; 92 | 93 | display: flex !important; 94 | flex-direction: column; 95 | 96 | width: 420px; 97 | padding: 0; 98 | 99 | font-family: Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important; 100 | font-size: 16px !important; 101 | font-weight: 400 !important; 102 | 103 | line-height: 1.25rem; 104 | 105 | color: hsl(0, 0%, 16%) !important; 106 | 107 | background: hsl(0, 0%, 100%) !important; 108 | border-radius: 6px; 109 | box-shadow: 0px 4px 24px -2px rgba(0, 0, 0, 0.08); 110 | 111 | resize: both; 112 | overflow: auto; 113 | direction: rtl; /* Set resize handler on left */ 114 | } 115 | 116 | .pwdmngr-modal .pwdmngr-modal__inner { 117 | direction: ltr; /* Set content back to left-to-right */ 118 | } 119 | 120 | .pwdmngr-modal .pwdmngr-modal__link { 121 | display: flex !important; 122 | align-items: center; 123 | 124 | font-family: inherit !important; 125 | font-size: 15px !important; 126 | font-weight: 500 !important; 127 | 128 | text-decoration: none !important; 129 | 130 | color: #4094a5; 131 | } 132 | 133 | .pwdmngr-modal .pwdmngr-modal__link-text { 134 | } 135 | 136 | .pwdmngr-modal .pwdmngr-modal__link-icon { 137 | margin-left: 4px; 138 | } 139 | 140 | .pwdmngr-modal .pwdmngr-modal__header { 141 | margin: 0 !important; 142 | display: flex; 143 | align-items: center; 144 | 145 | font-family: inherit !important; 146 | font-size: inherit !important; 147 | font-weight: inherit !important; 148 | 149 | padding: 12px 24px; 150 | 151 | color: inherit !important; 152 | 153 | background: rgba(241, 245, 248, 0.9) !important; 154 | 155 | border-top-left-radius: 6px; 156 | border-top-right-radius: 6px; 157 | 158 | cursor: move; 159 | } 160 | 161 | .pwdmngr-modal .pwdmngr-modal__box { 162 | padding: 12px 24px; 163 | 164 | font-family: inherit !important; 165 | font-size: inherit !important; 166 | font-weight: inherit !important; 167 | 168 | color: inherit !important; 169 | 170 | background: hsl(0, 0%, 100%) !important; 171 | } 172 | 173 | .pwdmngr-modal .pwdmngr-modal__input { 174 | background-color: #FFF !important; 175 | box-sizing: border-box !important; 176 | border-style: solid !important; 177 | border-color: rgba(226, 232, 240, 1) !important; 178 | font-family: inherit; 179 | margin: 0 !important; 180 | color: inherit !important; 181 | outline-offset: -2px; 182 | width: 100% !important; 183 | appearance: none !important; 184 | border-radius: 0.375rem !important; 185 | border-width: 1px !important; 186 | padding: 0.375rem 1rem !important; 187 | font-family: inherit !important; 188 | font-size: inherit !important; 189 | font-weight: inherit !important; 190 | line-height: 1.75rem !important; 191 | box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px !important; 192 | } 193 | 194 | .pwdmngr-modal .pwdmngr-modal__input:focus { 195 | outline: 1px solid transparent !important; 196 | outline-offset: 1px !important; 197 | box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px, rgba(59, 130, 246, 0.5) 0px 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px !important; 198 | } 199 | 200 | .pwdmngr-modal .pwdmngr-modal__input::placeholder { 201 | opacity: 1; 202 | color: #cbd5e0; 203 | } 204 | 205 | .pwdmngr-modal .pwdmngr-modal__content { 206 | max-height: 400px; 207 | overflow: auto; 208 | 209 | margin: auto 0; 210 | 211 | padding: 12px 24px; 212 | 213 | font-family: inherit !important; 214 | font-size: inherit !important; 215 | font-weight: inherit !important; 216 | 217 | color: inherit !important; 218 | 219 | background: hsl(0, 0%, 100%) !important; 220 | } 221 | 222 | .pwdmngr-modal .pwdmngr-modal__footer { 223 | display: flex !important; 224 | justify-content: flex-end; 225 | align-items: center; 226 | 227 | min-width: unset; 228 | margin: 0; 229 | 230 | padding: 12px 24px; 231 | 232 | font-family: inherit !important; 233 | font-size: inherit !important; 234 | font-weight: inherit !important; 235 | 236 | text-align: right; 237 | 238 | color: inherit !important; 239 | 240 | background: rgba(241, 245, 248, 0.9) !important; 241 | 242 | border-bottom-right-radius: 6px; 243 | border-bottom-left-radius: 6px; 244 | } 245 | 246 | .pwdmngr-modal .pwdmngr-modal__close { 247 | position: absolute; 248 | 249 | top: 14px; 250 | right: 20px; 251 | 252 | width: 24px; 253 | height: 24px; 254 | 255 | cursor: pointer; 256 | } 257 | 258 | .pwdmngr-modal .pwdmngr-modal__title { 259 | margin: 0 0 0 16px !important; 260 | 261 | font-family: inherit !important; 262 | font-size: 18px !important; 263 | font-weight: 600 !important; 264 | 265 | color: inherit !important; 266 | } 267 | 268 | .pwdmngr-modal .pwdmngr-modal__list { 269 | display: flex; 270 | flex-direction: column; 271 | align-items: center; 272 | 273 | margin: 0 !important; 274 | padding: 0; 275 | 276 | list-style: none !important; 277 | font-family: inherit !important; 278 | font-size: inherit !important; 279 | font-weight: inherit !important; 280 | } 281 | 282 | .pwdmngr-modal .pwdmngr-modal__list-heading, 283 | .pwdmngr-modal .pwdmngr-modal__list-item { 284 | display: flex; 285 | justify-content: space-between; 286 | align-items: center; 287 | 288 | width: 100%; 289 | 290 | font-family: inherit !important; 291 | font-size: inherit !important; 292 | font-weight: inherit !important; 293 | } 294 | 295 | .pwdmngr-modal .pwdmngr-modal__list-heading { 296 | font-family: inherit !important; 297 | font-size: 13px !important; 298 | font-weight: inherit !important; 299 | 300 | 301 | color: hsl(0, 0%, 72%) !important; 302 | } 303 | 304 | .pwdmngr-modal .pwdmngr-modal__list-item { 305 | margin-top: 8px !important; 306 | 307 | font-family: inherit !important; 308 | font-size: inherit !important; 309 | font-weight: inherit !important; 310 | 311 | color: inherit !important; 312 | } 313 | 314 | .pwdmngr-modal .pwdmngr-modal__list-name { 315 | width: 56%; 316 | 317 | overflow: hidden; 318 | white-space: nowrap; 319 | text-overflow: ellipsis; 320 | 321 | font-family: inherit !important; 322 | font-size: inherit !important; 323 | 324 | color: inherit !important; 325 | } 326 | 327 | .pwdmngr-modal .pwdmngr-modal__list-option { 328 | width: 20%; 329 | margin-left: 16px; 330 | 331 | text-align: center; 332 | 333 | cursor: pointer; 334 | } 335 | 336 | .pwdmngr-modal .pwdmngr-modal__list-option > svg { 337 | pointer-events: none; 338 | } 339 | 340 | .pwdmngr-modal .pwdmngr-modal__list-option > svg.active { 341 | fill: #46b346; 342 | } 343 | 344 | .pwdmngr-modal .pwdmngr-modal__list-option > svg.active path { 345 | stroke: white; 346 | } 347 | 348 | .pwdmngr-modal .pwdmngr-modal__preloader, 349 | .pwdmngr-modal .pwdmngr-modal__error { 350 | display: flex; 351 | flex-direction: column; 352 | justify-content: center; 353 | align-items: center; 354 | 355 | margin: auto 0 !important; 356 | padding: 20px 0 !important; 357 | 358 | font-family: inherit !important; 359 | font-size: inherit !important; 360 | font-weight: inherit !important; 361 | 362 | text-align: center; 363 | 364 | color: inherit !important; 365 | } 366 | 367 | .pwdmngr-modal .pwdmngr-modal__error a { 368 | color: rgb(64, 148, 165); 369 | } 370 | 371 | .pwdmngr-modal .pwdmngr-modal__icon { 372 | padding: 0 !important; 373 | } 374 | 375 | .pwdmngr-modal .pwdmngr-modal__hidden { 376 | display: none; 377 | } 378 | -------------------------------------------------------------------------------- /pwdmngr.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | this.DEBUG = false; 3 | 4 | this.ENDPOINT = ''; 5 | this.APIKEY = ''; 6 | this.URL = ''; 7 | 8 | this.ICON_APIKEY = 'icon-apikey'; 9 | this.ICON_WARNING = 'icon-warning'; 10 | this.ICON_NO_RESULTS = 'icon-no-results'; 11 | 12 | this.DRAG = false; 13 | 14 | this.POSITION_X = 0; 15 | this.POSITION_Y = 0; 16 | 17 | this.ELEMENT_X = 0; 18 | this.ELEMENT_Y = 0; 19 | 20 | this.log = function() { 21 | if (this.DEBUG) { 22 | Array.prototype.slice.call(arguments).forEach(element => console.log(element)); 23 | } 24 | }; 25 | 26 | this.$modal = function() { 27 | return document.getElementById('js-pwdmngr-modal'); 28 | } 29 | 30 | this.$header = function() { 31 | return document.getElementById('js-pwdmngr-header'); 32 | } 33 | 34 | this.$closeBtn = function() { 35 | return document.getElementById('js-pwdmngr-close'); 36 | } 37 | 38 | this.$search = function() { 39 | return document.getElementById('js-pwdmngr-search'); 40 | } 41 | 42 | this.$searchInput = function() { 43 | return document.getElementById('js-pwdmngr-search-input'); 44 | } 45 | 46 | this.$content = function() { 47 | return document.getElementById('js-pwdmngr-content'); 48 | } 49 | 50 | this.$apiSecret = function() { 51 | return document.getElementById('js-pwdmngr-api-secret'); 52 | } 53 | 54 | this.$apiSecretInput = function() { 55 | return document.getElementById('js-pwdmngr-api-secret-input'); 56 | } 57 | 58 | this.showError = function(message, icon) { 59 | let iconTPL = ''; 60 | 61 | switch (icon) { 62 | case this.ICON_APIKEY: 63 | iconTPL = pwdmngrTemplates('ICON_APIKEY'); 64 | break; 65 | 66 | case this.ICON_WARNING: 67 | iconTPL = pwdmngrTemplates('ICON_WARNING'); 68 | break; 69 | 70 | case this.ICON_NO_RESULTS: 71 | iconTPL = pwdmngrTemplates('ICON_NO_RESULTS'); 72 | break; 73 | } 74 | 75 | this.$content().innerHTML = ` 76 |

77 | ${ iconTPL } 78 | ${ message } 79 |

80 | `; 81 | }; 82 | 83 | this.showCredentialList = function(credentialList) { 84 | this.log('[password-manager] Showing User List', credentialList); 85 | 86 | this.$content().innerHTML = ` 87 | `; 91 | 92 | this.$content().querySelectorAll('[data-credential]').forEach(item => { 93 | item.addEventListener('click', () => this.copy(item)); 94 | }); 95 | }; 96 | 97 | this.showCredentialListHeader = function() { 98 | return `
  • ` 99 | + `CREDENTIAL` 100 | + `USER` 101 | + `PASSWORD` 102 | + `
  • `; 103 | }; 104 | 105 | this.showCredentialListItem = function(item) { 106 | return `
  • ` 107 | + `${ item.name }` 108 | + `${ pwdmngrTemplates('ICON_COPY') }` 109 | + `${ pwdmngrTemplates('ICON_COPY') }` 110 | + `
  • ` 111 | }; 112 | 113 | this.showApiSecret = function(item) { 114 | this.$search().classList.add('pwdmngr-modal__hidden'); 115 | this.$content().classList.add('pwdmngr-modal__hidden'); 116 | this.$apiSecret().classList.remove('pwdmngr-modal__hidden'); 117 | }; 118 | 119 | this.apiSecretInput = function(e) { 120 | e.preventDefault(); 121 | 122 | const value = this.$apiSecretInput().value; 123 | 124 | if (!value.length) { 125 | return; 126 | } 127 | 128 | if ((e.code !== 'Enter') && (e.code !== 'NumpadEnter')) { 129 | return; 130 | } 131 | 132 | this.$apiSecret().classList.add('pwdmngr-modal__hidden'); 133 | this.$content().classList.remove('pwdmngr-modal__hidden'); 134 | 135 | this.apiSearch({ 136 | api_secret: value, 137 | q: this.URL 138 | }); 139 | }; 140 | 141 | this.request = async function(url, body) { 142 | this.log('[password-manager] requesting Data'); 143 | 144 | const response = await fetch(url, { 145 | method: 'POST', 146 | headers: { 147 | 'Content-Type': 'application/json', 148 | 'Accept': 'application/json', 149 | 'Authorization': this.APIKEY 150 | }, 151 | body: JSON.stringify(body) 152 | }); 153 | 154 | const text = await response.text(); 155 | let data; 156 | 157 | try { 158 | data = JSON.parse(text); 159 | } catch (e) { 160 | return this.showError('Can not load url ' + url + ': ' + text, this.ICON_WARNING); 161 | } 162 | 163 | if (response.ok) { 164 | return data; 165 | } 166 | 167 | if (data.status === 'api_secret_required') { 168 | return this.showApiSecret(); 169 | } 170 | 171 | return this.showError('Can not load url ' + url + ': ' + data.message + '', this.ICON_WARNING); 172 | }; 173 | 174 | this.apiSearch = async function(query) { 175 | this.log('[password-manager] Checking credentials...'); 176 | 177 | if (typeof query === 'string') { 178 | query = { q: query }; 179 | } 180 | 181 | const data = await this.request(this.ENDPOINT + '/app/api/search', query); 182 | 183 | if (!data) { 184 | return; 185 | } 186 | 187 | if (!data.length) { 188 | return this.showError(`No results, but you can add them through WEB PANEL.`, this.ICON_NO_RESULTS) 189 | } 190 | 191 | this.showCredentialList(data); 192 | }; 193 | 194 | this.searchUrl = function() { 195 | this.apiSearch(this.URL); 196 | }; 197 | 198 | this.searchInput = function(e) { 199 | const value = this.$searchInput().value; 200 | 201 | if (e.code === 'Escape') { 202 | return this.apiSearch(this.URL); 203 | } 204 | 205 | if ((e.code !== 'Enter') && (e.code !== 'NumpadEnter')) { 206 | return; 207 | } 208 | 209 | e.preventDefault(); 210 | 211 | if (!value.length) { 212 | return this.apiSearch(this.URL); 213 | } 214 | 215 | this.apiSearch(value); 216 | }; 217 | 218 | this.clipboard = function(text) { 219 | const element = document.createElement('textarea'); 220 | 221 | element.style = 'position: absolute; width: 1px; height: 1px; left: -10px; top: -10px'; 222 | element.value = text; 223 | 224 | document.body.appendChild(element); 225 | 226 | element.select(); 227 | element.setSelectionRange(0, 99999); 228 | 229 | document.execCommand('copy'); 230 | 231 | document.body.removeChild(element); 232 | }; 233 | 234 | this.copy = async function(el) { 235 | const data = await this.request(this.ENDPOINT + '/app/api/' + el.dataset.credentialId + '/payload/' + el.dataset.credentialType); 236 | 237 | if (!data) { 238 | return; 239 | } 240 | 241 | this.clipboard(this.decode(data.value)); 242 | 243 | el.firstElementChild.classList.add('active'); 244 | 245 | setTimeout(() => el.firstElementChild.classList.remove('active'), 1000); 246 | }; 247 | 248 | this.decode = function(encoded) { 249 | const hex = atob(encoded).split('\\x'); 250 | let decoded = ''; 251 | 252 | for (let i = 0; i < hex.length; i++) { 253 | decoded += this.hex2char(hex[i]); 254 | } 255 | 256 | return decoded; 257 | } 258 | 259 | this.hex2char = function(hex) { 260 | const groups = hex.match(/[\s\S]{2}/g) || []; 261 | let char = ''; 262 | 263 | for (var i = 0, j = groups.length; i < j; i++) { 264 | char += '%' + ('0' + groups[i]).slice(-2); 265 | } 266 | 267 | return decodeURIComponent(char); 268 | } 269 | 270 | this.close = function() { 271 | if (!this.$modal()) { 272 | return; 273 | } 274 | 275 | this.$closeBtn().removeEventListener('click', e => this.close()); 276 | 277 | this.$modal().parentElement.removeChild(this.$modal()); 278 | }; 279 | 280 | this.drag = function() { 281 | const $modal = this.$modal(); 282 | const $header = this.$header(); 283 | 284 | $header.onmousedown = () => this.dragStart($modal); 285 | $header.onmouseup = (e) => this.dragEnd(); 286 | 287 | window.onmousemove = (e) => this.dragMove($modal, e); 288 | 289 | window.addEventListener('selectstart', this.dragTextSelect); 290 | }; 291 | 292 | this.dragStart = function($modal) { 293 | this.DRAG = true; 294 | 295 | this.ELEMENT_X = this.POSITION_X - $modal.offsetLeft; 296 | this.ELEMENT_Y = this.POSITION_Y - $modal.offsetTop; 297 | }; 298 | 299 | this.dragMove = function($modal, e) { 300 | this.POSITION_X = e.clientX; 301 | this.POSITION_Y = e.clientY; 302 | 303 | if (!this.DRAG) { 304 | return; 305 | } 306 | 307 | $modal.style.left = (this.POSITION_X - this.ELEMENT_X) + 'px'; 308 | $modal.style.top = (this.POSITION_Y - this.ELEMENT_Y) + 'px'; 309 | }; 310 | 311 | this.dragEnd = function() { 312 | this.DRAG = false; 313 | }; 314 | 315 | this.dragTextSelect = function(e) { 316 | if (this.DRAG) { 317 | e.preventDefault(); 318 | } 319 | }; 320 | 321 | this.createModal = function() { 322 | if (document.getElementById('js-pwdmngr-modal')) { 323 | return; 324 | } 325 | 326 | document.body.insertAdjacentHTML('beforeend', pwdmngrTemplates('MODAL')); 327 | 328 | this.drag(); 329 | 330 | this.$closeBtn().addEventListener('click', e => this.close()); 331 | 332 | this.$searchInput().addEventListener('input', e => this.searchInput(e)); 333 | this.$searchInput().addEventListener('keyup', e => this.searchInput(e)); 334 | 335 | this.$apiSecretInput().addEventListener('keyup', e => this.apiSecretInput(e)); 336 | 337 | this.$content().innerHTML = `
    ${ pwdmngrTemplates('PRELOADER') }
    `; 338 | }; 339 | 340 | this.setConfig = function(data) { 341 | this.ENDPOINT = data.endpoint; 342 | this.APIKEY = data.apikey; 343 | this.URL = data.url; 344 | 345 | document.getElementById('js-pwdmngr-webpanel').href = this.ENDPOINT; 346 | 347 | this.log('[password-manager] Setting Configuration'); 348 | this.log('[password-manager] ENDPOINT', this.ENDPOINT); 349 | this.log('[password-manager] APIKEY', this.APIKEY); 350 | this.log('[password-manager] URL', this.URL); 351 | }; 352 | 353 | this.init = function() { 354 | chrome.runtime.onMessage.addListener(this.initListener); 355 | 356 | this.log('[password-manager] Injected script NEW.'); 357 | } 358 | 359 | this.initListener = function(request) { 360 | if (this.$modal()) { 361 | return; 362 | } 363 | 364 | this.log('[password-manager] request.action', request.action); 365 | 366 | this.createModal(); 367 | 368 | switch (request.action) { 369 | case 'init': 370 | this.setConfig(request.content); 371 | this.searchUrl(); 372 | break; 373 | 374 | case 'error': 375 | this.showError(request.content, this.ICON_APIKEY); 376 | break; 377 | } 378 | } 379 | 380 | this.init(); 381 | })(); 382 | --------------------------------------------------------------------------------