├── .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 | 
34 |
35 | 
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 | 
38 |
39 | 
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 |
16 |
17 |
18 |
19 |
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 |
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 | `
88 | + this.showCredentialListHeader()
89 | + credentialList.map(item => this.showCredentialListItem(item)).join('')
90 | + `
`;
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 |
--------------------------------------------------------------------------------