├── .gitignore ├── assets ├── img │ ├── save.png │ ├── sgp.png │ ├── clipboard.png │ ├── loading.gif │ ├── options.png │ ├── sgp@57x57.png │ ├── sgp@72x72.png │ ├── sgp@112x112.png │ ├── sgp@144x144.png │ ├── save-checked.png │ └── clipboard-checked.png └── css │ └── style.css ├── grunt ├── options │ ├── clean.js │ ├── qunit.js │ ├── pkg.js │ ├── browserify.js │ ├── template.js │ ├── uglify.js │ ├── checksum.js │ ├── staticinline.js │ ├── cssmin.js │ ├── bookmarklet.js │ ├── manifest.js │ └── jshint.js └── tasks │ ├── aliases.js │ ├── bookmarklet.js │ └── checksum.js ├── .codeclimate.yml ├── mobile └── cache.manifest ├── Gruntfile.js ├── checksums.json ├── CHANGELOG ├── .travis.yml ├── 404.html ├── test ├── qunit │ ├── index.html │ └── mobile.js └── intern │ ├── main.js │ └── functional │ └── mobile.js ├── package.json ├── .jscsrc ├── .jshintrc ├── src ├── homepage │ └── index.html.tmpl ├── mobile │ ├── lib │ │ ├── localstorage-polyfill.js │ │ ├── shortcut.js │ │ └── identicon5.js │ ├── index.html │ ├── sgp.mobile.js │ └── sgp.mobile.css └── bookmarklet │ └── sgp.bookmarklet.js ├── README.md ├── bookmarklet └── bookmarklet.min.js ├── index.html └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | build 4 | -------------------------------------------------------------------------------- /assets/img/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/save.png -------------------------------------------------------------------------------- /assets/img/sgp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp.png -------------------------------------------------------------------------------- /assets/img/clipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/clipboard.png -------------------------------------------------------------------------------- /assets/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/loading.gif -------------------------------------------------------------------------------- /assets/img/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/options.png -------------------------------------------------------------------------------- /assets/img/sgp@57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp@57x57.png -------------------------------------------------------------------------------- /assets/img/sgp@72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp@72x72.png -------------------------------------------------------------------------------- /grunt/options/clean.js: -------------------------------------------------------------------------------- 1 | /* grunt-contrib-clean */ 2 | 3 | module.exports = { 4 | app: ['build'] 5 | }; 6 | -------------------------------------------------------------------------------- /assets/img/sgp@112x112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp@112x112.png -------------------------------------------------------------------------------- /assets/img/sgp@144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp@144x144.png -------------------------------------------------------------------------------- /assets/img/save-checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/save-checked.png -------------------------------------------------------------------------------- /grunt/options/qunit.js: -------------------------------------------------------------------------------- 1 | /* grunt-contrib-qunit */ 2 | 3 | module.exports = { 4 | app: ['test/qunit/*.html'] 5 | }; 6 | -------------------------------------------------------------------------------- /assets/img/clipboard-checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/clipboard-checked.png -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_paths: 2 | - bookmarklet/* 3 | - grunt/* 4 | - src/mobile/lib/* 5 | languages: 6 | JavaScript: true 7 | -------------------------------------------------------------------------------- /grunt/options/pkg.js: -------------------------------------------------------------------------------- 1 | /* Load package.json */ 2 | 3 | 'use strict'; 4 | 5 | module.exports = function (grunt) { 6 | return grunt.file.readJSON('package.json'); 7 | }; 8 | -------------------------------------------------------------------------------- /grunt/options/browserify.js: -------------------------------------------------------------------------------- 1 | /* grunt-browserify */ 2 | 3 | module.exports = { 4 | dist: { 5 | files: { 6 | 'build/mobile.js': ['src/mobile/*.js'] 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /grunt/options/template.js: -------------------------------------------------------------------------------- 1 | /* grunt-template */ 2 | 3 | module.exports = { 4 | app: { 5 | files: { 6 | 'index.html': ['src/homepage/index.html.tmpl'] 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /grunt/options/uglify.js: -------------------------------------------------------------------------------- 1 | /* grunt-contrib-uglify */ 2 | 3 | module.exports = { 4 | app: { 5 | files: { 6 | 'build/mobile.min.js': [ 7 | 'build/mobile.js' 8 | ] 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /mobile/cache.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # This manifest was generated by grunt-manifest HTML5 Cache Manifest Generator 3 | # Time: Thu Aug 25 2016 20:55:39 GMT-0400 (EDT) 4 | 5 | CACHE: 6 | index.html 7 | 8 | NETWORK: 9 | * 10 | -------------------------------------------------------------------------------- /grunt/options/checksum.js: -------------------------------------------------------------------------------- 1 | /* Checksum options */ 2 | 3 | module.exports = { 4 | app: { 5 | src: [ 6 | 'mobile/index.html', 7 | 'bookmarklet/bookmarklet.min.js' 8 | ], 9 | dest: 'checksums.json' 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /grunt/options/staticinline.js: -------------------------------------------------------------------------------- 1 | /* grunt-static-inline */ 2 | 3 | module.exports = { 4 | app: { 5 | options: { 6 | basepath: './' 7 | }, 8 | files: { 9 | 'mobile/index.html': 'src/mobile/index.html' 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Static Gruntfile 3 | - Load Grunt plugins with `load-grunt-tasks`. 4 | - Load Grunt options and aliases with `grunt-load-options`. 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = function (grunt) { 10 | require('load-grunt-tasks')(grunt); 11 | }; 12 | -------------------------------------------------------------------------------- /checksums.json: -------------------------------------------------------------------------------- 1 | {"bookmarklet/bookmarklet.min.js":"c8c2d64724351a752c2feafab551e030c405f52a4006588c5813a5e9218b2a5a4a57a88f70b8771137bb434ede9ffdae9c6395dc091627c9634f3a173cdc64ba","mobile/index.html":"435c36cb2c8fb88513a4dafe9e90a303a64cf2891655d1fed74a1b2638c0648977f6fd82c19b904738722b87ccebaeabc9e507e386a1901493e8fc9f4f9fb778"} -------------------------------------------------------------------------------- /grunt/options/cssmin.js: -------------------------------------------------------------------------------- 1 | /* grunt-contrib-cssmin */ 2 | 3 | /*jshint camelcase: false*/ 4 | /*jscs: disable requireCamelCaseOrUpperCaseIdentifiers */ 5 | 6 | module.exports = { 7 | add_banner: { 8 | files: { 9 | 'build/mobile.min.css': [ 10 | 'src/mobile/*.css' 11 | ] 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /grunt/options/bookmarklet.js: -------------------------------------------------------------------------------- 1 | /* Bookmarklet options */ 2 | 3 | module.exports = { 4 | app: { 5 | options: { 6 | anonymize: false, 7 | mangleVars: true, 8 | urlencode: true 9 | }, 10 | files: { 11 | 'bookmarklet/bookmarklet.min.js': ['src/bookmarklet/sgp.bookmarklet.js'] 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /grunt/options/manifest.js: -------------------------------------------------------------------------------- 1 | /* grunt-manifest */ 2 | 3 | module.exports = { 4 | generate: { 5 | options: { 6 | basePath: 'mobile/', 7 | network: ['*'], 8 | verbose: true, 9 | timestamp: true 10 | }, 11 | src: [ 12 | 'index.html' 13 | ], 14 | dest: 'mobile/cache.manifest' 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /grunt/options/jshint.js: -------------------------------------------------------------------------------- 1 | /* grunt-contrib-jshint */ 2 | 3 | module.exports = { 4 | options: { 5 | ignores: [ 6 | 'src/mobile/lib/**/*.js' 7 | ], 8 | jshintrc: true 9 | }, 10 | app: [ 11 | 'src/**/*.js' 12 | ], 13 | grunt: [ 14 | 'grunt/**/*.js' 15 | ], 16 | tests: [ 17 | 'test/**/*.js' 18 | ] 19 | }; 20 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 0.3.6 4 | 5 | - BEHAVIOR CHANGE: SGP no longer automatically saves any changes to advanced 6 | options whenever you generate a password. There is a new "save" button, 7 | available when you are viewing the advanced options, that allows you to 8 | manually save changes to advanced options. 9 | 10 | - Added hover text (title attributes) to some elements to provide functional 11 | explanations. 12 | -------------------------------------------------------------------------------- /grunt/tasks/aliases.js: -------------------------------------------------------------------------------- 1 | /* Grunt task aliases */ 2 | 3 | 'use strict'; 4 | 5 | module.exports = function (grunt) { 6 | 7 | grunt.registerTask( 8 | 'default', 9 | [ 10 | 'jshint', 11 | 'browserify', 12 | 'uglify', 13 | 'cssmin', 14 | 'staticinline', 15 | 'bookmarklet', 16 | 'template', 17 | 'clean', 18 | 'qunit', 19 | 'manifest', 20 | 'checksum' 21 | ] 22 | ); 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "5" 5 | before_install: npm install -g grunt-cli 6 | env: 7 | global: 8 | - secure: EpYkviZ1vRrXesZp221s0boZnhaQCLNcj0nX0p8shLH9wgR94Alc57THAp5g+1n3rziY/FrezI5jSr8J+6u3lNqOdkfsZ+zvzBQAOgtCKg7o8I4b0o0C/xLg1+dxoVTjEdXZhi1QskHzrNBvtZraSX6fW+XTaT3/hi5lwnpq04k= 9 | - secure: U1xFgm37PbYC9H8g4J9uWSHHxqvv/0AluL+4muJ6H9d8HaQV34WUTR6iRBusWn34Zc5m/aVeCreMInobknbM/9El66Q5B3tMHj7qMudrVGAyzrC6oaTYp4RtJexQ3XFJ0qCe79/wHWcJ+NWkIzToX5renuuME7ULMIzcPqDGIPg= 10 | -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 404 Not Found 12 | 13 | 14 | 15 | 16 | 17 |

Page not found

18 | 19 |

The requested page was not found. Sorry about that! You can probably find what you need on the homepage.

20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/qunit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | SGP QUnit tests 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SuperGenPass", 3 | "version": "0.3.11", 4 | "dependencies": { 5 | "crypto-js": "^3.1.6", 6 | "jquery": "^3.1.0", 7 | "supergenpass-lib": "^3.0.1" 8 | }, 9 | "devDependencies": { 10 | "bookmarkleter": "^0.2.2", 11 | "browserify": "^13.1.0", 12 | "grunt": "^1.0.1", 13 | "grunt-browserify": "^5.0.0", 14 | "grunt-contrib-clean": "^1.0.0", 15 | "grunt-contrib-concat": "^1.0.1", 16 | "grunt-contrib-cssmin": "^1.0.1", 17 | "grunt-contrib-jshint": "^1.0.0", 18 | "grunt-contrib-qunit": "^1.2.0", 19 | "grunt-contrib-uglify": "^2.0.0", 20 | "grunt-load-options": "^0.2.1", 21 | "grunt-manifest": "^0.4.4", 22 | "grunt-static-inline": "^0.1.9", 23 | "grunt-template": "^1.0.0", 24 | "intern": "^3.2.3", 25 | "load-grunt-tasks": "^3.5.0", 26 | "qunitjs": "^2.0.1" 27 | }, 28 | "description": "A bookmarklet password generator.", 29 | "homepage": "https://chriszarate.github.io/supergenpass/", 30 | "main": "Gruntfile.js", 31 | "scripts": { 32 | "test": "grunt qunit && [ \"$TRAVIS_PULL_REQUEST\" != \"true\" ] && node_modules/.bin/intern-runner config=test/intern/main" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/chriszarate/supergenpass" 37 | }, 38 | "author": { 39 | "name": "Chris Zarate", 40 | "url": "http://chris.zarate.org" 41 | }, 42 | "license": "GPL-2.0" 43 | } 44 | -------------------------------------------------------------------------------- /grunt/tasks/bookmarklet.js: -------------------------------------------------------------------------------- 1 | /* Bookmarklet task */ 2 | 3 | 'use strict'; 4 | 5 | var bookmarkleter = require('bookmarkleter'); 6 | 7 | module.exports = function (grunt) { 8 | 9 | var description = 'Generate a bookmarklet from JavaScript code.'; 10 | 11 | grunt.registerMultiTask('bookmarklet', description, function () { 12 | 13 | // Merge task-specific and/or target-specific options with these defaults. 14 | var options = this.options({ 15 | anonymize: true, 16 | urlencode: true, 17 | mangleVars: true, 18 | jQuery: false 19 | }); 20 | 21 | // File exists helper. 22 | var fileExists = function (filepath) { 23 | if (grunt.file.exists(filepath)) { 24 | return true; 25 | } else { 26 | grunt.log.warn('Source file "' + filepath + '" not found.'); 27 | return false; 28 | } 29 | }; 30 | 31 | // Read and return the file's source. 32 | var readFile = function (filepath) { 33 | return (fileExists(filepath)) ? grunt.file.read(filepath) : ''; 34 | }; 35 | 36 | this.files.forEach(function (file) { 37 | 38 | // Load files and create bookmarklet. 39 | var contents = file.src.map(readFile).join(''); 40 | var bookmarklet = bookmarkleter(contents, options); 41 | 42 | // Write joined contents to destination filepath. 43 | grunt.file.write(file.dest, bookmarklet); 44 | grunt.log.writeln('Bookmarklet: ' + file.dest); 45 | 46 | }); 47 | 48 | }); 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowEmptyBlocks": true, 3 | "disallowSpacesInsideArrayBrackets": true, 4 | "disallowSpacesInsideParentheses": true, 5 | "disallowSpaceAfterObjectKeys": true, 6 | "disallowSpaceAfterPrefixUnaryOperators": true, 7 | "disallowSpaceBeforePostfixUnaryOperators": true, 8 | "disallowSpaceBeforeBinaryOperators": [ 9 | "," 10 | ], 11 | "disallowMixedSpacesAndTabs": true, 12 | "disallowTrailingWhitespace": true, 13 | "disallowTrailingComma": true, 14 | "disallowYodaConditions": true, 15 | "disallowKeywords": [ "with" ], 16 | "disallowMultipleVarDecl": true, 17 | "requireSpaceBeforeBlockStatements": true, 18 | "requireParenthesesAroundIIFE": true, 19 | "requireSpacesInConditionalExpression": true, 20 | "requireBlocksOnNewline": 1, 21 | "requireCommaBeforeLineBreak": true, 22 | "requireSpaceBeforeBinaryOperators": true, 23 | "requireSpaceAfterBinaryOperators": true, 24 | "requireCamelCaseOrUpperCaseIdentifiers": true, 25 | "requireLineFeedAtFileEnd": true, 26 | "requireCapitalizedConstructors": true, 27 | "requireDotNotation": true, 28 | "requireSpacesInForStatement": true, 29 | "requireSpaceBetweenArguments": true, 30 | "requireCurlyBraces": [ 31 | "function", 32 | "if", 33 | "else", 34 | "for", 35 | "while", 36 | "do", 37 | "switch", 38 | "try", 39 | "catch" 40 | ], 41 | "requireSpaceAfterKeywords": [ 42 | "function", 43 | "if", 44 | "else", 45 | "for", 46 | "while", 47 | "do", 48 | "switch", 49 | "case", 50 | "return", 51 | "try", 52 | "catch", 53 | "typeof" 54 | ], 55 | "validateLineBreaks": "LF", 56 | "validateQuoteMarks": "'", 57 | "validateIndentation": 2 58 | } 59 | -------------------------------------------------------------------------------- /test/intern/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*global define*/ 4 | 5 | // Learn more about configuring this file at . 6 | // These default settings work OK for most people. The options that *must* be changed below are the 7 | // packages, suites, excludeInstrumentation, and (if you want functional tests) functionalSuites. 8 | define({ 9 | // The port on which the instrumenting proxy will listen 10 | proxyPort: 9000, 11 | 12 | // A fully qualified URL to the Intern proxy 13 | proxyUrl: 'http://localhost:9000/', 14 | 15 | // Browsers to run integration testing against. Note that version numbers must be strings if used with Sauce 16 | // OnDemand. Options that will be permutated are browserName, version, platform, and platformVersion; any other 17 | // capabilities options specified for an environment will be copied as-is 18 | environments: [ 19 | { browserName: 'android', version: '4.4', platform: 'Linux' }, 20 | { browserName: 'iphone', version: '9.2', platform: 'OS X 10.10' }, 21 | { browserName: 'internet explorer', version: '11.0', platform: 'Windows 7', requireWindowFocus: 'true' }, 22 | { browserName: 'firefox', version: '45.0', platform: 'Windows 7' }, 23 | { browserName: 'chrome', version: '50.0', platform: 'Windows 7' }, 24 | { browserName: 'safari', version: '8.0', platform: 'OS X 10.10' } 25 | ], 26 | 27 | // Maximum number of simultaneous integration tests that should be executed on the remote WebDriver service 28 | maxConcurrency: 2, 29 | 30 | // Name of the tunnel class to use for WebDriver tests 31 | tunnel: 'SauceLabsTunnel', 32 | 33 | // Functional test suite(s) to run in each browser once non-functional tests are completed 34 | functionalSuites: ['test/intern/functional/mobile'], 35 | 36 | // A regular expression matching URLs to files that should not be included in code coverage analysis 37 | excludeInstrumentation: /^bower_components|node_modules\// 38 | }); 39 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "maxerr" : 50, 4 | 5 | "bitwise" : true, 6 | "camelcase" : true, 7 | "curly" : true, 8 | "eqeqeq" : true, 9 | "forin" : true, 10 | "freeze" : true, 11 | "immed" : true, 12 | "indent" : 2, 13 | "latedef" : true, 14 | "newcap" : true, 15 | "noarg" : true, 16 | "noempty" : true, 17 | "nonbsp" : true, 18 | "nonew" : true, 19 | "plusplus" : true, 20 | "quotmark" : true, 21 | 22 | "undef" : true, 23 | "unused" : true, 24 | "strict" : true, 25 | "maxparams" : false, 26 | "maxdepth" : false, 27 | "maxstatements" : false, 28 | "maxcomplexity" : false, 29 | "maxlen" : false, 30 | 31 | "asi" : false, 32 | "boss" : false, 33 | "debug" : false, 34 | "eqnull" : false, 35 | "es5" : false, 36 | "esnext" : false, 37 | "moz" : false, 38 | 39 | "evil" : false, 40 | "expr" : false, 41 | "funcscope" : false, 42 | "globalstrict" : false, 43 | "iterator" : false, 44 | "lastsemic" : false, 45 | "laxbreak" : false, 46 | "laxcomma" : false, 47 | "loopfunc" : false, 48 | "multistr" : false, 49 | "noyield" : false, 50 | "notypeof" : false, 51 | "proto" : false, 52 | "scripturl" : false, 53 | "shadow" : false, 54 | "sub" : false, 55 | "supernew" : false, 56 | "validthis" : false, 57 | 58 | "browser" : false, 59 | "couch" : false, 60 | "devel" : false, 61 | "dojo" : false, 62 | "jasmine" : false, 63 | "jquery" : false, 64 | "mocha" : false, 65 | "mootools" : false, 66 | "node" : true, 67 | "nonstandard" : false, 68 | "prototypejs" : false, 69 | "qunit" : false, 70 | "rhino" : false, 71 | "shelljs" : false, 72 | "worker" : false, 73 | "wsh" : false, 74 | "yui" : false, 75 | 76 | "globals" : {} 77 | 78 | } 79 | -------------------------------------------------------------------------------- /grunt/tasks/checksum.js: -------------------------------------------------------------------------------- 1 | /* Checksum task */ 2 | 3 | 'use strict'; 4 | 5 | module.exports = function (grunt) { 6 | 7 | var description = 'Generate checksums for specified files.'; 8 | 9 | grunt.registerMultiTask('checksum', description, function () { 10 | 11 | var done = this.async(); 12 | var crypto = require('crypto'); 13 | var fs = require('fs'); 14 | 15 | // Merge task-specific and/or target-specific options with these defaults. 16 | var options = this.options({ 17 | algorithm: 'sha512', 18 | basepath: false 19 | }); 20 | 21 | // Placeholder for checksums. 22 | var result = {}; 23 | 24 | // Task counter. 25 | var tasks = this.files.length; 26 | 27 | // Check for basepath. 28 | var removeBasepath = function (filepath) { 29 | return (filepath.substring(0, options.basepath.length) === options.basepath) ? filepath.substring(options.basepath.length) : filepath; 30 | }; 31 | 32 | // Generate checksum. 33 | var checkSum = function (filepath, dest) { 34 | var shasum = crypto.createHash(options.algorithm); 35 | var stream = fs.ReadStream(filepath); 36 | stream.on('data', function (data) { 37 | shasum.update(data); 38 | }); 39 | stream.on('end', function () { 40 | var sum = shasum.digest('hex'); 41 | writeSum(removeBasepath(filepath), dest, sum); 42 | }); 43 | }; 44 | 45 | // Write to checksum file. 46 | var writeSum = function (src, dest, sum) { 47 | result[dest].sums[src] = sum; 48 | if (Object.keys(result[dest].sums).length === result[dest].count) { 49 | grunt.file.write(dest, JSON.stringify(result[dest].sums)); 50 | grunt.log.writeln('Checksums: ' + dest); 51 | if (!tasks) { 52 | done(); 53 | } 54 | tasks = tasks - 1; 55 | } 56 | }; 57 | 58 | // Process files. 59 | this.files.forEach(function (file) { 60 | 61 | // Stash checksum destination in scope. 62 | var dest = file.dest; 63 | 64 | // Create a placeholder record for this checksum task. 65 | result[dest] = { 66 | count: file.src.length, 67 | sums: {} 68 | }; 69 | 70 | // Loop through each source file. 71 | file.src.forEach(function (src) { 72 | checkSum(src, dest); 73 | }); 74 | 75 | }); 76 | 77 | }); 78 | 79 | }; 80 | -------------------------------------------------------------------------------- /src/homepage/index.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | SuperGenPass: A Free Bookmarklet Password Generator 12 | 13 | 14 | 15 | 16 | 17 |
18 |

Key

19 |
20 | 21 |
22 | 23 |

SuperGenPass is a different kind of password solution. Instead of storing your passwords on your hard disk or online—where they are vulnerable to theft and data loss—SuperGenPass uses a hash algorithm to transform a master password into unique, complex passwords for the Web sites you visit.

24 | 25 |

SuperGenPass is a bookmarklet and runs right in your Web browser. It never stores or transmits your passwords, so it’s ideal for use on multiple and public computers. It’s also completely free and open-sourced on GitHub. 26 | 27 |

28 | 29 |
30 |

SGP

31 |

Drag this to your bookmarks toolbar. (Or right-click and add to bookmarks.)

32 |
33 |
34 |

Mobile

35 |

Use on public computers and mobile devices. (Add it to your home screen!)

36 |
37 | 38 |
39 | 40 |
41 | 42 |

Should I use SuperGenPass?

43 |

Maybe! Do you like bookmarklets? Do you like not ever knowing what your passwords are? (That’s a good thing!) Do you like the idea of using a slightly quirky password solution? You do?

44 | 45 |

I have a question!

46 |

Please take a look at the FAQ on the SGP wiki. If you still have questions, please open an issue on GitHub.

47 | 48 |

Looking for GenPass?

49 | 50 |
51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SuperGenPass 2 | 3 | [![Build status][build-status]][travis-ci] 4 | 5 | [![SauceLabs status][saucelabs-status]][saucelabs] 6 | 7 | This is the official repository of [SuperGenPass][sgp]. It contains code for the 8 | bookmarklet and the mobile version. The underlying algorithm that is used by 9 | SuperGenPass to generate passwords has its own repository: 10 | [chriszarate/supergenpass-lib][sgp-lib]. 11 | 12 | Please see the [homepage][sgp] to obtain the latest stable version of the 13 | bookmarklet and the mobile version. For questions, please see the [FAQ][faq]. 14 | 15 | ## About 16 | 17 | SuperGenPass is a different kind of password solution. Instead of storing your 18 | passwords on your hard disk or online—where they are vulnerable to theft and 19 | data loss—SuperGenPass uses a hash algorithm to transform a master password 20 | into unique, complex passwords for the Web sites you visit. 21 | 22 | ``` 23 | SuperGenPass("masterpassword:example1.com") // => zVNqyKdf7F 24 | SuperGenPass("masterpassword:example2.com") // => eYPtU3mfVw 25 | ``` 26 | 27 | SuperGenPass is a bookmarklet and runs right in your Web browser. It *never 28 | stores or transmits your passwords*, so it’s ideal for use on multiple and 29 | public computers. It’s also completely free and open source. 30 | 31 | ## Should I use SuperGenPass? 32 | 33 | Maybe! Do you like bookmarklets? Do you like *not knowing* what your passwords 34 | are? Do you like the idea of using a slightly quirky password solution? You 35 | *do*? 36 | 37 | ## Develop locally 38 | 39 | SuperGenPass development requires [Grunt][grunt]: 40 | 41 | ```shell 42 | git clone https://github.com/chriszarate/supergenpass.git && cd supergenpass 43 | npm install 44 | grunt 45 | ``` 46 | 47 | ## Looking for GenPass? 48 | 49 | GenPass is maintained in its own repository: [chriszarate/genpass][gp]. 50 | 51 | 52 | ## License 53 | 54 | SuperGenPass is released under the [GNU General Public License version 2][gplv2]. 55 | 56 | 57 | ## Other implementations 58 | 59 | Since SuperGenPass is open-source, others have made their own versions for 60 | various platforms and with additional functionality. You can find a list on the 61 | [implementations wiki page][implementations]. Please note that these projects 62 | are not reviewed and compatibility is not guaranteed. 63 | 64 | 65 | [sgp]: http://supergenpass.com 66 | [sgp-lib]: https://github.com/chriszarate/supergenpass-lib 67 | [build-status]: https://travis-ci.org/chriszarate/supergenpass.svg?branch=master 68 | [travis-ci]: https://travis-ci.org/chriszarate/supergenpass 69 | [saucelabs-status]: https://saucelabs.com/browser-matrix/supergenpass.svg 70 | [saucelabs]: https://saucelabs.com/u/supergenpass 71 | [faq]: https://github.com/chriszarate/supergenpass/wiki/FAQ 72 | [grunt]: http://gruntjs.com 73 | [gp]: https://github.com/chriszarate/genpass 74 | [gplv2]: http://www.gnu.org/licenses/gpl-2.0.html 75 | [implementations]: https://github.com/chriszarate/supergenpass/wiki/Implementations 76 | -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | /* Lato Web font */ 2 | 3 | @font-face { 4 | font-family: 'Lato'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('Lato Regular'), local('Lato-Regular'), url(https://themes.googleusercontent.com/static/fonts/lato/v6/qIIYRU-oROkIk8vfvxw6QvesZW2xOQ-xsNqO47m55DA.woff) format('woff'); 8 | } 9 | 10 | @font-face { 11 | font-family: 'Lato'; 12 | font-style: normal; 13 | font-weight: 700; 14 | src: local('Lato Bold'), local('Lato-Bold'), url(https://themes.googleusercontent.com/static/fonts/lato/v6/qdgUG4U09HnJwhYI-uK18wLUuEpTyoUstqEm5AMlJo4.woff) format('woff'); 15 | } 16 | 17 | 18 | /* Document */ 19 | 20 | body { 21 | max-width: 35em; 22 | margin: 0 auto 1em auto; 23 | padding: 2em 3em; 24 | padding-top: 0; 25 | font-family: 'Lato', Helvetica, Arial, sans-serif; 26 | font-size: 1.3em; 27 | background-color: #fff; 28 | line-height: 1.5em; 29 | } 30 | 31 | header { 32 | text-align: center; 33 | } 34 | 35 | h1, h2, h3 { 36 | margin: 2em 0 1em 0; 37 | line-height: 1.25em; 38 | } 39 | 40 | h1 { 41 | margin-top: 1.5em; 42 | } 43 | 44 | h4 { 45 | margin: 0.5em 0 0 0; 46 | } 47 | 48 | a { 49 | color: #369; 50 | font-weight: bold; 51 | text-decoration: none; 52 | } 53 | 54 | a:hover { 55 | text-decoration: underline; 56 | } 57 | 58 | 59 | /* Actions */ 60 | 61 | #actions { 62 | margin: 2em 0; 63 | padding: 1em 0; 64 | border-top: solid 1px #ccc; 65 | border-bottom: solid 1px #ccc; 66 | } 67 | 68 | .well { 69 | width: 45%; 70 | } 71 | 72 | .bookmarklet { 73 | float: left; 74 | } 75 | 76 | .mobile { 77 | float: right; 78 | } 79 | 80 | .well p { 81 | color: #666; 82 | font-size: 0.9em; 83 | text-align: center; 84 | } 85 | 86 | .well a { 87 | display: inline-block; 88 | width: 6em; 89 | padding: 8px 0; 90 | color: #fff; 91 | background-color: #356; 92 | font-weight: bold; 93 | border-bottom: solid 2px #023; 94 | border-radius: 4px; 95 | } 96 | 97 | .well a:hover { 98 | text-decoration: none; 99 | background-color: #467; 100 | border-bottom: solid 2px #134; 101 | } 102 | 103 | 104 | /* Code */ 105 | 106 | pre { 107 | float: left; 108 | margin: 0; 109 | padding: 0.25em 0.75em; 110 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace; 111 | font-size: 1em; 112 | line-height: 1.2em; 113 | color: #666; 114 | background-color: #f7f7f9; 115 | border: 1px solid #e1e1e8; 116 | } 117 | 118 | strong.function { 119 | color: #333; 120 | } 121 | 122 | span.string { 123 | color: #690; 124 | } 125 | 126 | span.comment { 127 | color: #708090; 128 | } 129 | 130 | 131 | /* Tablet-y */ 132 | 133 | @media only screen and (max-width: 770px) { 134 | 135 | /* Tighten */ 136 | 137 | body { 138 | padding: 0 2em 2em 2em; 139 | } 140 | 141 | h1, h2, h3 { 142 | margin: 1.5em 0 0.75em 0; 143 | } 144 | 145 | h1 { 146 | margin-top: 1em; 147 | } 148 | 149 | pre { 150 | font-size: 0.8em; 151 | } 152 | 153 | header img { 154 | width: 150px; 155 | height: 100px; 156 | } 157 | 158 | } 159 | 160 | 161 | /* Phone-y */ 162 | 163 | @media only screen and (max-width: 550px) { 164 | 165 | /* Tighten */ 166 | 167 | body { 168 | margin: 0; 169 | padding: 0 1em 1em 1em; 170 | font-size: 1.1em; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /bookmarklet/bookmarklet.min.js: -------------------------------------------------------------------------------- 1 | javascript:%22use%20strict%22;void%20function(e){var%20t=20150216,n=%22https://chriszarate.github.io%22,o=%22https://chriszarate.github.io/supergenpass/mobile/%22,i=1e5,a=2,s=%22SGP%20may%20be%20blocked%20on%20this%20site%20by%20a%20security%20setting.%22,r=%22Would%20you%20like%20to%20open%20the%20mobile%20version%3F%22,c='You%20may%20wish%20to%20%3Ca%20href=%22'+o+'%22%20target=%22_blank%22%3Eopen%20the%20mobile%20version%3C/a%3E.',d=function(){var%20e=confirm(s+%22%20%22+r);e%26%26window.open(o)},p=function(e){var%20r=e(document),p=r,u=!1,f=0,l=function(){try{var%20e=%22_%22+(new%20Date).getTime(),t=this.contentWindow;if(t[e]=e,t[e]===e)return%20p.add(t.document),!0}catch(n){return!1}},h=function(){try{var%20t=e(this).height()*e(this).width();t%3Ef%26%26t%3Ei%26%26(r=e(this.contentWindow.document),f=t)}catch(n){}},g=function(){D.remove()},m=function(){D.html(s+%22%20%22+c)},v=function(){try{this.contentWindow.postMessage('{%22version%22:'+t+%22}%22,n)}catch(e){m()}},b=function(e){var%20t=e.originalEvent;t.origin===n%26%26%22undefined%22!=typeof%20t.data%26%26(g(),clearTimeout(M),y(JSON.parse(t.data)))},y=function(t){e.each(t,function(e,t){switch(e){case%22result%22:x(t);break;case%22height%22:w(Math.max(parseInt(t,10),167)+2)}})},x=function(t){e(%22input:password:visible%22,p).css(%22background%22,%22%239f9%22).val(t).trigger(%22change%20click%22).on(%22input%22,k).focus()},w=function(e){G.css(%22height%22,e)},k=function(){e(this).css(%22background%22,%22%23fff%22)},j=function(){L.remove()},z=%22font-family:sans-serif;font-size:18px;line-height:20px;%22,E=%22z-index:99999;position:absolute;top:0;right:5px;width:258px;margin:0;padding:0;box-sizing:content-box;%22+z,T=%22overflow:hidden;width:258px;height:20px;margin:0;padding:0;text-align:right;background-color:%23356;cursor:move;box-sizing:content-box;%22+z,S=%22padding:0%205px;color:%23fff;cursor:pointer;%22+z,W=%22position:absolute;width:258px;height:190px;padding:15px;color:%23333;background-color:%23fff;font-family:monospace;font-size:15px;text-align:center;%22,q=%22position:static;width:258px;height:190px;border:none;overflow:hidden;pointer-events:auto;%22,L=e(%22%3Cdiv/%3E%22,{style:E}),Y=e(%22%3Cdiv/%3E%22,{style:T}),C=e(%22%3Cspan/%3E%22,{style:S}).append(%22×%22),D=e(%22%3Cdiv/%3E%22,{style:W}).append(%22Loading%20SGP%20...%22),G=e(%22%3Ciframe/%3E%22,{src:o,scrolling:%22no%22,style:q});e(%22frame%22).filter(l).each(h),e(%22iframe%22,r).filter(l).each(h),r||d();var%20M=setTimeout(m,1e3*a);C.on(%22click%22,j),Y.on(%22dblclick%22,j),L.css(%22top%22,r.scrollTop()+%22px%22),e(document.activeElement).blur(),G.on(%22load%22,v),e(window).on(%22message%22,b),Y.append(C),L.append(Y,D,G).appendTo(e(%22body%22,r)),Y.on({mousedown:function(e){var%20t=L.offset();u=[e.pageX-t.left,e.pageY-t.top],G.css(%22pointer-events%22,%22none%22),e.preventDefault()},mouseup:function(){u=!1,G.css(%22pointer-events%22,%22auto%22)}}),r.on(%22mousemove%22,function(e){u%26%26L.css({left:e.pageX-u[0],top:e.pageY-u[1]})})};if(e%26%26e.fn%26%26parseFloat(e.fn.jquery)%3E=1.7)p(e);else{var%20u=document.createElement(%22script%22);u.src=%22//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js%22,u.onload=u.onreadystatechange=function(){var%20e=this.readyState;e%26%26%22loaded%22!==e%26%26%22complete%22!==e||p(jQuery.noConflict())},u.addEventListener(%22error%22,d),u.addEventListener(%22abort%22,d),document.getElementsByTagName(%22head%22)[0].appendChild(u)}}(window.jQuery); -------------------------------------------------------------------------------- /test/qunit/mobile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*jshint browser: true, jquery: true, latedef: false, qunit: true*/ 4 | 5 | QUnit.module('SuperGenPass mobile version'); 6 | 7 | // Enumerate jQuery selectors for caching. 8 | var $iframe = $('#SGP'); 9 | var $el = {}; 10 | var selectors = 11 | [ 12 | 'Passwd', 13 | 'Secret', 14 | 'Domain', 15 | 'RemoveSubdomains', 16 | 'Len', 17 | 'MethodMD5', 18 | 'MethodSHA512', 19 | 'SaveDefaults', 20 | 'Generate', 21 | 'Output' 22 | ]; 23 | 24 | // Clear local storage. 25 | localStorage.clear(); 26 | 27 | // Create click event. 28 | var clickEvent = document.createEvent('Event'); 29 | clickEvent.initEvent('click', true, true); 30 | 31 | // Populate selector cache. 32 | $.each(selectors, function (i, val) { 33 | $el[val] = $('#' + val, $iframe[0].contentWindow.document); 34 | }); 35 | 36 | // Send click event. 37 | var sendClick = function () { 38 | $el.Generate[0].dispatchEvent(clickEvent); 39 | }; 40 | 41 | 42 | /* Tests */ 43 | 44 | var suite = [ 45 | { 46 | name: 'initial input', 47 | setup: function () { 48 | $el.Passwd.val('test'); 49 | $el.Secret.val('secret'); 50 | $el.Domain.val('https://login.example.com'); 51 | $el.Len.val('10'); 52 | $el.MethodMD5.prop('checked', true); 53 | $el.RemoveSubdomains.prop('checked', true); 54 | }, 55 | expect: 'iTbF7RViG5' 56 | }, 57 | { 58 | name: 'remove secret', 59 | setup: function () { 60 | $el.Secret.val(''); 61 | }, 62 | expect: 'w9UbG0NEk7' 63 | }, 64 | { 65 | name: 'using SHA512', 66 | setup: function () { 67 | $el.MethodSHA512.prop('checked', true); 68 | }, 69 | expect: 'sJfoZg3nU8' 70 | }, 71 | { 72 | name: 'change length to 2', 73 | setup: function () { 74 | $el.Len.val('2'); 75 | }, 76 | expect: 'aC81' 77 | }, 78 | { 79 | name: 'change secret and length to 100', 80 | setup: function () { 81 | $el.Len.val('100'); 82 | $el.Secret.val('ssshh'); 83 | }, 84 | expect: 'fd35Ng0Xwne2Pb8f3XFu8r8y' 85 | }, 86 | { 87 | name: 'change domain and subdomain removal', 88 | setup: function () { 89 | $el.Domain.val('https://login.example.com'); 90 | $el.RemoveSubdomains.prop('checked', false); 91 | }, 92 | expect: 'alrcP2cLv1lDddHXjExlS0H9' 93 | }, 94 | { 95 | name: 'check local storage', 96 | setup: function () { 97 | $el.SaveDefaults[0].dispatchEvent(clickEvent); 98 | }, 99 | expect: function (assert, done) { 100 | assert.ok(localStorage.getItem('Len') === '24', 'Password length value stored.'); 101 | assert.ok(localStorage.getItem('Salt') === 'ssshh', 'Secret password value stored.'); 102 | assert.ok(localStorage.getItem('Method') === 'sha512', 'Hash method setting stored.'); 103 | assert.ok(localStorage.getItem('DisableTLD') === 'true', 'Subdomain removal setting stored.'); 104 | done(); 105 | } 106 | } 107 | ]; 108 | 109 | suite.forEach(function (test) { 110 | QUnit.test(test.name, function (assert) { 111 | var done = assert.async(); 112 | test.setup(); 113 | 114 | sendClick(); 115 | 116 | if (typeof test.expect === 'function') { 117 | test.expect(assert, done); 118 | return; 119 | } 120 | 121 | setTimeout(function () { 122 | assert.equal($el.Output.text(), test.expect); 123 | done(); 124 | }, 100); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /src/mobile/lib/localstorage-polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LocalStorage polyfill 3 | * Based on https://gist.github.com/remy/350433 by Remy Sharp 4 | * Improved by Chris Zarate 5 | */ 6 | 7 | 8 | 'use strict'; 9 | 10 | /* Wrap storage API tests in try/catch to avoid browser errors. */ 11 | 12 | var LocalStorage, SessionStorage; 13 | 14 | try { 15 | LocalStorage = window.localStorage; 16 | LocalStorage.setItem('TEST', '1'); 17 | LocalStorage.removeItem('TEST'); 18 | } catch(e) { 19 | LocalStorage = false; 20 | } 21 | 22 | try { 23 | SessionStorage = window.sessionStorage; 24 | SessionStorage.setItem('TEST', '1'); 25 | SessionStorage.removeItem('TEST'); 26 | } catch(e) { 27 | SessionStorage = false; 28 | } 29 | 30 | /* Provide polyfill if native support is not available. */ 31 | 32 | if(!LocalStorage || !SessionStorage) { 33 | 34 | var Storage = function (type) { 35 | function createCookie(name, value, days) { 36 | var date, expires; 37 | 38 | if (days) { 39 | date = new Date(); 40 | date.setTime(date.getTime()+(days*24*60*60*1000)); 41 | expires = "; expires="+date.toGMTString(); 42 | } else { 43 | expires = ""; 44 | } 45 | document.cookie = name+"="+value+expires+"; path=/"; 46 | } 47 | 48 | function readCookie(name) { 49 | var nameEQ = name + "=", 50 | ca = document.cookie.split(';'), 51 | i, c; 52 | 53 | for (i=0; i < ca.length; i++) { 54 | c = ca[i]; 55 | while (c.charAt(0) === ' ') { 56 | c = c.substring(1,c.length); 57 | } 58 | 59 | if (c.indexOf(nameEQ) === 0) { 60 | return c.substring(nameEQ.length,c.length); 61 | } 62 | } 63 | return null; 64 | } 65 | 66 | function setData(data) { 67 | data = JSON.stringify(data); 68 | if (type === 'session') { 69 | window.name = data; 70 | } else { 71 | createCookie('localStorage', data, 365); 72 | } 73 | } 74 | 75 | function clearData() { 76 | if (type === 'session') { 77 | window.name = ''; 78 | } else { 79 | createCookie('localStorage', '', 365); 80 | } 81 | } 82 | 83 | function getData() { 84 | var data = type === 'session' ? window.name : readCookie('localStorage'); 85 | return data ? JSON.parse(data) : {}; 86 | } 87 | 88 | 89 | // initialise if there's already data 90 | var data = getData(); 91 | 92 | return { 93 | length: 0, 94 | clear: function () { 95 | data = {}; 96 | this.length = 0; 97 | clearData(); 98 | }, 99 | getItem: function (key) { 100 | return data[key] === undefined ? null : data[key]; 101 | }, 102 | key: function (i) { 103 | // not perfect, but works 104 | var ctr = 0; 105 | for (var k in data) { 106 | if (ctr === i) return k; 107 | else ctr++; 108 | } 109 | return null; 110 | }, 111 | removeItem: function (key) { 112 | delete data[key]; 113 | this.length--; 114 | setData(data); 115 | }, 116 | setItem: function (key, value) { 117 | data[key] = value+''; // forces the value to a string 118 | this.length++; 119 | setData(data); 120 | } 121 | }; 122 | }; 123 | 124 | /* 125 | First try to provide polyfill on the window object. But remember that 126 | the browser's security policy may be standing in the way even when 127 | native support is available. So fall back to an imposter object. 128 | */ 129 | 130 | try { 131 | if(!LocalStorage) { 132 | LocalStorage = window.localStorage = new Storage('local'); 133 | } 134 | } catch(e) { 135 | LocalStorage = new Storage('local'); 136 | } 137 | 138 | try { 139 | if(!SessionStorage) { 140 | SessionStorage = window.sessionStorage = new Storage('session'); 141 | } 142 | } catch(e) { 143 | SessionStorage = new Storage('session'); 144 | } 145 | 146 | } 147 | 148 | /* Exports */ 149 | 150 | module.exports = { 151 | local: LocalStorage, 152 | session: SessionStorage 153 | }; 154 | -------------------------------------------------------------------------------- /src/mobile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | SGP 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 |

41 | SGP 42 | 43 | 44 |

45 | 46 | 47 | 48 |
Updated bookmarklet: SGP
49 | 50 |
51 | 52 |
53 | 54 | 55 | 56 |
57 | 58 |
59 | 60 | 61 |
62 | 63 |
64 | 65 | 66 | 67 | 68 |
69 | 70 |
71 | 72 | 73 |
74 | 75 |
76 | 77 | 78 |
79 | 80 | 81 |
82 |
83 | 84 |
85 | 86 |
Generate
87 | 88 |
89 |
90 |
**************
91 |
92 |
93 | 94 |
95 | 96 |
97 | 98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | SuperGenPass: A Free Bookmarklet Password Generator 12 | 13 | 14 | 15 | 16 | 17 |
18 |

Key

19 |
20 | 21 |
22 | 23 |

SuperGenPass is a different kind of password solution. Instead of storing your passwords on your hard disk or online—where they are vulnerable to theft and data loss—SuperGenPass uses a hash algorithm to transform a master password into unique, complex passwords for the Web sites you visit.

24 | 25 |

SuperGenPass is a bookmarklet and runs right in your Web browser. It never stores or transmits your passwords, so it’s ideal for use on multiple and public computers. It’s also completely free and open-sourced on GitHub. 26 | 27 |

28 | 29 |
30 |

SGP

31 |

Drag this to your bookmarks toolbar. (Or right-click and add to bookmarks.)

32 |
33 |
34 |

Mobile

35 |

Use on public computers and mobile devices. (Add it to your home screen!)

36 |
37 | 38 |
39 | 40 |
41 | 42 |

Should I use SuperGenPass?

43 |

Maybe! Do you like bookmarklets? Do you like not ever knowing what your passwords are? (That’s a good thing!) Do you like the idea of using a slightly quirky password solution? You do?

44 | 45 |

I have a question!

46 |

Please take a look at the FAQ on the SGP wiki. If you still have questions, please open an issue on GitHub.

47 | 48 |

Looking for GenPass?

49 | 50 |
51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/intern/functional/mobile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*global define*/ 4 | 5 | define([ 6 | 'intern!object', 7 | 'intern/chai!assert', 8 | 'require' 9 | ], function (registerSuite, assert, require) { 10 | 11 | var url = '../../../mobile/index.html'; 12 | 13 | registerSuite({ 14 | 15 | name: 'SuperGenPass mobile version', 16 | 17 | 'load the mobile version': function () { 18 | return this.remote 19 | .get(require.toUrl(url)) 20 | .findById('Generate').isDisplayed() 21 | .then(function (isDisplayed) { 22 | assert.ok(isDisplayed, 'Mobile version should display a generate button.'); 23 | }); 24 | }, 25 | 26 | 'click advanced options': function () { 27 | return this.remote 28 | .findById('Options').click().end() 29 | .findById('Secret').isDisplayed() 30 | .then(function (isDisplayed) { 31 | assert.ok(isDisplayed, 'Advanced options should be visible.'); 32 | }); 33 | }, 34 | 35 | 'enter master password': function () { 36 | var input = 'test'; 37 | return this.remote 38 | .findById('Passwd').clearValue().click().pressKeys(input) 39 | .getProperty('value') 40 | .then(function (value) { 41 | assert.ok(value === input, 'User should be able to type a master password.'); 42 | }); 43 | }, 44 | 45 | 'enter secret password': function () { 46 | var input = 'secret'; 47 | return this.remote 48 | .findById('Secret').clearValue().click().pressKeys(input) 49 | .getProperty('value') 50 | .then(function (value) { 51 | assert.ok(value === input, 'User should be able to type a secret password.'); 52 | }); 53 | }, 54 | 55 | 'enter domain': function () { 56 | var input = 'login.example.com'; 57 | return this.remote 58 | .findById('Domain').clearValue().click().pressKeys(input) 59 | .getProperty('value') 60 | .then(function (value) { 61 | assert.ok(value === input, 'User should be able to type a domain.'); 62 | }); 63 | }, 64 | 65 | 'enter length': function () { 66 | var input = '12'; 67 | return this.remote 68 | .findById('Len').clearValue().click().pressKeys(input) 69 | .getProperty('value') 70 | .then(function (value) { 71 | assert.ok(value === input, 'User should be able to type a length.'); 72 | }); 73 | }, 74 | 75 | 'set the hash method': function () { 76 | return this.remote 77 | .findByCssSelector('label[for="MethodSHA512"]').click().end() 78 | .findById('MethodSHA512').getProperty('checked') 79 | .then(function (isChecked) { 80 | assert.ok(isChecked, 'User should be able to set the hash method.'); 81 | }); 82 | }, 83 | 84 | 'submit the form': function () { 85 | return this.remote 86 | .findById('Generate').click().end() 87 | .findById('MaskText').setFindTimeout(100).getVisibleText() 88 | .then(function (value) { 89 | assert.ok(value === '**************', 'User should be able to generate a password.'); 90 | }); 91 | }, 92 | 93 | 'show the password': function () { 94 | return this.remote 95 | .findById('MaskText').click().end() 96 | .findById('Output').getVisibleText() 97 | .then(function (value) { 98 | assert.ok(value === 'cLZ1vg6U98L3', 'User should be able to retrieve the generated password.'); 99 | }); 100 | }, 101 | 102 | 'remove the secret password': function () { 103 | return this.remote 104 | .findById('Secret').clearValue().end() 105 | .findById('Generate').isDisplayed() 106 | .then(function (isDisplayed) { 107 | assert.ok(isDisplayed, 'Changing a value should reset the form.'); 108 | }); 109 | }, 110 | 111 | 'resubmit the form': function () { 112 | return this.remote 113 | .findById('Generate').click().end() 114 | .findById('MaskText').setFindTimeout(100).click().end() 115 | .findById('Output').getVisibleText() 116 | .then(function (value) { 117 | assert.ok(value === 'sJfoZg3nU8y3', 'Removing the secret password should change the generated password.'); 118 | }); 119 | }, 120 | 121 | 'change the hash method': function () { 122 | return this.remote 123 | .findByCssSelector('label[for="MethodMD5"]').click().end() 124 | .findById('Generate').click().end() 125 | .findById('MaskText').setFindTimeout(100).click().end() 126 | .findById('Output').getVisibleText() 127 | .then(function (value) { 128 | assert.ok(value === 'vBKDNdjhhL6d', 'Changing the hash method should change the generated password.'); 129 | }); 130 | }, 131 | 132 | 'change the length': function () { 133 | return this.remote 134 | .findById('Down').click().click().end() 135 | .findById('Generate').click().end() 136 | .findById('MaskText').setFindTimeout(100).click().end() 137 | .findById('Output').getVisibleText() 138 | .then(function (value) { 139 | assert.ok(value === 'w9UbG0NEk7', 'Changing the password length with the mouse should change the generated password.'); 140 | }); 141 | }, 142 | 143 | 'change the length again': function () { 144 | return this.remote 145 | .findById('Len').clearValue().click().pressKeys('24').end() 146 | .findById('Generate').click().end() 147 | .findById('MaskText').setFindTimeout(100).click().end() 148 | .findById('Output').getVisibleText() 149 | .then(function (value) { 150 | assert.ok(value === 'vBKDNdjhhL6dBfgDSRxZxAAA', 'Changing the password length again should change the generated password.'); 151 | }); 152 | }, 153 | 154 | 'change the subdomain option': function () { 155 | return this.remote 156 | .findByCssSelector('label[for="RemoveSubdomains"]').click().end() 157 | .findById('Domain').clearValue().click().pressKeys('login.example.com').end() 158 | .findById('Generate').click().end() 159 | .findById('MaskText').setFindTimeout(100).click().end() 160 | .findById('Output').getVisibleText() 161 | .then(function (value) { 162 | assert.ok(value === 'iNerPM9Zu79a8gUIcLzC1QAA', 'Changing the subdomain option should change the generated password.'); 163 | }); 164 | }, 165 | 166 | 'click advanced options again': function () { 167 | return this.remote 168 | .findById('Options').click().end() 169 | .findById('Secret').isDisplayed() 170 | .then(function (isDisplayed) { 171 | assert.ok(!isDisplayed, 'Advanced options should be hidden.'); 172 | }); 173 | } 174 | 175 | }); 176 | 177 | }); 178 | -------------------------------------------------------------------------------- /src/mobile/lib/shortcut.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http://www.openjs.com/scripts/events/keyboard_shortcuts/ 3 | * Version : 2.01.B 4 | * By Binny V A 5 | * License : BSD 6 | */ 7 | 8 | 9 | 'use strict'; 10 | 11 | var shortcut = { 12 | 'all_shortcuts':{},//All the shortcuts are stored in this array 13 | 'add': function(shortcut_combination,callback,opt) { 14 | //Provide a set of default options 15 | var default_options = { 16 | 'type':'keydown', 17 | 'propagate':false, 18 | 'disable_in_input':false, 19 | 'target':document, 20 | 'keycode':false 21 | }; 22 | if(!opt) opt = default_options; 23 | else { 24 | for(var dfo in default_options) { 25 | if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo]; 26 | } 27 | } 28 | 29 | var ele = opt.target; 30 | if(typeof opt.target == 'string') ele = document.getElementById(opt.target); 31 | shortcut_combination = shortcut_combination.toLowerCase(); 32 | 33 | //The function to be called at keypress 34 | var func = function(e) { 35 | e = e || window.event; 36 | 37 | if(opt.disable_in_input) { //Don't enable shortcut keys in Input, Textarea fields 38 | var element; 39 | if(e.target) element=e.target; 40 | else if(e.srcElement) element=e.srcElement; 41 | if(element.nodeType==3) element=element.parentNode; 42 | 43 | if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return; 44 | } 45 | 46 | //Find Which key is pressed 47 | var code; 48 | if (e.keyCode) code = e.keyCode; 49 | else if (e.which) code = e.which; 50 | var character = String.fromCharCode(code).toLowerCase(); 51 | 52 | if(code == 188) character=","; //If the user presses , when the type is onkeydown 53 | if(code == 190) character="."; //If the user presses , when the type is onkeydown 54 | 55 | var keys = shortcut_combination.split("+"); 56 | //Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked 57 | var kp = 0; 58 | 59 | //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken 60 | var shift_nums = { 61 | "`":"~", 62 | "1":"!", 63 | "2":"@", 64 | "3":"#", 65 | "4":"$", 66 | "5":"%", 67 | "6":"^", 68 | "7":"&", 69 | "8":"*", 70 | "9":"(", 71 | "0":")", 72 | "-":"_", 73 | "=":"+", 74 | ";":":", 75 | "'":"\"", 76 | ",":"<", 77 | ".":">", 78 | "/":"?", 79 | "\\":"|" 80 | }; 81 | //Special Keys - and their codes 82 | var special_keys = { 83 | 'esc':27, 84 | 'escape':27, 85 | 'tab':9, 86 | 'space':32, 87 | 'return':13, 88 | 'enter':13, 89 | 'backspace':8, 90 | 91 | 'scrolllock':145, 92 | 'scroll_lock':145, 93 | 'scroll':145, 94 | 'capslock':20, 95 | 'caps_lock':20, 96 | 'caps':20, 97 | 'numlock':144, 98 | 'num_lock':144, 99 | 'num':144, 100 | 101 | 'pause':19, 102 | 'break':19, 103 | 104 | 'insert':45, 105 | 'home':36, 106 | 'delete':46, 107 | 'end':35, 108 | 109 | 'pageup':33, 110 | 'page_up':33, 111 | 'pu':33, 112 | 113 | 'pagedown':34, 114 | 'page_down':34, 115 | 'pd':34, 116 | 117 | 'left':37, 118 | 'up':38, 119 | 'right':39, 120 | 'down':40, 121 | 122 | 'f1':112, 123 | 'f2':113, 124 | 'f3':114, 125 | 'f4':115, 126 | 'f5':116, 127 | 'f6':117, 128 | 'f7':118, 129 | 'f8':119, 130 | 'f9':120, 131 | 'f10':121, 132 | 'f11':122, 133 | 'f12':123 134 | }; 135 | 136 | var modifiers = { 137 | shift: { wanted:false, pressed:false}, 138 | ctrl : { wanted:false, pressed:false}, 139 | alt : { wanted:false, pressed:false}, 140 | meta : { wanted:false, pressed:false} //Meta is Mac specific 141 | }; 142 | 143 | if(e.ctrlKey) modifiers.ctrl.pressed = true; 144 | if(e.shiftKey) modifiers.shift.pressed = true; 145 | if(e.altKey) modifiers.alt.pressed = true; 146 | if(e.metaKey) modifiers.meta.pressed = true; 147 | 148 | for(var i=0; i 1) { //If it is a special key 166 | if(special_keys[k] == code) kp++; 167 | 168 | } else if(opt.keycode) { 169 | if(opt.keycode == code) kp++; 170 | 171 | } else { //The special keys did not match 172 | if(character == k) kp++; 173 | else { 174 | if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase 175 | character = shift_nums[character]; 176 | if(character == k) kp++; 177 | } 178 | } 179 | } 180 | } 181 | 182 | if(kp == keys.length && 183 | modifiers.ctrl.pressed == modifiers.ctrl.wanted && 184 | modifiers.shift.pressed == modifiers.shift.wanted && 185 | modifiers.alt.pressed == modifiers.alt.wanted && 186 | modifiers.meta.pressed == modifiers.meta.wanted) { 187 | callback(e); 188 | 189 | if(!opt.propagate) { //Stop the event 190 | //e.cancelBubble is supported by IE - this will kill the bubbling process. 191 | e.cancelBubble = true; 192 | e.returnValue = false; 193 | 194 | //e.stopPropagation works in Firefox. 195 | if (e.stopPropagation) { 196 | e.stopPropagation(); 197 | e.preventDefault(); 198 | } 199 | return false; 200 | } 201 | } 202 | }; 203 | this.all_shortcuts[shortcut_combination] = { 204 | 'callback':func, 205 | 'target':ele, 206 | 'event': opt.type 207 | }; 208 | //Attach the function with the event 209 | if(ele.addEventListener) ele.addEventListener(opt.type, func, false); 210 | else if(ele.attachEvent) ele.attachEvent('on'+opt.type, func); 211 | else ele['on'+opt.type] = func; 212 | }, 213 | 214 | //Remove the shortcut - just specify the shortcut and I will remove the binding 215 | 'remove':function(shortcut_combination) { 216 | shortcut_combination = shortcut_combination.toLowerCase(); 217 | var binding = this.all_shortcuts[shortcut_combination]; 218 | delete(this.all_shortcuts[shortcut_combination]); 219 | if(!binding) return; 220 | var type = binding.event; 221 | var ele = binding.target; 222 | var callback = binding.callback; 223 | 224 | if(ele.detachEvent) ele.detachEvent('on'+type, callback); 225 | else if(ele.removeEventListener) ele.removeEventListener(type, callback, false); 226 | else ele['on'+type] = false; 227 | } 228 | }; 229 | 230 | // Require shim 231 | module.exports = shortcut; 232 | -------------------------------------------------------------------------------- /src/bookmarklet/sgp.bookmarklet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*jshint browser: true, devel: true, jquery: true*/ 4 | 5 | void (function ($) { 6 | 7 | // Configuration 8 | var version = 20150216; 9 | var domain = 'https://chriszarate.github.io'; 10 | var mobile = 'https://chriszarate.github.io/supergenpass/mobile/'; 11 | var minFrameArea = 100000; 12 | var loadTimeout = 2; // seconds 13 | 14 | // Messages 15 | var securityMessage = 'SGP may be blocked on this site by a security setting.'; 16 | var offerQuestion = 'Would you like to open the mobile version?'; 17 | var offerLink = 'You may wish to open the mobile version.'; 18 | 19 | var offerMobileVersion = function () { 20 | var redirect = confirm(securityMessage + ' ' + offerQuestion); 21 | if (redirect) { 22 | window.open(mobile); 23 | } 24 | }; 25 | 26 | // Main 27 | var loadSGP = function ($) { 28 | 29 | // Defaults 30 | var $target = $(document); 31 | var $localFrames = $target; 32 | var dragging = false; 33 | var maxArea = 0; 34 | 35 | // Functions 36 | 37 | /* 38 | Determine if frame is local (not cross-origin). 39 | Adapted from answer by Esailija: 40 | http://stackoverflow.com/questions/11872917/ 41 | */ 42 | 43 | var isLocalFrame = function () { 44 | // Expects frame element as context. 45 | // Try/catch helps avoid XSS freakouts. 46 | try { 47 | var key = '_' + new Date().getTime(); 48 | var win = this.contentWindow; 49 | win[key] = key; 50 | if (win[key] === key) { 51 | $localFrames.add(win.document); 52 | return true; 53 | } 54 | } 55 | catch (e) { 56 | return false; 57 | } 58 | }; 59 | 60 | var findBiggestFrame = function () { 61 | // Expects frame element as context. 62 | // Try/catch helps avoid XSS freakouts. 63 | try { 64 | var area = $(this).height() * $(this).width(); 65 | if (area > maxArea && area > minFrameArea) { 66 | $target = $(this.contentWindow.document); 67 | maxArea = area; 68 | } 69 | } 70 | catch (e) {} 71 | }; 72 | 73 | var removeLoadingIndicator = function () { 74 | $loadingIndicator.remove(); 75 | }; 76 | 77 | var linkMobileVersion = function () { 78 | $loadingIndicator.html(securityMessage + ' ' + offerLink); 79 | }; 80 | 81 | var postMessage = function () { 82 | // Send current bookmarklet version to SGP generator. (Also communicates 83 | // current URL and opens channel for response.) 84 | try { 85 | this.contentWindow.postMessage('{"version":' + version + '}', domain); 86 | } catch (e) { 87 | linkMobileVersion(); 88 | } 89 | }; 90 | 91 | var receiveMessage = function (e) { 92 | var post = e.originalEvent; 93 | if (post.origin === domain && typeof post.data !== 'undefined') { 94 | removeLoadingIndicator(); 95 | clearTimeout(loadTimeoutID); 96 | processMessage(JSON.parse(post.data)); 97 | } 98 | }; 99 | 100 | var processMessage = function (data) { 101 | $.each(data, function (key, value) { 102 | switch (key) { 103 | case 'result': 104 | populatePassword(value); 105 | break; 106 | case 'height': 107 | changeFrameHeight(Math.max(parseInt(value, 10), 167) + 2); 108 | break; 109 | } 110 | }); 111 | }; 112 | 113 | var populatePassword = function (password) { 114 | // Populate generated password into password fields. 115 | $('input:password:visible', $localFrames) 116 | .css('background', '#9f9') 117 | .val(password) 118 | .trigger('change click') 119 | .on('input', resetPasswordField) 120 | .focus(); 121 | }; 122 | 123 | var changeFrameHeight = function (height) { 124 | // Change iframe height to match SGP generator document height. 125 | $frame.css('height', height); 126 | }; 127 | 128 | var resetPasswordField = function () { 129 | $(this).css('background', '#fff'); 130 | }; 131 | 132 | var closeWindow = function () { 133 | $box.remove(); 134 | }; 135 | 136 | // Define CSS properties. 137 | var fontStyle = 'font-family:sans-serif;font-size:18px;line-height:20px;'; 138 | var boxStyle = 'z-index:99999;position:absolute;top:0;right:5px;width:258px;margin:0;padding:0;box-sizing:content-box;' + fontStyle; 139 | var titleBarStyle = 'overflow:hidden;width:258px;height:20px;margin:0;padding:0;text-align:right;background-color:#356;cursor:move;box-sizing:content-box;' + fontStyle; 140 | var closeLinkStyle = 'padding:0 5px;color:#fff;cursor:pointer;' + fontStyle; 141 | var loadingIndicatorStyle = 'position:absolute;width:258px;height:190px;padding:15px;color:#333;background-color:#fff;font-family:monospace;font-size:15px;text-align:center;'; 142 | var frameStyle = 'position:static;width:258px;height:190px;border:none;overflow:hidden;pointer-events:auto;'; 143 | 144 | // Create SGP elements. 145 | var $box = $('
', {style: boxStyle}); 146 | var $titleBar = $('
', {style: titleBarStyle}); 147 | var $closeLink = $('', {style: closeLinkStyle}).append('×'); 148 | var $loadingIndicator = $('
', {style: loadingIndicatorStyle}).append('Loading SGP ...'); 149 | var $frame = $('