1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | "use strict";
|
8 |
|
9 |
|
10 |
|
11 | const promiseToolbox = require("promise-toolbox");
|
12 |
|
13 | const asCallback = promiseToolbox.asCallback;
|
14 | const promisifyAll = promiseToolbox.promisifyAll;
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | const makeAsyncWrapper = (function(push) {
|
20 | return function makeAsyncWrapper(fn) {
|
21 | return function asyncWrapper() {
|
22 | const args = [];
|
23 | push.apply(args, arguments);
|
24 | let callback;
|
25 |
|
26 | const n = args.length;
|
27 | if (n && typeof args[n - 1] === "function") {
|
28 | callback = args.pop();
|
29 | }
|
30 |
|
31 | return asCallback.call(
|
32 | new Promise(function(resolve) {
|
33 | resolve(fn.apply(this, args));
|
34 | }),
|
35 | callback
|
36 | );
|
37 | };
|
38 | };
|
39 | })(Array.prototype.push);
|
40 |
|
41 |
|
42 |
|
43 | const algorithmsById = Object.create(null);
|
44 | const algorithmsByName = Object.create(null);
|
45 |
|
46 | const globalOptions = Object.create(null);
|
47 | exports.options = globalOptions;
|
48 |
|
49 | let DEFAULT_ALGO;
|
50 | Object.defineProperty(exports, "DEFAULT_ALGO", {
|
51 | enumerable: true,
|
52 | get: function() {
|
53 | return DEFAULT_ALGO;
|
54 | },
|
55 | });
|
56 |
|
57 | function registerAlgorithm(algo) {
|
58 | const name = algo.name;
|
59 |
|
60 | if (algorithmsByName[name]) {
|
61 | throw new Error("name " + name + " already taken");
|
62 | }
|
63 | algorithmsByName[name] = algo;
|
64 |
|
65 | algo.ids.forEach(function(id) {
|
66 | if (algorithmsById[id]) {
|
67 | throw new Error("id " + id + " already taken");
|
68 | }
|
69 | algorithmsById[id] = algo;
|
70 | });
|
71 |
|
72 | globalOptions[name] = Object.assign(Object.create(null), algo.defaults);
|
73 |
|
74 | if (!DEFAULT_ALGO) {
|
75 | DEFAULT_ALGO = name;
|
76 | }
|
77 | }
|
78 |
|
79 |
|
80 |
|
81 | try {
|
82 | (function(argon2) {
|
83 | registerAlgorithm({
|
84 | name: "argon2",
|
85 | ids: ["argon2d", "argon2i"],
|
86 | defaults: require("argon2").defaults,
|
87 |
|
88 | getOptions: function(hash, info) {
|
89 | let rawOptions = info.options;
|
90 | let options = {};
|
91 |
|
92 |
|
93 | let version;
|
94 | if (rawOptions.slice(0, 2) === "v=") {
|
95 | version = +rawOptions.slice(2);
|
96 |
|
97 | const index = hash.indexOf(rawOptions) + rawOptions.length + 1;
|
98 | rawOptions = hash.slice(index, hash.indexOf("$", index));
|
99 | }
|
100 |
|
101 | rawOptions.split(",").forEach(function(datum) {
|
102 | const index = datum.indexOf("=");
|
103 | if (index === -1) {
|
104 | options[datum] = true;
|
105 | } else {
|
106 | options[datum.slice(0, index)] = datum.slice(index + 1);
|
107 | }
|
108 | });
|
109 |
|
110 | options = {
|
111 | memoryCost: +options.m,
|
112 | parallelism: +options.p,
|
113 | timeCost: +options.t,
|
114 | };
|
115 | if (version !== undefined) {
|
116 | options.version = version;
|
117 | }
|
118 | return options;
|
119 | },
|
120 | hash: argon2.hash,
|
121 | verify: function(password, hash) {
|
122 | return argon2.verify(hash, password);
|
123 | },
|
124 | });
|
125 | })(require("argon2"));
|
126 | } catch (_) {}
|
127 |
|
128 | (function(bcrypt) {
|
129 | registerAlgorithm({
|
130 | name: "bcrypt",
|
131 | ids: ["2", "2a", "2b", "2x", "2y"],
|
132 | defaults: { cost: 10 },
|
133 |
|
134 | getOptions: function(_, info) {
|
135 | return {
|
136 | cost: +info.options,
|
137 | };
|
138 | },
|
139 | hash: function(password, options) {
|
140 | return bcrypt.genSalt(options.cost).then(function(salt) {
|
141 | return bcrypt.hash(password, salt);
|
142 | });
|
143 | },
|
144 | needsRehash: function(_, info) {
|
145 | const id = info.id;
|
146 | if (id !== "2a" && id !== "2b" && id !== "2y") {
|
147 | return true;
|
148 | }
|
149 |
|
150 |
|
151 | },
|
152 | verify: function(password, hash) {
|
153 |
|
154 | if (hash.startsWith("$2y$")) {
|
155 | hash = "$2a$" + hash.slice(4);
|
156 | }
|
157 |
|
158 | return bcrypt.compare(password, hash);
|
159 | },
|
160 | });
|
161 | })(promisifyAll(require("bcryptjs")));
|
162 |
|
163 |
|
164 |
|
165 | const getHashInfo = (function(HASH_RE) {
|
166 | return function getHashInfo(hash) {
|
167 | const matches = hash.match(HASH_RE);
|
168 | if (!matches) {
|
169 | throw new Error("invalid hash " + hash);
|
170 | }
|
171 |
|
172 | return {
|
173 | id: matches[1],
|
174 | options: matches[2],
|
175 | };
|
176 | };
|
177 | })(/^\$([^$]+)\$([^$]*)\$/);
|
178 |
|
179 | function getAlgorithmByName(name) {
|
180 | const algo = algorithmsByName[name];
|
181 | if (!algo) {
|
182 | throw new Error("no available algorithm with name " + name);
|
183 | }
|
184 |
|
185 | return algo;
|
186 | }
|
187 |
|
188 | function getAlgorithmFromId(id) {
|
189 | const algo = algorithmsById[id];
|
190 | if (!algo) {
|
191 | throw new Error("no available algorithm with id " + id);
|
192 | }
|
193 |
|
194 | return algo;
|
195 | }
|
196 |
|
197 | function getAlgorithmFromHash(hash) {
|
198 | return getAlgorithmFromId(getHashInfo(hash).id);
|
199 | }
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 | function hash(password, algo, options) {
|
214 | algo = getAlgorithmByName(algo || DEFAULT_ALGO);
|
215 |
|
216 | return algo.hash(
|
217 | password,
|
218 | Object.assign(Object.create(null), globalOptions[algo.name], options)
|
219 | );
|
220 | }
|
221 | exports.hash = makeAsyncWrapper(hash);
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | function getInfo(hash) {
|
233 | const info = getHashInfo(hash);
|
234 | const algo = getAlgorithmFromId(info.id);
|
235 | info.algorithm = algo.name;
|
236 | info.options = algo.getOptions(hash, info);
|
237 |
|
238 | return info;
|
239 | }
|
240 | exports.getInfo = getInfo;
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | function needsRehash(hash, algo, options) {
|
255 | const info = getInfo(hash);
|
256 |
|
257 | if (info.algorithm !== (algo || DEFAULT_ALGO)) {
|
258 | return true;
|
259 | }
|
260 |
|
261 | const algoNeedsRehash = getAlgorithmFromId(info.id).needsRehash;
|
262 | const result = algoNeedsRehash && algoNeedsRehash(hash, info);
|
263 | if (typeof result === "boolean") {
|
264 | return result;
|
265 | }
|
266 |
|
267 | const expected = Object.assign(
|
268 | Object.create(null),
|
269 | globalOptions[info.algorithm],
|
270 | options
|
271 | );
|
272 | const actual = info.options;
|
273 |
|
274 | for (const prop in actual) {
|
275 | const value = actual[prop];
|
276 | if (typeof value === "number" && value < expected[prop]) {
|
277 | return true;
|
278 | }
|
279 | }
|
280 |
|
281 | return false;
|
282 | }
|
283 | exports.needsRehash = needsRehash;
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 | function verify(password, hash) {
|
295 | return getAlgorithmFromHash(hash).verify(password, hash);
|
296 | }
|
297 | exports.verify = makeAsyncWrapper(verify);
|