-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
90 lines (77 loc) · 2.93 KB
/
index.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
/*
Copyright 2020, 2021 Maharshi Mukherjee
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { promisify } = require('util')
const { pbkdf2 } = require('crypto')
const tweetnacl = require('tweetnacl')
const msgpackr = require('msgpackr')
const toDate = require('date-fns/toDate')
const isAfter = require('date-fns/isAfter')
const sub = require('date-fns/sub')
const isString = require('lodash.isstring')
const omit = require('lodash.omit')
const pbkdf = promisify(pbkdf2)
class BetterAuthError extends Error {}
class InvalidDataError extends BetterAuthError {}
exports.BetterAuthError = BetterAuthError
exports.InvalidDataError = InvalidDataError
exports.generateKeyPairFromPW = async function generateKeyPairFromPW (pw, salt, {
iterations = 10e5,
hashAlgo = 'sha512'
} = {}) {
const {
secretKey,
publicKey
} = await tweetnacl.sign.keyPair.fromSeed(await pbkdf(pw, salt, iterations, 32, hashAlgo))
return {
secretKey: Buffer.from(secretKey),
publicKey: Buffer.from(publicKey)
}
}
exports.generateRandomKP = function generateRandomKP () {
return tweetnacl.sign.keyPair()
}
/*
* Verifies the data that it indeed comes from key and the timestamp is within the past 5 minutes
* @param {Object} data The user-supplied password
* @returns {Object} The verified data
*/
exports.verifyData = function verifyData (data, key) {
if (!Number.isFinite(data.timestamp)) {
throw new InvalidDataError('Data has no timestamp')
}
if (!isString(data._sig) && data._sig.length !== 88) {
throw new InvalidDataError('Data has no or invalid signature')
}
const fiveminuteago = sub(new Date(), {
minutes: 5
})
// If signed timestamp is not within the past 5 minutes, throw error
if (!isAfter(toDate(data.timestamp), fiveminuteago)) {
throw new InvalidDataError('Data has expired signature')
}
const datawithoutsig = msgpackr.encode(omit(data, '_sig'))
const sig = Buffer.from(data._sig, 'base64')
return tweetnacl.sign.detached.verify(datawithoutsig, sig, key)
}
/*
* @param {Object} data Supplied data.
* @param {Buffer} key Supplied key
*/
exports.signObject = function signObject (data, key) {
data.timestamp = Date.now()
const datawithoutsig = msgpackr.encode(data)
const sig = tweetnacl.sign.detached(datawithoutsig, key)
data._sig = Buffer.from(sig).toString('base64')
return data
}