├── .gitignore ├── .jpmignore ├── data ├── icon-16.png ├── icon-32.png ├── icon-48.png ├── icon-64.png ├── icon-72.png ├── icon-96.png ├── icon-140.png ├── icon-300.png ├── font │ ├── fontello.eot │ ├── fontello.ttf │ ├── fontello.woff │ ├── fontello.json │ └── fontello.svg ├── js │ ├── worker.js │ └── panel.js ├── css │ ├── panel.css │ └── fontello.css └── panel.html ├── LICENSE ├── README.md ├── package.json └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /passwordmaker.js 3 | *.xpi 4 | -------------------------------------------------------------------------------- /.jpmignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .gitignore 3 | .jpmignore 4 | node_modules/ 5 | *.xpi 6 | -------------------------------------------------------------------------------- /data/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/icon-16.png -------------------------------------------------------------------------------- /data/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/icon-32.png -------------------------------------------------------------------------------- /data/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/icon-48.png -------------------------------------------------------------------------------- /data/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/icon-64.png -------------------------------------------------------------------------------- /data/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/icon-72.png -------------------------------------------------------------------------------- /data/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/icon-96.png -------------------------------------------------------------------------------- /data/icon-140.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/icon-140.png -------------------------------------------------------------------------------- /data/icon-300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/icon-300.png -------------------------------------------------------------------------------- /data/font/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/font/fontello.eot -------------------------------------------------------------------------------- /data/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/font/fontello.ttf -------------------------------------------------------------------------------- /data/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emersion/firefox-passwordmaker/HEAD/data/font/fontello.woff -------------------------------------------------------------------------------- /data/js/worker.js: -------------------------------------------------------------------------------- 1 | self.port.on("passwd-auto-fill", function (passwd) { 2 | if (document.activeElement) { 3 | document.activeElement.value = passwd; 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /data/css/panel.css: -------------------------------------------------------------------------------- 1 | label { 2 | display: inline-block; 3 | position: relative; 4 | width: 100%; 5 | } 6 | .btn-ctn { 7 | position: absolute; 8 | right: 0; 9 | bottom: 0.3em; 10 | } 11 | .btn-ctn button { 12 | border: none; 13 | background: none; 14 | padding: 0; 15 | } 16 | input { 17 | width: 100%; 18 | box-sizing: border-box; 19 | } -------------------------------------------------------------------------------- /data/font/fontello.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "css_prefix_text": "icon-", 4 | "css_use_suffix": false, 5 | "hinting": true, 6 | "units_per_em": 1000, 7 | "ascent": 850, 8 | "glyphs": [ 9 | { 10 | "uid": "c8585e1e5b0467f28b70bce765d5840c", 11 | "css": "docs", 12 | "code": 59393, 13 | "src": "fontawesome" 14 | }, 15 | { 16 | "uid": "3a00327e61b997b58518bd43ed83c3df", 17 | "css": "login", 18 | "code": 59394, 19 | "src": "fontawesome" 20 | }, 21 | { 22 | "uid": "f4445feb55521283572ee88bc304f928", 23 | "css": "floppy", 24 | "code": 59392, 25 | "src": "fontawesome" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /data/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PasswordMaker 6 | 7 | 8 | 9 | 10 | 11 |
12 |
19 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 emersion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /data/font/fontello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2015 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PasswordMaker 2 | ============= 3 | 4 | This extension provides a simple PasswordMaker feature for Firefox. It's available on Firefox for Linux, Windows, OS X and Android. 5 | 6 | Unmaintained 7 | ------------ 8 | 9 | This addon is not maintained anymore, please use the WebExtension version instead: https://github.com/emersion/passwordmaker 10 | 11 | ![PasswordMaker 0.11](https://cloud.githubusercontent.com/assets/506932/8762930/6fca6812-2d87-11e5-911b-6a7e354fcc45.png) 12 | 13 | Features: 14 | * Action button with domain name autocompletion, master password saving (in Firefox's secure database) and clipboard support 15 | * Preferences to manage the profile 16 | * 8 hashing algorithms: 17 | * SHA-1 18 | * HMAC-SHA-1 19 | * SHA-256 20 | * HMAC-SHA-256 21 | * MD5 22 | * HMAC-MD5 23 | * RIPEMD-160 24 | * HMAC-RIPEMD-160 25 | * 6 included character sets 26 | * Latest and greatest circular icon technology 27 | 28 | How does it work? 29 | ----------------- 30 | 31 | ![diagram](https://cloud.githubusercontent.com/assets/506932/3291715/4b9b80d6-f587-11e3-9115-d322e5748806.png) 32 | 33 | Installing 34 | ---------- 35 | 36 | You can find the extension on Firefox add-ons website: https://addons.mozilla.org/fr/firefox/addon/password-maker-x/ 37 | 38 | > There is also an Android app available: https://play.google.com/store/apps/details?id=io.github.eddieringle.android.apps.passwordmaker 39 | 40 | Building 41 | -------- 42 | 43 | This extension was built using the [add-on SDK](https://developer.mozilla.org/en-US/Add-ons/SDK). 44 | 45 | Generate the library from [`node-passwordmaker`](https://github.com/emersion/node-passwordmaker): 46 | ``` 47 | npm build 48 | ``` 49 | 50 | To run the extension: 51 | ``` 52 | jpm run 53 | ``` 54 | 55 | To build the extension using the SDK: 56 | ``` 57 | jpm xpi 58 | ``` 59 | 60 | Licence 61 | ------- 62 | 63 | This extension is released under the MIT licence. 64 | -------------------------------------------------------------------------------- /data/css/fontello.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'fontello'; 3 | src: url('../font/fontello.eot?35162964'); 4 | src: url('../font/fontello.eot?35162964#iefix') format('embedded-opentype'), 5 | url('../font/fontello.woff?35162964') format('woff'), 6 | url('../font/fontello.ttf?35162964') format('truetype'), 7 | url('../font/fontello.svg?35162964#fontello') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ 12 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ 13 | /* 14 | @media screen and (-webkit-min-device-pixel-ratio:0) { 15 | @font-face { 16 | font-family: 'fontello'; 17 | src: url('../font/fontello.svg?35162964#fontello') format('svg'); 18 | } 19 | } 20 | */ 21 | 22 | [class^="icon-"]:before, [class*=" icon-"]:before { 23 | font-family: "fontello"; 24 | font-style: normal; 25 | font-weight: normal; 26 | speak: none; 27 | 28 | display: inline-block; 29 | text-decoration: inherit; 30 | width: 1em; 31 | margin-right: .2em; 32 | text-align: center; 33 | /* opacity: .8; */ 34 | 35 | /* For safety - reset parent styles, that can break glyph codes*/ 36 | font-variant: normal; 37 | text-transform: none; 38 | 39 | /* fix buttons height, for twitter bootstrap */ 40 | line-height: 1em; 41 | 42 | /* Animation center compensation - margins should be symmetric */ 43 | /* remove if not needed */ 44 | margin-left: .2em; 45 | 46 | /* you can be more comfortable with increased icons size */ 47 | /* font-size: 120%; */ 48 | 49 | /* Uncomment for 3D effect */ 50 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 51 | } 52 | 53 | .icon-floppy:before { content: '\e800'; } /* '' */ 54 | .icon-docs:before { content: '\e801'; } /* '' */ 55 | .icon-login:before { content: '\e802'; } /* '' */ -------------------------------------------------------------------------------- /data/js/panel.js: -------------------------------------------------------------------------------- 1 | // List of panel entries 2 | var domainEntry = document.getElementById('domain'), 3 | masterPasswdEntry = document.getElementById('password-master'), 4 | generatedPasswdEntry = document.getElementById('password-generated'), 5 | copyBtn = document.getElementById('btn-copy'), 6 | autoFillBtn = document.getElementById('btn-auto-fill'), 7 | saveMasterBtn = document.getElementById('btn-save-master'), 8 | prefs = null, 9 | username = '', 10 | isShowing = false; 11 | 12 | var charsets = {}; 13 | 14 | // Called each time an entry is updated 15 | var onUpdate = function () { 16 | var opts = { 17 | data: domainEntry.value, 18 | masterPassword: masterPasswdEntry.value, 19 | username: username, 20 | modifier: prefs.modifier || '', 21 | hashAlgorithm: prefs.hashAlgorithm || 'md5', 22 | whereToUseL33t: prefs.useL33t || 'off', 23 | l33tLevel: prefs.l33tLevel || 1, 24 | length: prefs.length || 8, 25 | prefix: prefs.prefix || '', 26 | suffix: prefs.suffix || '', 27 | charset: charsets[prefs.charset] || charsets['alphanumsym'] 28 | }; 29 | 30 | if (!opts.masterPassword) { 31 | return; 32 | } 33 | 34 | self.port.emit('passwd-generate', opts); 35 | }; 36 | 37 | self.port.on('passwd-generated', function (passwd) { 38 | generatedPasswdEntry.value = passwd; 39 | }); 40 | 41 | // @see https://github.com/emersion/firefox-passwordmaker/issues/1 42 | var updateTimeout = null; 43 | var delayedUpdate = function () { 44 | if (typeof updateTimeout !== null) { 45 | clearTimeout(updateTimeout); 46 | } 47 | 48 | updateTimeout = setTimeout(function () { 49 | updateTimeout = null; 50 | onUpdate(); 51 | }, 500); 52 | }; 53 | 54 | var isGeneratedPasswdRevealed = function () { 55 | return (generatedPasswdEntry.type == 'text'); 56 | }; 57 | var revealGeneratedPasswd = function () { 58 | generatedPasswdEntry.type = 'text'; 59 | generatedPasswdEntry.select(); 60 | }; 61 | var hideGeneratedPasswd = function () { 62 | generatedPasswdEntry.type = 'password'; 63 | }; 64 | 65 | // When this panel is displayed 66 | self.port.on('show', function onShow(data) { 67 | prefs = data.prefs; 68 | charsets = data.charsets; 69 | isShowing = true; 70 | 71 | // Change generated password entry type according to prefs 72 | if (prefs.passwordVisibility == 'always') { 73 | revealGeneratedPasswd(); 74 | } else { 75 | hideGeneratedPasswd(); 76 | } 77 | 78 | // Prefill entries if possible 79 | if (data.username != 'undefined') { 80 | username = data.username; 81 | } 82 | 83 | if (data.domain) { 84 | domainEntry.value = data.domain; 85 | } 86 | if (!masterPasswdEntry.value) { 87 | masterPasswdEntry.value = data.passwd; 88 | 89 | if (data.passwd) { // Master password already saved 90 | saveMasterBtn.disabled = true; 91 | } 92 | } 93 | if (!masterPasswdEntry.value) { 94 | if (!data.domain) { 95 | domainEntry.focus(); 96 | } else { 97 | masterPasswdEntry.focus(); 98 | } 99 | } else { 100 | onUpdate(); 101 | generatedPasswdEntry.focus(); 102 | } 103 | 104 | isShowing = false; 105 | }); 106 | 107 | // Listen for keyup events 108 | domainEntry.addEventListener('keyup', function onDomainKeyup() { 109 | delayedUpdate(); 110 | }); 111 | masterPasswdEntry.addEventListener('keyup', function onDomainKeyup() { 112 | delayedUpdate(); 113 | }); 114 | 115 | // Listen for clicks on buttons 116 | copyBtn.addEventListener('click', function onCopyClick() { 117 | self.port.emit('passwd-copy', { passwd: generatedPasswdEntry.value }); 118 | }); 119 | 120 | 121 | saveMasterBtn.addEventListener('click', function onSaveMasterClick() { 122 | self.port.emit('master-passwd-save', { passwd: masterPasswdEntry.value }); 123 | saveMasterBtn.disabled = true; 124 | }); 125 | 126 | generatedPasswdEntry.addEventListener('mouseover', function () { 127 | if (prefs && prefs.passwordVisibility == 'hover') { 128 | revealGeneratedPasswd(); 129 | } 130 | }); 131 | generatedPasswdEntry.addEventListener('mouseout', function () { 132 | if (prefs && prefs.passwordVisibility == 'hover') { 133 | hideGeneratedPasswd(); 134 | } 135 | }); 136 | generatedPasswdEntry.addEventListener('focus', function () { 137 | if (!generatedPasswdEntry.value.length) { 138 | onUpdate(); 139 | } 140 | if (!isShowing && prefs && prefs.passwordVisibility == 'click') { 141 | revealGeneratedPasswd(); 142 | } 143 | }); 144 | generatedPasswdEntry.addEventListener('blur', function () { 145 | if (prefs && prefs.passwordVisibility == 'click') { 146 | hideGeneratedPasswd(); 147 | } 148 | }); 149 | generatedPasswdEntry.addEventListener('click', function () { 150 | if (prefs && prefs.passwordVisibility == 'click' && !isGeneratedPasswdRevealed()) { 151 | revealGeneratedPasswd(); 152 | } 153 | }); 154 | 155 | // Auto-fill password 156 | function autoFillPasswd() { 157 | if (generatedPasswdEntry.value.length != 0) { 158 | self.port.emit('passwd-auto-fill', { passwd: generatedPasswdEntry.value }); 159 | } 160 | } 161 | 162 | document.addEventListener('keypress', function (event) { 163 | if (event.keyCode == 13) { // Enter key 164 | event.preventDefault(); 165 | autoFillPasswd(); 166 | } 167 | }); 168 | 169 | autoFillBtn.addEventListener('click', autoFillPasswd); 170 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Password Maker X", 3 | "name": "password-maker-x", 4 | "id": "jid1-SbKLzc5j3xRe0w@jetpack", 5 | "version": "2.0.3", 6 | "description": "Generate per-website passwords based on a master one.", 7 | "icon": "resource://jid1-SbKLzc5j3xRe0w-at-jetpack/data/icon-48.png", 8 | "main": "index.js", 9 | "author": "emersion (http://emersion.fr)", 10 | "homepage": "https://github.com/emersion/firefox-passwordmaker", 11 | "engines": { 12 | "firefox": ">=38.0a1", 13 | "fennec": ">=38.0a1" 14 | }, 15 | "license": "MIT", 16 | "permissions": { 17 | "private-browsing": true 18 | }, 19 | "dependencies": { 20 | "passwordmaker": "^0.1.0" 21 | }, 22 | "scripts": { 23 | "build": "browserify -r passwordmaker -s module.exports > passwordmaker.js", 24 | "xpi": "jpm xpi", 25 | "start": "jpm run" 26 | }, 27 | "preferences": [ 28 | { 29 | "name": "hashAlgorithm", 30 | "title": "Hash algorithm", 31 | "type": "menulist", 32 | "value": "sha256", 33 | "options": [ 34 | { 35 | "value": "sha256", 36 | "label": "sha256" 37 | }, 38 | { 39 | "value": "hmac-sha256", 40 | "label": "hmac-sha256" 41 | }, 42 | { 43 | "value": "sha1", 44 | "label": "sha1" 45 | }, 46 | { 47 | "value": "hmac-sha1", 48 | "label": "hmac-sha1" 49 | }, 50 | { 51 | "value": "md5", 52 | "label": "md5" 53 | }, 54 | { 55 | "value": "hmac-md5", 56 | "label": "hmac-md5" 57 | }, 58 | { 59 | "value": "rmd160", 60 | "label": "rmd160" 61 | }, 62 | { 63 | "value": "hmac-rmd160", 64 | "label": "hmac-rmd160" 65 | } 66 | ] 67 | }, 68 | { 69 | "name": "length", 70 | "title": "Generated passwords length", 71 | "type": "integer", 72 | "value": 8 73 | }, 74 | { 75 | "name": "modifier", 76 | "title": "Modifier", 77 | "type": "string", 78 | "value": "" 79 | }, 80 | { 81 | "name": "charset", 82 | "title": "Character set", 83 | "type": "menulist", 84 | "value": "alphanumsym", 85 | "options": [ 86 | { 87 | "value": "alpha", 88 | "label": "Alphas" 89 | }, 90 | { 91 | "value": "alphanum", 92 | "label": "Alphanumerics" 93 | }, 94 | { 95 | "value": "alphanumsym", 96 | "label": "Alphanumerics & symbols" 97 | }, 98 | { 99 | "value": "hex", 100 | "label": "Hexadecimal" 101 | }, 102 | { 103 | "value": "num", 104 | "label": "Numbers" 105 | }, 106 | { 107 | "value": "sym", 108 | "label": "Symbols" 109 | }, 110 | { 111 | "value": "custom", 112 | "label": "Custom..." 113 | } 114 | ] 115 | }, 116 | { 117 | "name": "customCharset", 118 | "title": "Custom charset", 119 | "type": "string", 120 | "value": "" 121 | }, 122 | { 123 | "name": "urlComponents", 124 | "title": "URL components", 125 | "type": "radio", 126 | "value": "domain", 127 | "options": [ 128 | { 129 | "value": "domain", 130 | "label": "Domain" 131 | }, 132 | { 133 | "value": "domain+subdomain", 134 | "label": "Domain & subdomain(s)" 135 | } 136 | ] 137 | }, 138 | { 139 | "name": "useL33t", 140 | "title": "Use l33t", 141 | "type": "menulist", 142 | "value": "off", 143 | "options": [ 144 | { 145 | "value": "off", 146 | "label": "Never" 147 | }, 148 | { 149 | "value": "before-hashing", 150 | "label": "Before hashing" 151 | }, 152 | { 153 | "value": "after-hashing", 154 | "label": "After hashing" 155 | }, 156 | { 157 | "value": "both", 158 | "label": "Before and after hashing" 159 | } 160 | ] 161 | }, 162 | { 163 | "name": "l33tLevel", 164 | "title": "L33t level (1 to 9)", 165 | "type": "integer", 166 | "value": 1 167 | }, 168 | { 169 | "name": "prefix", 170 | "title": "Password prefix", 171 | "type": "string", 172 | "value": "" 173 | }, 174 | { 175 | "name": "suffix", 176 | "title": "Password suffix", 177 | "type": "string", 178 | "value": "" 179 | }, 180 | { 181 | "name": "passwordVisibility", 182 | "title": "Password visibility", 183 | "type": "menulist", 184 | "value": "click", 185 | "options": [ 186 | { 187 | "value": "click", 188 | "label": "Show on click" 189 | }, 190 | { 191 | "value": "hover", 192 | "label": "Show on hover" 193 | }, 194 | { 195 | "value": "always", 196 | "label": "Always show" 197 | } 198 | ] 199 | }, 200 | { 201 | "name": "hotKey", 202 | "title": "Keyboard shortcut", 203 | "description": "Examples: accel-s, meta-shift-i, control-alt-d, control-shift-i (accel is Ctrl/Cmd). Press this shortcut and Return in a password field to fill it automatically.", 204 | "type": "string", 205 | "value": "accel-f6" 206 | } 207 | ], 208 | "devDependencies": { 209 | "browserify": "^12.0.1" 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var self = require('sdk/self'); 2 | var tabs = require('sdk/tabs'); 3 | var urls = require('sdk/url'); 4 | 5 | var makePassword = require('./passwordmaker'); 6 | 7 | var passwords = require('sdk/passwords'); 8 | var prefs = require('sdk/simple-prefs').prefs; 9 | 10 | let { Cc, Ci, Cu } = require('chrome'); 11 | 12 | var instance = Cc['@mozilla.org/moz/jssubscript-loader;1']; 13 | var loader = instance.getService(Ci.mozIJSSubScriptLoader); 14 | 15 | /** 16 | * Check if the addon is installed on a mobile phone or not. 17 | * Useful because a button is added to the toolbar if it's on a desktop, and an entry in the menu if it's on a mobile phone. 18 | * @return {Boolean} True if it's on Firefox for Android, false otherwise. 19 | * @see https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/Code_snippets#Supporting_both_desktop_and_mobile 20 | * @see https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Contributor_s_Guide/Modules 21 | */ 22 | function isOnMobile() { 23 | return (Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULRuntime) 24 | .widgetToolkit.toLowerCase() == 'android'); 25 | } 26 | 27 | var masterPasswdUrl = 'addon:password-maker-x', 28 | masterPasswdUsername = 'undefined', // Firefox doesn't want to store a password without a username 29 | masterPasswdRealm = 'Password Maker X master password', 30 | masterCredential = null; 31 | /** 32 | * Get the master credential. 33 | * @param {Function} callback The callback. Will be called with `{ username: 'blah', password: '42' }` as parameter. 34 | */ 35 | function getMasterCredential(callback) { 36 | if (!masterCredential) { 37 | passwords.search({ 38 | url: masterPasswdUrl, 39 | onComplete: function onComplete(credentials) { 40 | if (credentials[0]) { 41 | masterCredential = credentials[0]; 42 | callback(masterCredential); 43 | } else { // Not found, provide an empty credential 44 | callback({ 45 | username: masterPasswdUsername, 46 | password: '' 47 | }); 48 | } 49 | } 50 | }); 51 | } else { 52 | callback(masterCredential); 53 | } 54 | } 55 | 56 | /** 57 | * Get the current tab's domain name. 58 | * @return {String} The domain name. 59 | */ 60 | function getCurrentDomain() { 61 | var urlStr = tabs.activeTab.url, 62 | url = urls.URL(urlStr), 63 | tld = urls.getTLD(urlStr), 64 | host = url.host || ''; 65 | 66 | if (host && prefs.urlComponents == 'domain') { //Only keep primary domain 67 | var escapedTld = tld.replace(/\./g, '\\.'); 68 | 69 | var domainRegexp = new RegExp('(^|\.)([^\.]+\.'+escapedTld+')$'), 70 | result = domainRegexp.exec(host); 71 | 72 | if (result) { 73 | host = result[2]; 74 | } 75 | } 76 | 77 | return host; 78 | } 79 | 80 | function autoFillPasswd(passwd) { 81 | var worker = tabs.activeTab.attach({ 82 | contentScriptFile: self.data.url('js/worker.js') 83 | }); 84 | worker.port.emit('passwd-auto-fill', passwd); 85 | } 86 | 87 | /** 88 | * A set of available charsets. 89 | * @type {Object} 90 | * @private 91 | */ 92 | var charsets = { 93 | 'alpha': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 94 | 'alphanum': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 95 | 'alphanumsym': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()_-+={}|[]\\:";\'<>?,./', 96 | 'hex': '0123456789abcdef', 97 | 'num': '0123456789', 98 | 'sym': '`~!@#$%^&*()_-+={}|[]\\:";\'<>?,./', 99 | 'custom': prefs.customCharset || '' 100 | }; 101 | 102 | if (!isOnMobile()) { 103 | // These APIs are not supported in Firefox for Android 104 | var { ToggleButton } = require('sdk/ui/button/toggle'); 105 | var { Hotkey } = require('sdk/hotkeys'); 106 | 107 | var panels = require('sdk/panel'); 108 | var clipboard = require('sdk/clipboard'); 109 | 110 | // Displaying a panel 111 | var panel; 112 | function getPanel() { 113 | if (!panel) { 114 | panel = panels.Panel({ 115 | contentURL: self.data.url('panel.html'), 116 | contentScriptFile: self.data.url('js/panel.js'), 117 | height: 170, 118 | onHide: function () { 119 | button.state('window', { checked: false }); 120 | } 121 | }); 122 | panel.on('show', function() { 123 | getMasterCredential(function (cred) { 124 | panel.port.emit('show', { 125 | domain: getCurrentDomain(), 126 | prefs: prefs, 127 | username: cred.username, 128 | passwd: cred.password, 129 | charsets: charsets 130 | }); 131 | }); 132 | }); 133 | 134 | // Hack to display tooltips in panel 135 | // @see http://stackoverflow.com/a/26663026 136 | require('sdk/view/core').getActiveView(panel).setAttribute('tooltip', 'aHTMLTooltip'); 137 | 138 | panel.port.on('passwd-generate', function (data) { 139 | var passwd = ''; 140 | try { 141 | passwd = makePassword(data); 142 | } catch (err) { 143 | console.error('Could not generate password:', err); 144 | } 145 | panel.port.emit('passwd-generated', passwd); 146 | }); 147 | panel.port.on('passwd-auto-fill', function (data) { 148 | getPanel().hide(); 149 | autoFillPasswd(data.passwd); 150 | }); 151 | panel.port.on('passwd-copy', function (data) { 152 | getPanel().hide(); 153 | clipboard.set(data.passwd); 154 | }); 155 | panel.port.on('master-passwd-save', function (data) { 156 | masterCredential = { 157 | url: masterPasswdUrl, 158 | realm: masterPasswdRealm, 159 | username: masterPasswdUsername, 160 | password: data.passwd 161 | }; 162 | passwords.store(masterCredential); 163 | }); 164 | } 165 | return panel; 166 | } 167 | function destroyPanel() { 168 | getPanel().destroy(); // After panel is destroyed, web page can get focus again 169 | panel = null; 170 | } 171 | function showPanel() { 172 | getPanel().show({ 173 | position: button 174 | }); 175 | 176 | if (!button.checked) { 177 | button.state('window', { checked: true }); 178 | } 179 | } 180 | 181 | var showHotKey = Hotkey({ 182 | combo: prefs.hotKey, 183 | onPress: function() { 184 | showPanel(); 185 | } 186 | }); 187 | 188 | // Add a button 189 | var button = ToggleButton({ 190 | id: 'password-maker-btn', 191 | label: 'Password Maker ('+prefs.hotKey+')', 192 | icon: { 193 | '16': './icon-16.png', 194 | '32': './icon-32.png', 195 | '64': './icon-64.png' 196 | }, 197 | onChange: function (state) { 198 | if (state.checked) { 199 | showPanel(); 200 | } 201 | } 202 | }); 203 | } else { 204 | Cu.import('resource://gre/modules/Services.jsm'); 205 | Cu.import('resource://gre/modules/Prompt.jsm'); 206 | 207 | // 'clipboard' module not available on Firefox for Android for now 208 | var clipboardHelper = Cc['@mozilla.org/widget/clipboardhelper;1'].getService(Ci.nsIClipboardHelper); 209 | 210 | var masterPasswd = ''; 211 | /** 212 | * Show the addon dialog. Contains a few inputs: the domain and the master password. 213 | * @param {Window} window The browser window. 214 | */ 215 | var showGeneratorPrompt = function (window) { 216 | // First, get saved credentials 217 | getMasterCredential(function (cred) { 218 | var domain = getCurrentDomain(), 219 | passwd = cred.password || masterPasswd; 220 | 221 | // Open the dialog 222 | var p = new Prompt({ 223 | window: window, 224 | title: 'Generate password', 225 | buttons: ['Copy', 'Autofill', 'Cancel'] 226 | }).addTextbox({ 227 | value: domain, 228 | id: 'domain', 229 | hint: 'Domain', 230 | autofocus: !domain 231 | }).addPassword({ 232 | value: passwd, 233 | id: 'password', 234 | hint: 'Master password', 235 | autofocus: (domain && !passwd) 236 | }).show(function (data) { 237 | if (data.button == 2) { // Cancel 238 | return; 239 | } 240 | 241 | masterPasswd = data.password; // Remember the password for this session 242 | 243 | // Generate the password 244 | var opts = { 245 | data: data.domain, 246 | masterPassword: data.password, 247 | username: (cred.username != 'undefined') ? cred.username : '', 248 | modifier: prefs.modifier || '', 249 | hashAlgorithm: prefs.hashAlgorithm || 'md5', 250 | whereToUseL33t: prefs.useL33t || 'off', 251 | l33tLevel: prefs.l33tLevel || 1, 252 | length: prefs.length || 8, 253 | prefix: prefs.prefix || '', 254 | suffix: prefs.suffix || '', 255 | charset: charsets[prefs.charset] || charsets['alphanumsym'] 256 | }; 257 | 258 | var passwd = makePassword(opts); 259 | 260 | if (data.button == 0) { 261 | // Copy to clipboard 262 | clipboardHelper.copyString(passwd); 263 | 264 | // Display a nice toast 265 | window.NativeWindow.toast.show('Generated password copied to clipboard', 'short'); 266 | } 267 | if (data.button == 1) { 268 | // Autofill password 269 | autoFillPasswd(passwd); 270 | } 271 | }); 272 | }); 273 | }; 274 | 275 | // Add a menu item 276 | var menuId; 277 | var loadIntoWindow = function (window) { 278 | if (!window) { 279 | return; 280 | } 281 | 282 | menuId = window.NativeWindow.menu.add('PasswordMaker', self.data.url('./icon-16.png'), function() { 283 | showGeneratorPrompt(window); 284 | }); 285 | } 286 | 287 | var unloadFromWindow = function (window) { 288 | if (!window) { 289 | return; 290 | } 291 | 292 | window.NativeWindow.menu.remove(menuId); 293 | } 294 | 295 | var windowListener = { 296 | onOpenWindow: function(aWindow) { 297 | // Wait for the window to finish loading 298 | let domWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow); 299 | domWindow.addEventListener('UIReady', function onLoad() { 300 | domWindow.removeEventListener('UIReady', onLoad, false); 301 | loadIntoWindow(domWindow); 302 | }, false); 303 | }, 304 | 305 | onCloseWindow: function(aWindow) {}, 306 | onWindowTitleChange: function(aWindow, aTitle) {} 307 | }; 308 | 309 | // Load into any existing windows 310 | let windows = Services.wm.getEnumerator('navigator:browser'); 311 | while (windows.hasMoreElements()) { 312 | let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow); 313 | loadIntoWindow(domWindow); 314 | } 315 | 316 | // Load into any new windows 317 | Services.wm.addListener(windowListener); 318 | 319 | // TODO: remove UI on shutdown 320 | } 321 | --------------------------------------------------------------------------------