├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .prettierrc ├── DEV_ONLY ├── App.js ├── index.js └── polyfill.js ├── LICENSE ├── README.md ├── package.json ├── rollup.config.js ├── src ├── constants.js ├── index.js └── utils.js ├── test-node.js ├── test ├── constants.js ├── helpers │ └── setup-browser-env.js ├── index.js └── utils.js ├── webpack ├── webpack.config.dev.js ├── webpack.config.js └── webpack.config.minified.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "plugins": [ 5 | [ 6 | "transform-runtime", 7 | { 8 | "helpers": false, 9 | "polyfill": false, 10 | "regenerator": true 11 | } 12 | ] 13 | ], 14 | "presets": [ 15 | [ 16 | "env", 17 | { 18 | "loose": true, 19 | "modules": false 20 | } 21 | ], 22 | "stage-2" 23 | ] 24 | }, 25 | "production": { 26 | "presets": [ 27 | [ 28 | "env", 29 | { 30 | "loose": true, 31 | "modules": false 32 | } 33 | ], 34 | "stage-2" 35 | ] 36 | }, 37 | "test": { 38 | "plugins": [ 39 | [ 40 | "transform-runtime", 41 | { 42 | "helpers": false, 43 | "polyfill": false, 44 | "regenerator": true 45 | } 46 | ] 47 | ], 48 | "presets": [ 49 | [ 50 | "env", 51 | { 52 | "loose": true 53 | } 54 | ], 55 | "stage-2" 56 | ] 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["rapid7/browser", "rapid7/strict"], 3 | "globals": { 4 | "__dirname": true, 5 | "global": true, 6 | "module": true, 7 | "process": true, 8 | "require": true 9 | }, 10 | "parser": "babel-eslint", 11 | "rules": {} 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .nyc_output 3 | coverage 4 | dist 5 | node_modules 6 | index.js 7 | index.js.map 8 | node.js 9 | node.js.map 10 | *.log 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .eslintrc 3 | .git 4 | .gitignore 5 | .idea 6 | .npmignore 7 | .nyc_output 8 | coverage 9 | DEV_ONLY 10 | node_modules 11 | src 12 | test 13 | test-node.js 14 | rollup.config.js 15 | webpack 16 | *.log 17 | yarn.lock -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": [".babelrc", ".eslintrc"], 5 | "options": { 6 | "parser": "json" 7 | } 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /DEV_ONLY/App.js: -------------------------------------------------------------------------------- 1 | // polyfills 2 | import './polyfill'; 3 | 4 | // external dependencies 5 | import {deepEqual} from 'fast-equals'; 6 | 7 | // src 8 | import * as src from '../src'; 9 | // import * as src from '../index'; 10 | 11 | const KEY = 'SOME_STATIC_KEY'; // static string 12 | // const KEY = { 13 | // some: 'object', 14 | // to: 'stringify', 15 | // }; // static object 16 | // const KEY = Date.now(); // dynamic value 17 | 18 | const map = new Map(); 19 | 20 | map.set({foo: 'bar'}, 'baz'); 21 | 22 | const VALUES = { 23 | array: ['foo', 123, {bar: 'baz'}], 24 | arrayBuffer: new ArrayBuffer(36), 25 | long: `Lorem ipsum dolor amet viral lomo celiac kale chips gentrify dreamcatcher shoreditch austin offal before they sold out keffiyeh tbh put a bird on it. Synth franzen echo park truffaut 90's sriracha offal yr adaptogen banjo venmo hella. Everyday carry sustainable bicycle rights, kickstarter craft beer fingerstache pitchfork twee shabby chic salvia offal ramps quinoa. Narwhal kickstarter flannel vice lo-fi, live-edge kitsch raclette schlitz portland hot chicken kombucha yuccie YOLO. Irony put a bird on it actually cloud bread cardigan tbh, freegan poke unicorn adaptogen man braid cold-pressed shabby chic ethical. Aesthetic single-origin coffee messenger bag tousled craft beer palo santo food truck listicle. 26 | 27 | Squid try-hard umami adaptogen flexitarian. Edison bulb whatever copper mug cloud bread synth schlitz raclette distillery la croix pop-up lumbersexual mlkshk gastropub pickled kogi. Gluten-free DIY try-hard beard, put a bird on it food truck microdosing photo booth poke keytar tattooed authentic. Biodiesel fixie wolf leggings synth, meggings vaporware. Cardigan gochujang schlitz umami normcore tote bag ethical ugh gentrify chartreuse locavore mlkshk literally. Pug cred lo-fi authentic, put a bird on it street art etsy direct trade chicharrones actually. Before they sold out crucifix tote bag chia. 28 | 29 | Mumblecore iceland glossier viral meggings, farm-to-tatextble enamel pin. Seitan scenester pickled master cleanse heirloom, taiyaki food truck PBR&B narwhal cliche tilde jianbing vexillologist art party. Next level forage four dollar toast seitan pabst marfa chillwave fashion axe banh mi jianbing. Chia vaporware narwhal pickled echo park blue bottle scenester lyft small batch ethical street art locavore. Hexagon franzen food truck cred put a bird on it kale chips schlitz gochujang viral four dollar toast before they sold out polaroid taxidermy chia. Disrupt lumbersexual letterpress celiac, glossier subway tile listicle kogi echo park sriracha direct trade. Raclette 8-bit kombucha, +1 hashtag microdosing messenger bag tumblr kickstarter palo santo disrupt. 30 | 31 | Cloud bread locavore austin offal. Leggings shoreditch bespoke yr lomo williamsburg. Godard chambray direct trade cloud bread. Deep v flexitarian kickstarter pug raw denim chambray twee prism williamsburg glossier forage. 32 | 33 | Kale chips truffaut roof party schlitz, ramps YOLO meditation tumeric iPhone ethical. Church-key cred forage crucifix, pug hexagon selfies paleo celiac organic put a bird on it ethical woke gluten-free. Cardigan bespoke art party, leggings iceland chillwave truffaut forage swag PBR&B chia green juice fixie gastropub sartorial. Disrupt seitan chillwave pour-over, actually fanny pack XOXO listicle before they sold out wolf brunch pickled tote bag. Irony flannel trust fund fam, artisan neutra jianbing chicharrones cliche pabst williamsburg portland.`, 34 | map, 35 | nil: null, 36 | number: 12345, 37 | object: {some: 'data'}, 38 | short: 'my special text + foo', 39 | typedArray: new Uint8Array(12), 40 | }; 41 | 42 | const parse = (string) => 43 | JSON.parse( 44 | string, 45 | (key, value) => 46 | Array.isArray(value) && value.every((item) => Array.isArray(item) && item.length === 2) ? new Map(value) : value 47 | ); 48 | 49 | const succeed = async (value, key = KEY) => { 50 | const encrypted = await src.encrypt(value, key); 51 | const decrypted = await src.decrypt(encrypted, key, {parse}); 52 | 53 | return { 54 | decrypted, 55 | encrypted, 56 | isEqual: deepEqual(value, decrypted), 57 | original: value, 58 | }; 59 | }; 60 | 61 | const runSucceed = () => 62 | new Promise((resolve) => { 63 | Object.keys(VALUES).forEach(async (key, index) => { 64 | const result = await succeed(VALUES[key]); 65 | 66 | console.log('[succeed encrypt/decrypt]', key, ':', result); 67 | 68 | if (index === Object.keys(VALUES).length - 1) { 69 | resolve(); 70 | } 71 | }); 72 | }); 73 | 74 | const fail = async (value) => { 75 | try { 76 | const encrypted = await src.encrypt(value, KEY); 77 | const decrypted = await src.decrypt(encrypted, 'nope', {parse}); 78 | 79 | return { 80 | decrypted, 81 | encrypted, 82 | isEqual: deepEqual(value, decrypted), 83 | original: value, 84 | }; 85 | } catch (error) { 86 | console.error(error); 87 | } 88 | }; 89 | 90 | const runFail = () => 91 | new Promise((resolve) => { 92 | Object.keys(VALUES).forEach(async (key, index) => { 93 | await fail(VALUES[key]); 94 | 95 | if (index === Object.keys(VALUES).length - 1) { 96 | resolve(); 97 | } 98 | }); 99 | }); 100 | 101 | const runGenerateSecret = () => 102 | new Promise((resolve) => { 103 | Object.keys(VALUES).forEach(async (key, index) => { 104 | const encryptionKey = await src.generateSecret(); 105 | const result = await succeed(VALUES[key], encryptionKey); 106 | 107 | console.log('[generated secret]', key, ':', result); 108 | 109 | if (index === Object.keys(VALUES).length - 1) { 110 | resolve(); 111 | } 112 | }); 113 | }); 114 | 115 | const runHash = () => 116 | new Promise((resolve) => { 117 | Object.keys(VALUES).forEach(async (key, index) => { 118 | const result = await src.hash(VALUES[key]); 119 | 120 | console.log('[hash]', key, ':', result); 121 | 122 | if (index === Object.keys(VALUES).length - 1) { 123 | resolve(); 124 | } 125 | }); 126 | }); 127 | 128 | const run = async () => { 129 | console.group('succeed encrypt / decrypt'); 130 | await runSucceed(); 131 | console.groupEnd('succeed encrypt / decrypt'); 132 | 133 | console.group('fail encrypt / decrypt'); 134 | await runFail(); 135 | console.groupEnd('fail encrypt / decrypt'); 136 | 137 | console.group('generate secret'); 138 | await runGenerateSecret(); 139 | console.groupEnd('generate secret'); 140 | 141 | console.group('hash'); 142 | await runHash(); 143 | console.groupEnd('hash'); 144 | 145 | console.group('errors'); 146 | 147 | try { 148 | const result = await src.encrypt(''); 149 | 150 | console.log(result); 151 | } catch (error) { 152 | console.error(error); 153 | } 154 | 155 | try { 156 | const result = await src.encrypt('', 'foo', 'bar'); 157 | 158 | console.log(result); 159 | } catch (error) { 160 | console.error(error); 161 | } 162 | 163 | try { 164 | const result = await src.decrypt(''); 165 | 166 | console.log(result); 167 | } catch (error) { 168 | console.error(error); 169 | } 170 | 171 | try { 172 | const result = await src.decrypt('', 'foo', 'bar'); 173 | 174 | console.log(result); 175 | } catch (error) { 176 | console.error(error); 177 | } 178 | 179 | try { 180 | const result = await src.hash('foo', 'bar'); 181 | 182 | console.log(result); 183 | } catch (error) { 184 | console.error(error); 185 | } 186 | 187 | console.groupEnd('errors'); 188 | }; 189 | 190 | run(); 191 | -------------------------------------------------------------------------------- /DEV_ONLY/index.js: -------------------------------------------------------------------------------- 1 | import './App'; 2 | 3 | document.body.style.backgroundColor = '#1d1d1d'; 4 | document.body.style.color = '#d5d5d5'; 5 | document.body.style.margin = 0; 6 | document.body.style.padding = 0; 7 | 8 | const div = document.createElement('div'); 9 | 10 | div.textContent = 'Check the console for details.'; 11 | 12 | document.body.appendChild(div); 13 | -------------------------------------------------------------------------------- /DEV_ONLY/polyfill.js: -------------------------------------------------------------------------------- 1 | import Bluebird from 'bluebird'; 2 | import encoding from 'text-encoding'; 3 | 4 | if (!window.Promise) { 5 | window.Promise = Bluebird; 6 | } 7 | 8 | if (!window.TextDecoder) { 9 | window.TextDecoder = encoding.TextDecoder; 10 | } 11 | 12 | if (!window.TextEncoder) { 13 | window.TextEncoder = encoding.TextEncoder; 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tony Quetano 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # krip 2 | 3 | Dead simple encryption in both browser and node, using WebCrypto under the hood 4 | 5 | ## Table of contents 6 | 7 | - [Usage](#usage) 8 | - [API](#api) 9 | - [encrypt](#encrypt) 10 | - [decrypt](#decrypt) 11 | - [generateSecret](#generatesecret) 12 | - [hash](#hash) 13 | - [Support](#support) 14 | - [Browser](#browser) 15 | - [NodeJS](#nodejs) 16 | - [Development](#development) 17 | 18 | ## Usage 19 | 20 | ```javascript 21 | import { decrypt, encrypt } from "krip"; 22 | 23 | const SECRET = "MY_SPECIAL_KEY"; 24 | 25 | const test = async value => { 26 | const encrypted = await encrypt(value, SECRET); 27 | 28 | console.log("encrypted: ", encrypted); 29 | 30 | const decrypted = await decrypt(encrypted, SECRET); 31 | 32 | console.log("decrypted: ", decrypted); 33 | }; 34 | 35 | test("my secret value"); 36 | // encrypted: 4439AA90B8374AA440BE3F816ADF0EA3501B61DE699264D5CAD89C381E595DD2337DCD00E7AE33F37A87C38CED 37 | // decrypted: my secret value 38 | ``` 39 | 40 | ## API 41 | 42 | #### encrypt 43 | 44 | _encrypt(value: any, secret: any[, options: Object]): string_ 45 | 46 | Encrypt a `value` based on the passed `secret` and `options`. The `value` can be any serializable value, as can the `secret` (if not a string, under the hood they will be stringified). 47 | 48 | ```javascript 49 | const SECRET = {some: 'special key'}; 50 | 51 | const result = await encrypt({some: 'data'}, SECRET); 52 | 53 | console.log(result); 54 | // FD3740F30AEF1B25588C7227+96800272F06DCD73749802EB6FF7052614C876D30ED7398B4579295CE90FC8 55 | ``` 56 | 57 | `options` is an object of the following possible values: 58 | 59 | ```javascript 60 | { 61 | // charset to use when encoding / decoding text 62 | charset: string = 'utf-8', 63 | // size of the TypedArray used for the crypto iv 64 | ivSize: number = 12, 65 | // the parser used to deserialize the encrypted value 66 | parse: function = JSON.parse, 67 | // the stringifier used to serialize the secret / value to encrypt 68 | stringify: function = JSON.stringify, 69 | } 70 | ``` 71 | 72 | #### decrypt 73 | 74 | _decrypt(encryptedValue: string, secret: any[, options: Object]): string_ 75 | 76 | Decrypt the `encryptedValue` based on the passed `secret` and `options`. The `secret` must be equal in value as the one used in the `encrypt` method. 77 | 78 | ```javascript 79 | const SECRET = {some: 'special key'}; 80 | 81 | const encrypted = await encrypt({some: 'data'}, SECRET); 82 | 83 | console.log(encrypted); 84 | // FD3740F30AEF1B25588C722796800272F06DCD73749802EB6FF7052614C876D30ED7398B4579295CE90FC8 85 | 86 | const result = await decrypt(encrypted, SECRET); 87 | 88 | console.log(result); 89 | // {some: 'data'} 90 | ``` 91 | 92 | `options` is an object of the following possible values: 93 | 94 | ```javascript 95 | { 96 | // charset to use when encoding / decoding text 97 | charset: string = 'utf-8', 98 | // size of the TypedArray used for the crypto iv 99 | ivSize: number = 12, 100 | // the parser used to deserialize the encrypted value 101 | parse: function = JSON.parse, 102 | // the stringifier used to serialize the secret 103 | stringify: function = JSON.stringify, 104 | } 105 | ``` 106 | 107 | #### generateSecret 108 | 109 | _generateSecret([options: Object]): CryptoKey_ 110 | 111 | Generate a dynamic secret to use in `encrypt` or `decrypt`. 112 | 113 | ```javascript 114 | const SECRET = await generateSecret(); 115 | 116 | console.log(SECRET); 117 | // CryptoKey {...} 118 | ``` 119 | 120 | `options` is an object of the following possible values: 121 | 122 | ```javascript 123 | { 124 | // the length of the key used when generating a key 125 | keyLength: number = 256, 126 | } 127 | ``` 128 | 129 | #### hash 130 | 131 | _hash(value: any[, algorithm: string[, options: Object]]): string_ 132 | 133 | Generate a unique SHA-based one-way hash. 134 | 135 | ```javascript 136 | const hashed = await hash('foo', 'SHA-1'); 137 | 138 | console.log(hashed); 139 | // 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 140 | ``` 141 | 142 | `algorithm` is a string of the following possible values: 143 | 144 | - `SHA-1` 145 | - `SHA-256` (default) 146 | - `SHA-384` 147 | - `SHA-512` 148 | 149 | `options` is an object of the following possible values: 150 | 151 | ```javascript 152 | { 153 | // charset to use when encoding / decoding text 154 | charset: string = 'utf-8', 155 | // the stringifier used to serialize the value to hash 156 | stringify: function = JSON.stringify, 157 | } 158 | ``` 159 | 160 | ## Support 161 | 162 | #### Browser 163 | 164 | - Chrome 37+ 165 | - Firefox 34+ 166 | - Edge (all versions) 167 | - Opera 24+ 168 | - IE 11+ 169 | - Requires polyfills for `Promise`, `TextDecoder`, and `TextEncoder` 170 | - Safari 7.1+ 171 | - iOS 8+ 172 | - Android 5+ 173 | 174 | #### NodeJS 175 | 176 | - 4+ 177 | 178 | ## Development 179 | 180 | Standard stuff, clone the repo and `npm install` dependencies. The npm scripts available: 181 | 182 | - `build` => run `rollup` to build browser and node versions 183 | - standard versions to top-level directory, minified versions to `dist` folder 184 | - `clean:dist` => run `rimraf` on `dist` folder 185 | - `dev` => run `webpack` dev server to run example app / playground 186 | - `dist` => run `clean:dist`, `build` 187 | - `lint` => run `eslint` against all files in the `src` folder 188 | - `lint:fix` => run `lint`, fixing issues when possible 189 | - `prepublish` => runs `prepublish:compile` when publishing 190 | - `prepublish:compile` => run `lint`, `test:coverage`, `build` 191 | - `start` => run `dev` 192 | - `test` => run AVA test functions with `NODE_ENV=test` 193 | - `test:coverage` => run `test` but with `nyc` for coverage checker 194 | - `test:watch` => run `test`, but with persistent watcher 195 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "tony_quetano@rapid7.com", 3 | "ava": { 4 | "babel": "inherit", 5 | "failFast": true, 6 | "files": [ 7 | "test/*.js" 8 | ], 9 | "require": [ 10 | "babel-register", 11 | "./test/helpers/setup-browser-env.js" 12 | ], 13 | "source": [ 14 | "src/*.js" 15 | ], 16 | "verbose": true 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/rapid7/krip/issues" 20 | }, 21 | "homepage": "https://github.com/rapid7/krip#readme", 22 | "dependencies": { 23 | "@trust/webcrypto": "^0.9.2", 24 | "webcrypto-shim": "^0.1.4" 25 | }, 26 | "description": "Dead simple encryption in both browser and node, using WebCrypto under the hood", 27 | "devDependencies": { 28 | "ava": "^0.25.0", 29 | "babel-cli": "^6.26.0", 30 | "babel-eslint": "^8.2.5", 31 | "babel-loader": "^7.1.4", 32 | "babel-plugin-transform-runtime": "^6.23.0", 33 | "babel-polyfill": "^6.26.0", 34 | "babel-preset-env": "^1.7.0", 35 | "babel-preset-minify": "^0.4.3", 36 | "babel-preset-stage-2": "^6.24.1", 37 | "babel-register": "^6.26.0", 38 | "babel-runtime": "^6.26.0", 39 | "bluebird": "^3.5.1", 40 | "browser-env": "^3.2.5", 41 | "eslint": "^5.0.1", 42 | "eslint-config-rapid7": "^3.0.2", 43 | "eslint-friendly-formatter": "^4.0.1", 44 | "eslint-loader": "^2.0.0", 45 | "fast-equals": "^1.5.3", 46 | "html-webpack-plugin": "^3.2.0", 47 | "in-publish": "^2.0.0", 48 | "nyc": "^12.0.2", 49 | "rollup": "^0.62.0", 50 | "rollup-plugin-babel": "^3.0.7", 51 | "rollup-plugin-commonjs": "^9.1.3", 52 | "rollup-plugin-node-resolve": "^3.3.0", 53 | "rollup-plugin-replace": "^2.0.0", 54 | "sinon": "^6.0.1", 55 | "text-encoding": "^0.6.4", 56 | "webpack": "^4.14.0", 57 | "webpack-cli": "^3.0.8", 58 | "webpack-dev-server": "^3.1.4" 59 | }, 60 | "main": "index.js", 61 | "name": "krip", 62 | "keywords": [ 63 | "webcrypto", 64 | "crypto", 65 | "encrypt", 66 | "encryption", 67 | "hash" 68 | ], 69 | "license": "MIT", 70 | "repository": { 71 | "type": "git", 72 | "url": "git+https://github.com/rapid7/krip.git" 73 | }, 74 | "scripts": { 75 | "build": "NODE_ENV=production rollup -c", 76 | "clean:dist": "rimraf dist", 77 | "dev": "NODE_ENV=development webpack-dev-server --colors --progress --profile --config=webpack/webpack.config.dev.js", 78 | "dist": "npm run clean:dist && npm run build", 79 | "lint": "eslint --max-warnings 0 src", 80 | "lint:fix": "npm run lint -- --fix", 81 | "prepublish": "if in-publish; then npm run prepublish:compile; fi", 82 | "prepublish:compile": "npm run lint && npm run test:coverage && npm run dist", 83 | "start": "npm run dev", 84 | "test": "NODE_PATH=. NODE_ENV=test ava", 85 | "test:coverage": "nyc --cache npm test", 86 | "test:watch": "npm run test -- --watch" 87 | }, 88 | "version": "1.0.0" 89 | } 90 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import replace from 'rollup-plugin-replace'; 4 | import resolve from 'rollup-plugin-node-resolve'; 5 | 6 | export default [ 7 | { 8 | input: 'src/index.js', 9 | output: { 10 | exports: 'named', 11 | file: 'index.js', 12 | format: 'umd', 13 | name: 'krip', 14 | sourcemap: true, 15 | }, 16 | plugins: [ 17 | babel({ 18 | exclude: 'node_modules/**', 19 | }), 20 | commonjs({ 21 | include: ['node_modules/**'], 22 | }), 23 | resolve({ 24 | browser: true, 25 | jsnext: true, 26 | main: true, 27 | module: true, 28 | }), 29 | ], 30 | }, 31 | { 32 | input: 'src/index.js', 33 | output: { 34 | exports: 'named', 35 | file: 'node.js', 36 | format: 'umd', 37 | name: 'krip', 38 | sourcemap: true, 39 | }, 40 | plugins: [ 41 | babel({ 42 | exclude: 'node_modules/**', 43 | }), 44 | commonjs({ 45 | include: ['node_modules/**'], 46 | }), 47 | replace({ 48 | 'GLOBAL.TextDecoder': "require('util').TextDecoder", 49 | 'GLOBAL.TextEncoder': "require('util').TextEncoder", 50 | 'GLOBAL.crypto || GLOBAL.msCrypto': "require('@trust/webcrypto')", 51 | }), 52 | resolve({ 53 | browser: true, 54 | jsnext: true, 55 | main: true, 56 | module: true, 57 | }), 58 | ], 59 | }, 60 | { 61 | input: 'src/index.js', 62 | output: { 63 | exports: 'named', 64 | file: 'dist/index.min.js', 65 | format: 'umd', 66 | name: 'krip', 67 | }, 68 | plugins: [ 69 | babel({ 70 | exclude: 'node_modules/**', 71 | presets: ['minify'], 72 | }), 73 | commonjs({ 74 | include: ['node_modules/**'], 75 | }), 76 | resolve({ 77 | browser: true, 78 | jsnext: true, 79 | main: true, 80 | module: true, 81 | }), 82 | ], 83 | }, 84 | { 85 | input: 'src/index.js', 86 | output: { 87 | exports: 'named', 88 | file: 'dist/node.min.js', 89 | format: 'umd', 90 | name: 'krip', 91 | }, 92 | plugins: [ 93 | babel({ 94 | exclude: 'node_modules/**', 95 | presets: ['minify'], 96 | }), 97 | commonjs({ 98 | include: ['node_modules/**'], 99 | }), 100 | replace({ 101 | 'GLOBAL.TextDecoder': "require('util').TextDecoder", 102 | 'GLOBAL.TextEncoder': "require('util').TextEncoder", 103 | 'GLOBAL.crypto || GLOBAL.msCrypto': "require('@trust/webcrypto')", 104 | }), 105 | resolve({ 106 | browser: true, 107 | jsnext: true, 108 | main: true, 109 | module: true, 110 | }), 111 | ], 112 | }, 113 | ]; 114 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | // external dependencies 2 | /** 3 | * @constant {string} ALGORITHM 4 | */ 5 | export const ALGORITHM = 'AES-GCM'; 6 | 7 | /** 8 | * @constant {string} CHARSET 9 | */ 10 | export const CHARSET = 'utf-8'; 11 | 12 | /** 13 | * @constant {boolean} IS_BROWSER 14 | */ 15 | export const IS_BROWSER = typeof window !== 'undefined'; 16 | 17 | /** 18 | * @constant {Object} GLOBAL 19 | */ 20 | export const GLOBAL = IS_BROWSER ? window : global; 21 | 22 | /** 23 | * @constant {function} CRYPTO 24 | */ 25 | export const CRYPTO = GLOBAL.crypto || GLOBAL.msCrypto; 26 | 27 | /** 28 | * @constant {Array} CRYPTO_KEY_PROPERTIES 29 | */ 30 | export const CRYPTO_KEY_PROPERTIES = ['algorithm', 'extractable', 'type', 'usages']; 31 | 32 | /** 33 | * @constant {function} CRYPTO_SUBTLE 34 | */ 35 | export const CRYPTO_SUBTLE = CRYPTO.subtle || CRYPTO.webkitSubtle; 36 | 37 | /** 38 | * @constant {string} HASH_ALGORITHM 39 | */ 40 | export const HASH_ALGORITHM = 'SHA-256'; 41 | 42 | /** 43 | * @constant {number} HASH_BYTE_INCREMENT 44 | */ 45 | export const HASH_BYTE_INCREMENT = 4; 46 | 47 | /** 48 | * @constant {number} IV_SIZE 49 | */ 50 | export const IV_SIZE = 12; 51 | 52 | /** 53 | * @constant {number} KEY_LENGTH 54 | */ 55 | export const KEY_LENGTH = 256; 56 | 57 | /** 58 | * @constant {number} PARSEINT_TO_HEX 59 | */ 60 | export const PARSEINT_TO_HEX = 16; 61 | 62 | /** 63 | * @constant function} PARSER 64 | * 65 | * @param {string} string the string to parse 66 | * @returns {any} the parsed string value 67 | */ 68 | export const PARSER = (string) => { 69 | try { 70 | return JSON.parse(string); 71 | } catch (error) { 72 | return string; 73 | } 74 | }; 75 | 76 | /** 77 | * @constant {function} STRINGIFIER 78 | * 79 | * @param {any} value the value to stringify 80 | * @returns {string} the stringified value 81 | */ 82 | export const STRINGIFIER = (value) => { 83 | try { 84 | return JSON.stringify(value); 85 | } catch (error) { 86 | return `${value}`; 87 | } 88 | }; 89 | 90 | /** 91 | * @constant {number} STRIP_LEADING_CRYPT_HEX 92 | */ 93 | export const STRIP_LEADING_CRYPT_HEX = -2; 94 | 95 | /** 96 | * @constant {number} STRIP_LEADING_HASH_HEX 97 | */ 98 | export const STRIP_LEADING_HASH_HEX = -8; 99 | 100 | /** 101 | * @constant {Object} TEXT 102 | * 103 | * @property {function} Decoder the decoder to use when converting text 104 | * @property {function} Encoder the encoder to use when converting text 105 | */ 106 | export const TEXT = { 107 | Decoder: GLOBAL.TextDecoder, 108 | Encoder: GLOBAL.TextEncoder, 109 | }; 110 | 111 | /** 112 | * @constant {Object} TYPED_ARRAY_TYPES 113 | */ 114 | export const TYPED_ARRAY_TYPES = { 115 | '[object Float32Array]': true, 116 | '[object Float64Array]': true, 117 | '[object Int8Array]': true, 118 | '[object Int16Array]': true, 119 | '[object Int32Array]': true, 120 | '[object Uint8Array]': true, 121 | '[object Uint8ClampedArray]': true, 122 | '[object Uint16Array]': true, 123 | '[object Uint32Array]': true, 124 | }; 125 | 126 | /** 127 | * @constant {number} UNHEX_INDEX_MULTIPLIER 128 | */ 129 | export const UNHEX_INDEX_MULTIPLIER = 2; 130 | 131 | /** 132 | * @constant {Array} VALID_HASH_ALGORITHMS 133 | */ 134 | export const VALID_HASH_ALGORITHMS = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']; 135 | 136 | /** 137 | * @const {Object} DEFAULT_OPTIONS 138 | * 139 | * @property {string} charset the charset to use for encoding / decoding the text 140 | * @property {number} ivSize the size of TypedArray to use when building the iv 141 | * @property {number} keyLength the length of the generated key 142 | * @property {function} parse the method to use when parsing the stringified value upon decryption 143 | * @property {function} stringify the method to use when stringifying the value for encryption 144 | */ 145 | export const DEFAULT_OPTIONS = { 146 | charset: CHARSET, 147 | ivSize: IV_SIZE, 148 | keyLength: KEY_LENGTH, 149 | parse: PARSER, 150 | stringify: STRINGIFIER, 151 | }; 152 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // external dependencies 2 | import 'webcrypto-shim'; 3 | 4 | // constants 5 | import { 6 | GLOBAL, 7 | HASH_ALGORITHM, 8 | IS_BROWSER, 9 | TEXT, 10 | VALID_HASH_ALGORITHMS 11 | } from './constants'; 12 | 13 | // utils 14 | import { 15 | getNormalizedOptions, 16 | getCryptoHash, 17 | getCryptoKey, 18 | getHexStringForCrypt, 19 | getHexStringForHash, 20 | isPlainObject, 21 | rejectsAttempt, 22 | subtleDecrypt, 23 | subtleEncrypt, 24 | subtleGenerateKey, 25 | throwsProcessing 26 | } from './utils'; 27 | 28 | if (!GLOBAL.Promise) { 29 | throw new ReferenceError(`Promise must be available globally.`); 30 | } 31 | 32 | ['TextDecoder', 'TextEncoder'].forEach((requirement) => { 33 | if (IS_BROWSER && !GLOBAL[requirement]) { 34 | throw new ReferenceError(`${requirement} must be available globally.`); 35 | } 36 | }); 37 | 38 | /** 39 | * @function decrypt 40 | * 41 | * @description 42 | * decrypt the encrypted value based on the key and options passed 43 | * 44 | * @param {string} encrypted the encrypted string value to decrypt 45 | * @param {string} secret the key to use for decryption (same as key used for encryption) 46 | * @param {function} [passedOptions={}] custom options used in the decryption 47 | * @returns {any} the decrypted value 48 | */ 49 | export const decrypt = (encrypted, secret, passedOptions = {}) => { 50 | if (!secret) { 51 | return rejectsAttempt('secret', 'provided'); 52 | } 53 | 54 | if (passedOptions && !isPlainObject(passedOptions)) { 55 | return rejectsAttempt('options', 'a plain object'); 56 | } 57 | 58 | const options = getNormalizedOptions(passedOptions); 59 | 60 | return getCryptoKey(secret, 'decrypt', options) 61 | .then((cryptoKey) => subtleDecrypt(cryptoKey, encrypted, options)) 62 | .then((buffer) => new TEXT.Decoder(options.charset).decode(buffer)) 63 | .then((text) => options.parse(text)) 64 | .catch(throwsProcessing('decrypt')); 65 | }; 66 | 67 | /** 68 | * @function encrypt 69 | * 70 | * @description 71 | * encrypt the value based on the key and options passed 72 | * 73 | * @param {any} value the value to encrypt 74 | * @param {string} secret the key to use for encryption (same as key used for decryption) 75 | * @param {Object} [passedOptions={}] custom options used in the encryption 76 | * @returns {string} the encrypted value 77 | */ 78 | export const encrypt = (value, secret, passedOptions = {}) => { 79 | if (!secret) { 80 | return rejectsAttempt('secret', 'provided'); 81 | } 82 | 83 | if (passedOptions && !isPlainObject(passedOptions)) { 84 | return rejectsAttempt('options', 'a plain object'); 85 | } 86 | 87 | const options = getNormalizedOptions(passedOptions); 88 | 89 | return getCryptoKey(secret, 'encrypt', options) 90 | .then((cryptoKey) => subtleEncrypt(cryptoKey, options.stringify(value), options)) 91 | .then(({buffer, iv}) => `${getHexStringForCrypt(iv)}${getHexStringForCrypt(buffer)}`) 92 | .catch(throwsProcessing('encrypt')); 93 | }; 94 | 95 | /** 96 | * @function generateSecret 97 | * 98 | * @description 99 | * generate a new crypto key based on the options passed 100 | * 101 | * @param {Object} [options={}] custom options used in the generation of the key 102 | * @returns {CryptoKey} the generated key 103 | */ 104 | export const generateSecret = (options = {}) => subtleGenerateKey(getNormalizedOptions(options)); 105 | 106 | /** 107 | * @function hash 108 | * 109 | * @description 110 | * hash the value passed based on the SHA-256 algorithm 111 | * 112 | * @param {any} value the value to hash 113 | * @param {string} [algorithm=HASH_ALGORITHM] the algorithm to use when hashing 114 | * @param {Object} [passedOptions={}] custom options used in the generation of the hash 115 | * @returns {string} the SHA-256 hash of the value passed 116 | */ 117 | export const hash = (value, algorithm = HASH_ALGORITHM, passedOptions = {}) => { 118 | const options = getNormalizedOptions(passedOptions); 119 | 120 | if (!~VALID_HASH_ALGORITHMS.indexOf(algorithm.toUpperCase())) { 121 | return rejectsAttempt('algorithm', `one of "${VALID_HASH_ALGORITHMS.join('", "')}"`); 122 | } 123 | 124 | if (passedOptions && !isPlainObject(passedOptions)) { 125 | return rejectsAttempt('options', 'a plain object', 'processing'); 126 | } 127 | 128 | return getCryptoHash(value, algorithm, options) 129 | .then((buffer) => getHexStringForHash(buffer, options)) 130 | .catch(throwsProcessing('hash')); 131 | }; 132 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | // constants 2 | import { 3 | ALGORITHM, 4 | CRYPTO, 5 | CRYPTO_KEY_PROPERTIES, 6 | CRYPTO_SUBTLE, 7 | DEFAULT_OPTIONS, 8 | HASH_ALGORITHM, 9 | HASH_BYTE_INCREMENT, 10 | PARSEINT_TO_HEX, 11 | STRIP_LEADING_CRYPT_HEX, 12 | STRIP_LEADING_HASH_HEX, 13 | TEXT, 14 | TYPED_ARRAY_TYPES, 15 | UNHEX_INDEX_MULTIPLIER 16 | } from './constants'; 17 | 18 | /** 19 | * @function getObjectClass 20 | * 21 | * @description 22 | * call the native object toString on the value passed 23 | * 24 | * @param {any} value the value to get the object class of as a string 25 | * @returns {string} the object class 26 | */ 27 | export const getObjectClass = (value) => Object.prototype.toString.call(value); 28 | 29 | /** 30 | * @function getTypedArray 31 | * 32 | * @description 33 | * get a new typed array based on the value passed 34 | * 35 | * @param {any} buffer the buffer to convert to a typed array 36 | * @returns {TypedArray} the typed array 37 | */ 38 | export const getTypedArray = (buffer) => new Uint8Array(buffer); 39 | 40 | /** 41 | * @function getBufferFromHexString 42 | * 43 | * @description 44 | * convert the hex string into the buffer it was built from 45 | * 46 | * @param {string} hex the hex string to convert to a buffer 47 | * @returns {ArrayBuffer} the buffer 48 | */ 49 | export const getBufferFromHexString = (hex) => { 50 | const length = hex.length / UNHEX_INDEX_MULTIPLIER; 51 | const typedArray = getTypedArray(new ArrayBuffer(length)); 52 | 53 | let hexIndex; 54 | 55 | for (let index = 0; index < length; index++) { 56 | hexIndex = index * UNHEX_INDEX_MULTIPLIER; 57 | 58 | typedArray[index] = parseInt(hex[hexIndex] + hex[hexIndex + 1], 16); 59 | } 60 | 61 | return typedArray.buffer; 62 | }; 63 | 64 | /** 65 | * @function getHexStringForCrypt 66 | * 67 | * @description 68 | * build a hex string for encrypt / decrypt from a typed array 69 | * 70 | * @param {ArrayBuffer} typedArray the typedArray to convert to a hex string 71 | * @returns {string} the stringified buffer 72 | */ 73 | export const getHexStringForCrypt = (typedArray) => { 74 | const byteArray = Array.prototype.slice.call(typedArray.buffer ? typedArray : getTypedArray(typedArray), 0); 75 | 76 | let hexString = ''; 77 | 78 | for (let index = 0; index < byteArray.length; index++) { 79 | hexString += `00${byteArray[index].toString(PARSEINT_TO_HEX).toUpperCase()}`.slice(STRIP_LEADING_CRYPT_HEX); 80 | } 81 | 82 | return hexString; 83 | }; 84 | 85 | /** 86 | * @function getHexStringForHash 87 | * 88 | * @description 89 | * build a hex string for a one-way hash from a typed array 90 | * 91 | * @param {TypedArray} typedArray the typed array to get the buffer from 92 | * @returns {string} the hex string 93 | */ 94 | export const getHexStringForHash = (typedArray) => { 95 | const view = new DataView(typedArray.buffer); 96 | 97 | let hexString = ''; 98 | 99 | for (let index = 0; index < view.byteLength; index += HASH_BYTE_INCREMENT) { 100 | hexString += `00000000${view.getUint32(index).toString(PARSEINT_TO_HEX)}`.slice(STRIP_LEADING_HASH_HEX); 101 | } 102 | 103 | return hexString; 104 | }; 105 | 106 | /** 107 | * @function getNormalizedSecret 108 | * 109 | * @description 110 | * get the secret normalized 111 | * 112 | * @param {any} secret the secret to normalize 113 | * @param {Object} options custom options used for operations 114 | * @param {function} options.stringify the stringify method to use for non-string keys 115 | * @returns {string} the stringified secret 116 | */ 117 | export const getNormalizedSecret = (secret, options) => 118 | typeof secret === 'string' ? secret : options.stringify(secret); 119 | 120 | /** 121 | * @function getNormalizedOptions 122 | * 123 | * @description 124 | * get the full options object by merging the options passed with the default options 125 | * 126 | * @param {Object} options the options passed 127 | * @returns {Object} the options passed shallowly merged with the default options 128 | */ 129 | export const getNormalizedOptions = (options) => ({ 130 | ...DEFAULT_OPTIONS, 131 | ...options, 132 | }); 133 | 134 | /** 135 | * @function isArrayBuffer 136 | * 137 | * @description 138 | * is the value passed an array buffer 139 | * 140 | * @param {any} value value to test 141 | * @returns {boolean} is the value an array buffer 142 | */ 143 | export const isArrayBuffer = (value) => getObjectClass(value) === '[object ArrayBuffer]'; 144 | 145 | /** 146 | * @function isCryptoKey 147 | * 148 | * @description 149 | * is the value passed a CryptoKey 150 | * 151 | * @param {any} value value to test 152 | * @returns {boolean} is the value an already-built crypto key 153 | */ 154 | export const isCryptoKey = (value) => { 155 | if (getObjectClass(value) === '[object CryptoKey]') { 156 | return true; 157 | } 158 | 159 | if (!value || typeof value !== 'object') { 160 | return false; 161 | } 162 | 163 | for (let index = 0; index < CRYPTO_KEY_PROPERTIES.length; index++) { 164 | // eslint-disable-next-line max-depth 165 | if (!Object.prototype.hasOwnProperty.call(value, CRYPTO_KEY_PROPERTIES[index])) { 166 | return false; 167 | } 168 | } 169 | 170 | return true; 171 | }; 172 | /** 173 | * @function isPlainObject 174 | * 175 | * @description 176 | * is the value passed a plain object 177 | * 178 | * @param {any} value value to test 179 | * @returns {boolean} is the value a plain object 180 | */ 181 | export const isPlainObject = (value) => !!value && getObjectClass(value) === '[object Object]'; 182 | 183 | /** 184 | * @function isTypedArray 185 | * 186 | * @description 187 | * is the value passed a typed array 188 | * 189 | * @param {any} value value to test 190 | * @returns {boolean} is the value a typed array 191 | */ 192 | export const isTypedArray = (value) => !!TYPED_ARRAY_TYPES[getObjectClass(value)]; 193 | 194 | /** 195 | * @function subtleDecrypt 196 | * 197 | * @description 198 | * decrypt the string based on the crypto key used 199 | * 200 | * @param {string} cryptoKey the key the encryption is based on 201 | * @param {strin} encrypted the encrypted getHexStringForCrypt string 202 | * @param {Object} options custom options used for operations 203 | * @param {number} options.ivSize the size of TypedArray to use for building the iv 204 | * @returns {string} the decrypted string 205 | */ 206 | export const subtleDecrypt = (cryptoKey, encrypted, options) => { 207 | const ivHex = encrypted.slice(0, options.ivSize * UNHEX_INDEX_MULTIPLIER); 208 | const textHex = encrypted.slice(options.ivSize * UNHEX_INDEX_MULTIPLIER); 209 | 210 | return CRYPTO_SUBTLE.decrypt( 211 | { 212 | iv: getTypedArray(getBufferFromHexString(ivHex)), 213 | name: ALGORITHM, 214 | tagLength: 128, 215 | }, 216 | cryptoKey, 217 | getBufferFromHexString(textHex) 218 | ); 219 | }; 220 | 221 | /** 222 | * @function subtleDigest 223 | * 224 | * @description 225 | * run the crypto digest method for the encoded key 226 | * 227 | * @param {string} encodedKey the key to digest 228 | * @param {string} algorithm the algorithm to digest the key with 229 | * @returns {CryptoKey} the crypto key object 230 | */ 231 | export const subtleDigest = (encodedKey, algorithm) => 232 | CRYPTO_SUBTLE.digest({name: algorithm.toUpperCase()}, encodedKey); 233 | 234 | /** 235 | * @function subtleEncrypt 236 | * 237 | * @description 238 | * encrypt the text based on the key passed 239 | * 240 | * @param {CryptoKey} cryptoKey the crypto key object 241 | * @param {string} text the text to encrypt 242 | * @param {Object} options custom options used for operations 243 | * @param {string} options.charset the charset to use when encoding the text 244 | * @param {number} options.ivSize the size of TypedArray to use for building the iv 245 | * @returns {Promise} promise resolving to the buffer and the iv of the text 246 | */ 247 | export const subtleEncrypt = (cryptoKey, text, options) => { 248 | const iv = CRYPTO.getRandomValues(getTypedArray(options.ivSize)); 249 | 250 | return CRYPTO_SUBTLE.encrypt( 251 | { 252 | iv, 253 | name: ALGORITHM, 254 | tagLength: 128, 255 | }, 256 | cryptoKey, 257 | new TEXT.Encoder(options.charset).encode(text) 258 | ).then((buffer) => ({ 259 | buffer, 260 | iv, 261 | })); 262 | }; 263 | 264 | /** 265 | * @function subtleGenerateKey 266 | * 267 | * @description 268 | * generate a new CryptoKey 269 | * 270 | * @param {Object} options custom options used for operations 271 | * @param {number} options.keyLength the length of the key to generate 272 | * @returns {CryptoKey} a custom crypto key 273 | */ 274 | export const subtleGenerateKey = (options) => 275 | CRYPTO_SUBTLE.generateKey( 276 | { 277 | length: options.keyLength, 278 | name: ALGORITHM, 279 | }, 280 | false, 281 | ['decrypt', 'encrypt'] 282 | ); 283 | 284 | /** 285 | * @function subtleImportKey 286 | * 287 | * @description 288 | * run the crypto importKey method based on the hash and type passed 289 | * 290 | * @param {ArrayBuffer} hash the digested hash from the key digestion 291 | * @param {string} type the type ot crypto process to allow 292 | * @returns {Promise} promise resolving to the key 293 | */ 294 | export const subtleImportKey = (hash, type) => CRYPTO_SUBTLE.importKey('raw', hash, ALGORITHM, false, [type]); 295 | 296 | /** 297 | * @function getCryptoHash 298 | * 299 | * @description 300 | * get the hash used for crypto keys 301 | * 302 | * @param {any} secret the secret to to base the hash on 303 | * @param {string} algorithm the hash algorithm to use in the key digestion 304 | * @param {Object} options custom options used for operations 305 | * @param {string} options.charset the charset to use when encoding the text 306 | * @returns {Promise} promise resolving to the hash 307 | */ 308 | export const getCryptoHash = (secret, algorithm, options) => 309 | Promise.resolve( 310 | isTypedArray(secret) 311 | ? secret 312 | : isArrayBuffer(secret) 313 | ? getTypedArray(secret) 314 | : new TEXT.Encoder(options.charset).encode(getNormalizedSecret(secret, options)) 315 | ) 316 | .then((encodedKey) => subtleDigest(encodedKey, algorithm)) 317 | .then(getTypedArray); 318 | 319 | /** 320 | * @function getCryptoKey 321 | * 322 | * @description 323 | * get the CryptoKey object to use for encryption / decrytion 324 | * 325 | * @param {string} secret the secret to base the encryption / decryption on 326 | * @param {string} type the type ot crypto process to allow 327 | * @param {Object} options custom options used for operations 328 | * @param {string} options.charset the charset to use when encoding the text 329 | * @returns {Promise} promise resolving to the crypto key 330 | */ 331 | export const getCryptoKey = (secret, type, options) => 332 | isCryptoKey(secret) 333 | ? Promise.resolve(secret) 334 | : getCryptoHash(secret, HASH_ALGORITHM, options).then((hash) => subtleImportKey(hash, type)); 335 | 336 | /** 337 | * @function throws 338 | * 339 | * @description 340 | * throws a string as an error to prevent having a stack trace, but still 341 | * say which operation failed 342 | * 343 | * @throws 344 | * 345 | * @param {string} value the value the action is taken upon 346 | * @param {string} action the action taken 347 | * @returns {Promise} the rejected promise 348 | */ 349 | export const rejectsAttempt = (value, action) => Promise.reject(new ReferenceError(`The ${value} must be ${action}.`)); 350 | 351 | /** 352 | * @function throwsProcessing 353 | * 354 | * @description 355 | * throw a new error based on processing 356 | * 357 | * @param {string} type the type being processed 358 | * @returns {function(Error): void} the function to throw the error 359 | */ 360 | export const throwsProcessing = (type) => (error) => { 361 | throw new error.constructor(`Could not ${type} this value.`); 362 | }; 363 | -------------------------------------------------------------------------------- /test-node.js: -------------------------------------------------------------------------------- 1 | const krip = require('./node'); 2 | 3 | const KEY = {some: 'key'}; 4 | 5 | const test = async (value) => { 6 | const encrypted = await krip.encrypt(value, KEY); 7 | 8 | console.log(encrypted); 9 | 10 | const decrypted = await krip.decrypt(encrypted, KEY); 11 | 12 | console.log(decrypted); 13 | 14 | const key = await krip.generateSecret(); 15 | 16 | console.log(key); 17 | 18 | const hashed = await krip.hash('foo', 'SHA-1'); 19 | 20 | console.log(hashed); 21 | }; 22 | 23 | test({some: 'data'}); 24 | -------------------------------------------------------------------------------- /test/constants.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | 4 | // src 5 | import * as constants from 'src/constants'; 6 | 7 | test('if PARSER will parse a valid JSON string', (t) => { 8 | const object = {foo: 'bar'}; 9 | const string = JSON.stringify(object); 10 | 11 | const result = constants.PARSER(string); 12 | 13 | t.not(result, object); 14 | t.deepEqual(result, object); 15 | }); 16 | 17 | test('if PARSER will return the value passed if unable to parse', (t) => { 18 | const object = {foo: 'bar'}; 19 | 20 | const result = constants.PARSER(object); 21 | 22 | t.is(result, object); 23 | }); 24 | 25 | test('if STRINGIFIER will parse a valid JSON string', (t) => { 26 | const object = {foo: 'bar'}; 27 | 28 | const result = constants.STRINGIFIER({foo: 'bar'}); 29 | 30 | t.is(result, JSON.stringify(object)); 31 | }); 32 | 33 | test('if STRINGIFIER will return the value passed if unable to parse', (t) => { 34 | const object = {foo: 'bar'}; 35 | 36 | object.nested = object; 37 | 38 | const result = constants.STRINGIFIER(object); 39 | 40 | t.is(result, `${object}`); 41 | }); 42 | -------------------------------------------------------------------------------- /test/helpers/setup-browser-env.js: -------------------------------------------------------------------------------- 1 | // external dependencies 2 | import browserEnv from 'browser-env'; 3 | import webcrypto from '@trust/webcrypto'; 4 | import { 5 | TextDecoder, 6 | TextEncoder 7 | } from 'util'; 8 | 9 | browserEnv(); 10 | 11 | window.crypto = webcrypto; 12 | window.Promise = global.Promise; 13 | 14 | global.TextDecoder = window.TextDecoder = TextDecoder; 15 | global.TextEncoder = window.TextEncoder = TextEncoder; 16 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // test 2 | import test from 'ava'; 3 | import nodeCrypto from 'crypto'; 4 | 5 | // src 6 | import * as index from 'src/index'; 7 | import * as utils from 'src/utils'; 8 | import { 9 | IV_SIZE, 10 | UNHEX_INDEX_MULTIPLIER 11 | } from 'src/constants'; 12 | 13 | const KEY = 'MY_SPECIAL_KEY'; 14 | 15 | test('if encrypt will successfully encrypt the value, and decrypt will successfully decrypt the encrypted value', async (t) => { 16 | const value = { 17 | some: 'data', 18 | }; 19 | 20 | const encrypted = await index.encrypt(value, KEY); 21 | 22 | const ivHex = encrypted.slice(0, IV_SIZE * 2); 23 | const textHex = encrypted.slice(IV_SIZE * 2); 24 | 25 | t.true(/^[a-fA-F0-9]+$/.test(ivHex)); 26 | t.true(/^[a-fA-F0-9]+$/.test(textHex)); 27 | 28 | t.not(encrypted, value); 29 | t.notDeepEqual(encrypted, value); 30 | 31 | const decrypted = await index.decrypt(encrypted, KEY); 32 | 33 | t.not(decrypted, value); 34 | t.deepEqual(decrypted, value); 35 | }); 36 | 37 | test('if decrypt will return a rejected promise if no secret is provided', async (t) => { 38 | const decrypted = 'decrypted'; 39 | 40 | try { 41 | await index.decrypt(decrypted); 42 | 43 | t.fail(); 44 | } catch (error) { 45 | t.pass(error); 46 | } 47 | }); 48 | 49 | test('if decrypt will return a rejected promise if the options passed are not a plain object', async (t) => { 50 | const decrypted = 'decrypted'; 51 | const secret = 'secret'; 52 | const passedOptions = 'passedOptions'; 53 | 54 | try { 55 | await index.decrypt(decrypted, secret, passedOptions); 56 | 57 | t.fail(); 58 | } catch (error) { 59 | t.pass(error); 60 | } 61 | }); 62 | 63 | test('if encrypt will return a rejected promise if no secret is provided', async (t) => { 64 | const value = 'value'; 65 | 66 | try { 67 | await index.encrypt(value); 68 | 69 | t.fail(); 70 | } catch (error) { 71 | t.pass(error); 72 | } 73 | }); 74 | 75 | test('if encrypt will return a rejected promise if the options passed are not a plain object', async (t) => { 76 | const value = 'value'; 77 | const secret = 'secret'; 78 | const passedOptions = 'passedOptions'; 79 | 80 | try { 81 | await index.encrypt(value, secret, passedOptions); 82 | 83 | t.fail(); 84 | } catch (error) { 85 | t.pass(error); 86 | } 87 | }); 88 | 89 | test('if generateSecret will generate a new CryptoKey', async (t) => { 90 | const result = await index.generateSecret(); 91 | 92 | t.true(utils.isCryptoKey(result)); 93 | 94 | t.is(result.algorithm.length, 256); 95 | t.is(result.algorithm.name, 'AES-GCM'); 96 | t.false(result.extractable); 97 | t.is(result.type, 'secret'); 98 | t.deepEqual(result.usages, ['decrypt', 'encrypt']); 99 | }); 100 | 101 | test('if hash will generate a new hash', async (t) => { 102 | const value = { 103 | some: 'data', 104 | }; 105 | 106 | const result = await index.hash(value); 107 | 108 | t.is( 109 | result, 110 | nodeCrypto 111 | .createHash('sha256') 112 | .update(JSON.stringify(value), 'utf8') 113 | .digest('hex') 114 | ); 115 | }); 116 | 117 | test('if hash will generate a new hash based on other types', async (t) => { 118 | const value = { 119 | some: 'data', 120 | }; 121 | 122 | t.is( 123 | await index.hash(value, 'SHA-1'), 124 | nodeCrypto 125 | .createHash('sha1') 126 | .update(JSON.stringify(value), 'utf8') 127 | .digest('hex') 128 | ); 129 | 130 | t.is( 131 | await index.hash(value, 'SHA-384'), 132 | nodeCrypto 133 | .createHash('sha384') 134 | .update(JSON.stringify(value), 'utf8') 135 | .digest('hex') 136 | ); 137 | 138 | t.is( 139 | await index.hash(value, 'SHA-512'), 140 | nodeCrypto 141 | .createHash('sha512') 142 | .update(JSON.stringify(value), 'utf8') 143 | .digest('hex') 144 | ); 145 | }); 146 | 147 | test('if hash will return a rejected promise if an invalid algorithm is provided', async (t) => { 148 | const value = 'value'; 149 | const algorithm = 'algorithm'; 150 | 151 | try { 152 | await index.hash(value, algorithm); 153 | 154 | t.fail(); 155 | } catch (error) { 156 | t.pass(error); 157 | } 158 | }); 159 | 160 | test('if hash will return a rejected promise if the options passed are not a plain object', async (t) => { 161 | const value = 'value'; 162 | const algorithm = 'SHA-256'; 163 | const passedOptions = 'passedOptions'; 164 | 165 | try { 166 | await index.hash(value, algorithm, passedOptions); 167 | 168 | t.fail(); 169 | } catch (error) { 170 | t.pass(error); 171 | } 172 | }); 173 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | 3 | // test 4 | import test from 'ava'; 5 | import sinon from 'sinon'; 6 | 7 | // src 8 | import * as utils from 'src/utils'; 9 | import * as constants from 'src/constants'; 10 | 11 | test('if getObjectClass will return the correct object class', (t) => { 12 | const map = new Map(); 13 | 14 | const result = utils.getObjectClass(map); 15 | 16 | t.is(result, Object.prototype.toString.call(map)); 17 | }); 18 | 19 | test('if getTypedArray will return the value passed as the typedarray of that', (t) => { 20 | const buffer = new ArrayBuffer(12); 21 | 22 | const result = utils.getTypedArray(buffer); 23 | 24 | t.true(result instanceof Uint8Array); 25 | t.is(result.buffer, buffer); 26 | }); 27 | 28 | test('if getBufferFromHexString will get the ArrayBuffer based on the hex string passed', (t) => { 29 | const hexString = 'EE10820AB66E4891567955E9'; 30 | 31 | const result = utils.getBufferFromHexString(hexString); 32 | 33 | t.true(result instanceof ArrayBuffer); 34 | t.is(result.byteLength, hexString.length / 2); 35 | 36 | t.deepEqual(new Uint8Array(result), new Uint8Array([238, 16, 130, 10, 182, 110, 72, 145, 86, 121, 85, 233])); 37 | }); 38 | 39 | test('if getHexStringForCrypt will get the hexString based on the TypedArray passed', (t) => { 40 | const typedArray = new Uint8Array([17, 4, 84, 221]); 41 | 42 | const result = utils.getHexStringForCrypt(typedArray); 43 | 44 | t.is(result, '110454DD'); 45 | }); 46 | 47 | test('if getHexStringForCrypt will get the hexString based on the ArrayBuffer passed', (t) => { 48 | const typedArray = new Uint8Array([17, 4, 84, 221]); 49 | 50 | const result = utils.getHexStringForCrypt(typedArray.buffer); 51 | 52 | t.is(result, '110454DD'); 53 | }); 54 | 55 | test('if getHexStringForHash will get the hexString based on the TypedArray passed', (t) => { 56 | const typedArray = new Uint8Array([17, 4, 84, 221]); 57 | 58 | const result = utils.getHexStringForHash(typedArray); 59 | 60 | t.is(result, '110454dd'); 61 | }); 62 | 63 | test('if getNormalizedSecret will return the secret passed if a string', (t) => { 64 | const secret = 'secret'; 65 | const options = { 66 | stringify: sinon.stub().callsFake((value) => 67 | value 68 | .split('') 69 | .reverse() 70 | .join('') 71 | ), 72 | }; 73 | 74 | const result = utils.getNormalizedSecret(secret, options); 75 | 76 | t.true(options.stringify.notCalled); 77 | 78 | t.is(result, secret); 79 | }); 80 | 81 | test('if getNormalizedSecret will return the serialized secret passed if not a string', (t) => { 82 | const secret = {some: 'secret'}; 83 | const options = { 84 | stringify: sinon.stub().callsFake((value) => 85 | value.some 86 | .split('') 87 | .reverse() 88 | .join('') 89 | ), 90 | }; 91 | 92 | const result = utils.getNormalizedSecret(secret, options); 93 | 94 | t.true(options.stringify.calledOnce); 95 | t.true(options.stringify.calledWith(secret)); 96 | 97 | t.is(result, 'terces'); 98 | }); 99 | 100 | test('if getNormalizedOptions will return the options merged into the default options as a new object', (t) => { 101 | const options = { 102 | stringify() {}, 103 | }; 104 | 105 | const result = utils.getNormalizedOptions(options); 106 | 107 | t.not(result, constants.DEFAULT_OPTIONS); 108 | t.not(result, options); 109 | t.deepEqual(result, { 110 | ...constants.DEFAULT_OPTIONS, 111 | ...options, 112 | }); 113 | }); 114 | 115 | test('if isArrayBuffer will test if the value passed is an ArrayBuffer', (t) => { 116 | const object = new Uint8Array(12); 117 | 118 | t.false(utils.isArrayBuffer(object)); 119 | t.true(utils.isArrayBuffer(object.buffer)); 120 | }); 121 | 122 | test('if isCryptoKey will test if the value passed is a CryptoKey', (t) => { 123 | class CryptoKey { 124 | constructor() { 125 | return this; 126 | } 127 | 128 | get [Symbol.toStringTag]() { 129 | return 'CryptoKey'; 130 | } 131 | } 132 | 133 | const cryptoKey = new CryptoKey(); 134 | const object = { 135 | algorithm: 'AES-GCM', 136 | extractable: false, 137 | type: 'foo', 138 | usages: ['encrypt'], 139 | }; 140 | 141 | t.true(utils.isCryptoKey(cryptoKey)); 142 | t.true(utils.isCryptoKey(object)); 143 | 144 | t.false(utils.isCryptoKey(null)); 145 | t.false(utils.isCryptoKey(object.algorithm)); 146 | t.false(utils.isCryptoKey({})); 147 | }); 148 | 149 | test('if isPlainObject will test if the value passed is a plain object', (t) => { 150 | t.true(utils.isPlainObject({})); 151 | t.false(utils.isPlainObject(true)); 152 | t.false(utils.isPlainObject(null)); 153 | t.false(utils.isPlainObject(123)); 154 | t.false(utils.isPlainObject('string')); 155 | }); 156 | 157 | test('if isTypedArray will test if the value passed is an TypedArray', (t) => { 158 | const object = new Uint8Array(12); 159 | 160 | t.true(utils.isTypedArray(object)); 161 | t.false(utils.isTypedArray(object.buffer)); 162 | }); 163 | 164 | test.serial('if subtleDecrypt will decrypt the crypto key', async (t) => { 165 | const decrypted = 'decrypted'; 166 | 167 | const decrypt = sinon.stub(constants.CRYPTO_SUBTLE, 'decrypt').resolves(decrypted); 168 | 169 | const cryptoKey = 'cryptoKey'; 170 | const ivHex = utils.getHexStringForCrypt(new Uint8Array(12)); 171 | const textHex = utils.getHexStringForCrypt(new Uint8Array(96)); 172 | const encryptedText = `${ivHex}+${textHex}`; 173 | const options = { 174 | ivSize: 12, 175 | }; 176 | 177 | const result = await utils.subtleDecrypt(cryptoKey, encryptedText, options); 178 | 179 | t.true(decrypt.calledOnce); 180 | t.deepEqual(decrypt.args[0], [ 181 | { 182 | iv: utils.getTypedArray(utils.getBufferFromHexString(ivHex)), 183 | name: constants.ALGORITHM, 184 | tagLength: 128, 185 | }, 186 | cryptoKey, 187 | utils.getBufferFromHexString(textHex), 188 | ]); 189 | 190 | t.is(result, decrypted); 191 | 192 | decrypt.restore(); 193 | }); 194 | 195 | test.serial('if subtleDigest will digest the key with the algorithm passed', async (t) => { 196 | const encodedKey = 'encodedKey'; 197 | const algorithm = 'algorithm'; 198 | 199 | const digested = 'digested'; 200 | 201 | const digest = sinon.stub(constants.CRYPTO_SUBTLE, 'digest').resolves(digested); 202 | 203 | const result = await utils.subtleDigest(encodedKey, algorithm); 204 | 205 | t.true(digest.calledOnce); 206 | t.deepEqual(digest.args[0], [{name: algorithm.toUpperCase()}, encodedKey]); 207 | 208 | digest.restore(); 209 | 210 | t.is(result, digested); 211 | }); 212 | 213 | test.serial('if subtleEncrypt will encrypt the crypto key', async (t) => { 214 | const encrypted = 'encrypted'; 215 | 216 | const randomValues = new Uint8Array(12); 217 | 218 | const getRandomValues = sinon.stub(constants.CRYPTO, 'getRandomValues').returns(randomValues); 219 | const encrypt = sinon.stub(constants.CRYPTO_SUBTLE, 'encrypt').resolves(encrypted); 220 | 221 | const cryptoKey = 'cryptoKey'; 222 | const text = 'text'; 223 | const options = { 224 | charset: 'utf-8', 225 | ivSize: 12, 226 | }; 227 | 228 | const result = await utils.subtleEncrypt(cryptoKey, text, options); 229 | 230 | t.true(getRandomValues.calledOnce); 231 | t.deepEqual(getRandomValues.args[0], [utils.getTypedArray(options.ivSize)]); 232 | 233 | t.true(encrypt.calledOnce); 234 | t.deepEqual(encrypt.args[0], [ 235 | { 236 | iv: randomValues, 237 | name: constants.ALGORITHM, 238 | tagLength: 128, 239 | }, 240 | cryptoKey, 241 | new TextEncoder(options.charset).encode(text), 242 | ]); 243 | 244 | t.deepEqual(result, { 245 | buffer: encrypted, 246 | iv: randomValues, 247 | }); 248 | 249 | encrypt.restore(); 250 | }); 251 | 252 | test.serial('if subtleGenerateKey will generate the key with the length passed', async (t) => { 253 | const options = { 254 | keyLength: 128, 255 | }; 256 | 257 | const generated = 'generated'; 258 | 259 | const generateKey = sinon.stub(constants.CRYPTO_SUBTLE, 'generateKey').resolves(generated); 260 | 261 | const result = await utils.subtleGenerateKey(options); 262 | 263 | t.true(generateKey.calledOnce); 264 | t.deepEqual(generateKey.args[0], [ 265 | { 266 | length: options.keyLength, 267 | name: constants.ALGORITHM, 268 | }, 269 | false, 270 | ['decrypt', 'encrypt'], 271 | ]); 272 | 273 | generateKey.restore(); 274 | 275 | t.is(result, generated); 276 | }); 277 | 278 | test.serial('if subtleImportKey will import the key with the hash passed', async (t) => { 279 | const hash = 'hash'; 280 | const type = 'type'; 281 | 282 | const imported = 'imported'; 283 | 284 | const importKey = sinon.stub(constants.CRYPTO_SUBTLE, 'importKey').resolves(imported); 285 | 286 | const result = await utils.subtleImportKey(hash, type); 287 | 288 | t.true(importKey.calledOnce); 289 | t.deepEqual(importKey.args[0], ['raw', hash, constants.ALGORITHM, false, [type]]); 290 | 291 | importKey.restore(); 292 | 293 | t.is(result, imported); 294 | }); 295 | 296 | test.serial('if getCryptoHash will return the has to use in crypto.subtle when the secret is a string', async (t) => { 297 | const secret = 'secret'; 298 | const algorithm = 'SHA-256'; 299 | const options = { 300 | charset: 'utf-8', 301 | stringify: JSON.stringify, 302 | }; 303 | 304 | const result = await utils.getCryptoHash(secret, algorithm, options); 305 | 306 | const encodedKey = await utils.subtleDigest(new TextEncoder(options.charset).encode(secret), algorithm); 307 | 308 | t.deepEqual(result, utils.getTypedArray(encodedKey)); 309 | }); 310 | 311 | test.serial( 312 | 'if getCryptoHash will return the has to use in crypto.subtle when the secret is an ArrayBuffer', 313 | async (t) => { 314 | const secret = new ArrayBuffer(32); 315 | const algorithm = 'SHA-256'; 316 | const options = { 317 | charset: 'utf-8', 318 | stringify: JSON.stringify, 319 | }; 320 | 321 | const result = await utils.getCryptoHash(secret, algorithm, options); 322 | 323 | const encodedKey = await utils.subtleDigest(utils.getTypedArray(secret), algorithm); 324 | 325 | t.deepEqual(result, utils.getTypedArray(encodedKey)); 326 | } 327 | ); 328 | 329 | test.serial( 330 | 'if getCryptoHash will return the has to use in crypto.subtle when the secret is a TypedArray', 331 | async (t) => { 332 | const secret = new Uint8Array(12); 333 | const algorithm = 'SHA-256'; 334 | const options = { 335 | charset: 'utf-8', 336 | stringify: JSON.stringify, 337 | }; 338 | 339 | const result = await utils.getCryptoHash(secret, algorithm, options); 340 | 341 | const encodedKey = await utils.subtleDigest(secret, algorithm); 342 | 343 | t.deepEqual(result, utils.getTypedArray(encodedKey)); 344 | } 345 | ); 346 | 347 | test.serial('if getCryptoKey will resolve the secret passed if a CryptoKey', async (t) => { 348 | const type = 'encrypt'; 349 | const options = { 350 | stringify: sinon.stub().callsFake(JSON.stringify), 351 | }; 352 | const secret = await window.crypto.subtle.generateKey( 353 | { 354 | length: 128, 355 | name: 'AES-GCM', 356 | }, 357 | false, 358 | [type] 359 | ); 360 | 361 | const result = await utils.getCryptoKey(secret, type, options); 362 | 363 | t.is(result, secret); 364 | }); 365 | 366 | test.serial('if getCryptoKey will generate a new key if the secret passed is not a CryptoKey', async (t) => { 367 | const type = 'encrypt'; 368 | const options = { 369 | stringify: sinon.stub().callsFake(JSON.stringify), 370 | }; 371 | const secret = 'secret'; 372 | 373 | const result = await utils.getCryptoKey(secret, type, options); 374 | 375 | t.not(result, secret); 376 | 377 | const {algorithm, ...restOfResult} = result; 378 | 379 | t.deepEqual(restOfResult, { 380 | extractable: false, 381 | type: 'secret', 382 | usages: [type], 383 | }); 384 | 385 | t.deepEqual( 386 | {...algorithm}, 387 | { 388 | length: 256, 389 | name: 'AES-GCM', 390 | } 391 | ); 392 | }); 393 | 394 | test('if rejectsAttempt will return a promise rejection with the constructed message', async (t) => { 395 | const value = 'value'; 396 | const action = 'action'; 397 | 398 | try { 399 | await utils.rejectsAttempt(value, action); 400 | } catch (error) { 401 | t.true(error instanceof ReferenceError); 402 | t.is(error.message, `The ${value} must be ${action}.`); 403 | } 404 | }); 405 | 406 | test('if throwsProcessing will throw an error with the specific message', (t) => { 407 | const type = 'type'; 408 | 409 | const throws = utils.throwsProcessing(type); 410 | 411 | const parentError = new Error('boom'); 412 | 413 | try { 414 | throws(parentError); 415 | } catch (error) { 416 | t.true(error instanceof parentError.constructor); 417 | t.is(error.message, `Could not ${type} this value.`); 418 | } 419 | }); 420 | -------------------------------------------------------------------------------- /webpack/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const path = require('path'); 5 | 6 | const defaultConfig = require('./webpack.config'); 7 | 8 | const ROOT = path.resolve(__dirname, '..'); 9 | 10 | module.exports = Object.assign({}, defaultConfig, { 11 | devServer: { 12 | contentBase: './dist', 13 | inline: true, 14 | port: 3000, 15 | stats: { 16 | assets: false, 17 | chunkModules: false, 18 | chunks: true, 19 | colors: true, 20 | hash: false, 21 | timings: true, 22 | version: false, 23 | }, 24 | }, 25 | 26 | entry: path.join(ROOT, 'DEV_ONLY/index.js'), 27 | 28 | externals: undefined, 29 | 30 | module: Object.assign({}, defaultConfig.module, { 31 | rules: defaultConfig.module.rules.map((rule) => { 32 | if (rule.loader === 'eslint-loader') { 33 | return Object.assign({}, rule, { 34 | options: Object.assign({}, rule.options, { 35 | emitError: undefined, 36 | failOnWarning: false, 37 | }), 38 | }); 39 | } 40 | 41 | return rule; 42 | }), 43 | }), 44 | 45 | node: { 46 | fs: 'empty', 47 | }, 48 | 49 | plugins: [...defaultConfig.plugins, new HtmlWebpackPlugin()], 50 | }); 51 | -------------------------------------------------------------------------------- /webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | const path = require('path'); 4 | const webpack = require('webpack'); 5 | 6 | const ROOT = path.resolve(__dirname, '..'); 7 | 8 | module.exports = { 9 | devtool: '#source-map', 10 | 11 | entry: path.join(ROOT, 'src/index.js'), 12 | 13 | mode: 'development', 14 | 15 | module: { 16 | rules: [ 17 | { 18 | enforce: 'pre', 19 | include: [path.resolve(ROOT, 'src')], 20 | loader: 'eslint-loader', 21 | options: { 22 | emitError: true, 23 | failOnError: true, 24 | failOnWarning: true, 25 | formatter: require('eslint-friendly-formatter'), 26 | }, 27 | test: /\.js$/, 28 | }, 29 | { 30 | include: [path.resolve(ROOT, 'src'), /DEV_ONLY/], 31 | loader: 'babel-loader', 32 | test: /\.js$/, 33 | }, 34 | ], 35 | }, 36 | 37 | output: { 38 | filename: 'krip.js', 39 | library: 'krip', 40 | libraryTarget: 'umd', 41 | path: path.resolve(ROOT, 'dist'), 42 | umdNamedDefine: true, 43 | }, 44 | 45 | plugins: [new webpack.EnvironmentPlugin(['NODE_ENV'])], 46 | }; 47 | -------------------------------------------------------------------------------- /webpack/webpack.config.minified.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | const webpack = require('webpack'); 4 | const OptimizeJsPlugin = require('optimize-js-plugin'); 5 | 6 | const defaultConfig = require('./webpack.config'); 7 | 8 | module.exports = Object.assign({}, defaultConfig, { 9 | devtool: undefined, 10 | 11 | mode: 'production', 12 | 13 | output: Object.assign({}, defaultConfig.output, {filename: 'krip.min.js'}), 14 | 15 | plugins: defaultConfig.plugins.concat([ 16 | new webpack.LoaderOptionsPlugin({ 17 | debug: false, 18 | minimize: true, 19 | }), 20 | new webpack.optimize.OccurrenceOrderPlugin(), 21 | new OptimizeJsPlugin({sourceMap: false}), 22 | ]), 23 | }); 24 | --------------------------------------------------------------------------------