-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathcommon.js
106 lines (93 loc) · 2.81 KB
/
common.js
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
'use strict';
const crypto = require('node:crypto');
const SCRYPT_PARAMS = { N: 32768, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
const SCRYPT_PREFIX = '$scrypt$N=32768,r=8,p=1,maxmem=67108864$';
const serializeHash = (hash, salt) => {
const saltString = salt.toString('base64').split('=')[0];
const hashString = hash.toString('base64').split('=')[0];
return `${SCRYPT_PREFIX}${saltString}$${hashString}`;
};
const parseOptions = (options) => {
const values = [];
const items = options.split(',');
for (const item of items) {
const [key, val] = item.split('=');
values.push([key, Number(val)]);
}
return Object.fromEntries(values);
};
const deserializeHash = (phcString) => {
const [, name, options, salt64, hash64] = phcString.split('$');
if (name !== 'scrypt') {
throw new Error('Node.js crypto module only supports scrypt');
}
const params = parseOptions(options);
const salt = Buffer.from(salt64, 'base64');
const hash = Buffer.from(hash64, 'base64');
return { params, salt, hash };
};
const SALT_LEN = 32;
const KEY_LEN = 64;
const hashPassword = (password) =>
new Promise((resolve, reject) => {
crypto.randomBytes(SALT_LEN, (err, salt) => {
if (err) {
reject(err);
return;
}
crypto.scrypt(password, salt, KEY_LEN, SCRYPT_PARAMS, (err, hash) => {
if (err) {
reject(err);
return;
}
resolve(serializeHash(hash, salt));
});
});
});
const validatePassword = (password, serHash) => {
const { params, salt, hash } = deserializeHash(serHash);
return new Promise((resolve, reject) => {
const callback = (err, hashedPassword) => {
if (err) {
reject(err);
return;
}
resolve(crypto.timingSafeEqual(hashedPassword, hash));
};
crypto.scrypt(password, salt, hash.length, params, callback);
});
};
const jsonParse = (buffer) => {
if (buffer.length === 0) return null;
try {
return JSON.parse(buffer);
} catch {
return null;
}
};
const receiveBody = async (req) => {
const buffers = [];
for await (const chunk of req) buffers.push(chunk);
return Buffer.concat(buffers).toString();
};
const CRC_LEN = 4;
const generateToken = (secret, characters, tokenLength) => {
const length = tokenLength - CRC_LEN;
if (length < 0 || secret === '' || characters === '') return '';
const base = characters.length;
const uint8 = new Uint8Array(length);
for (let i = 0; i < length; i++) {
const index = crypto.randomInt(0, base);
uint8[i] = characters.charCodeAt(index);
}
const key = String.fromCharCode.apply(null, uint8);
const md5 = crypto.createHash('md5').update(key + secret);
return key + md5.digest('hex').substring(0, CRC_LEN);
};
module.exports = Object.freeze({
hashPassword,
validatePassword,
jsonParse,
receiveBody,
generateToken,
});