├── .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 |
--------------------------------------------------------------------------------
/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 | 
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 | 
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 |
--------------------------------------------------------------------------------