-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.js
156 lines (139 loc) · 4.42 KB
/
auth.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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//auth.js
const datefns = require('date-fns');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
module.exports = {config, checkToken, generateToken, getJWT, verifyJWT};
//crypto setup
const configuration = {
cryptoKey: "default_cryptography_key",
JWTexpiration: '14d'
};
const algorithm = 'aes-192-cbc';
const salt = "determi__nate"; //crypto.randomBytes(16);
var key = crypto.scryptSync(configuration.cryptoKey, salt, 24);
/**
* sets configuration options:
* @param {Object} options key value pair options
* @returns {boolean} true if all required options have been set, false otherwise
*/
function config(options){
for(let i in options)
if (configuration.hasOwnProperty(i))
configuration[i] = options[i];
return checkConfig();
}
/**
* checks to verify whether all necessary configuration settings have been set
* @returns {boolean}
*/
function checkConfig(){
if(configuration.cryptoKey=="default_cryptography_key")
throw new Error("WARNING: Default cryptography key present. Change with config() in production code.")
else
key = crypto.scryptSync(configuration.cryptoKey, salt, 24);
for(let i in configuration)
if(configuration[i]==null)
return false;
return true;
}
/**
* accepts a token and attempts to decrypt it and
* verify that it was issued less than x minutes ago
* if successful, resolves the username of the user
* otherwise, rejects with error message
* @param {string} token
* @param {number} minutes
* @returns {string} decrypted email
*/
function checkToken(token, minutes=5){
var decrypted = decryptToken(token);
if(decrypted && withinMinutes(decrypted.time, minutes))
return {email: decrypted.email, level: decrypted.level};
else
return false;
}
/**
* checks if time is within X minutes of the current time
* AND is not after the current time
* @param {string} time time ISOstring
* @param {number} min number of minutes
* @returns {boolean}
*/
function withinMinutes(time, min){
let now = new Date();
time = new Date(time);
let expiration = datefns.addMinutes(time, min); //calculate expiration time
if( expiration >= now && time <= now) //if expiration is after now, and the time is in the past
return true;
else{
console.log('auth token too old');
return false;
}
}
/**
* generates an encrypted JSON with email and current time
* @param {string} email
* @returns {string} encrypted token with the user's email and current time
*/
function generateToken(email, level){
if(!(email.includes('@') && email.includes ('.'))) //check if we have an email address
return false;
checkConfig();
const iv = crypto.randomBytes(16).toString('base64').slice(0,16);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let toEncrypt = JSON.stringify({email: email, level: level, time: new Date()});
let encrypted = cipher.update(toEncrypt, 'utf8', 'base64');
encrypted += cipher.final('base64');
encrypted += "_"+iv;
return encrypted;
}
/**
* decrypts and parses token
* @param {string} token
*/
function decryptToken(data){
var [token, iv] = data.split("_");
try{
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.update(token, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
catch(e){
return false;
}
}
/**
* signs and returns a jsonwebtoken with user and auth level information
* @param {string} email email of user
* @param {string} level authorization level of user
* @returns {string} jsonwebtoken
*/
function getJWT(email, level="user"){
return(
jwt.sign({
user: email,
level: level
},
configuration.cryptoKey, { expiresIn: configuration.JWTexpiration })
);
}
/**
* checks validity of token and returns user and auth level
* @param {string} token jwt to parse and check
* @returns {promise}
* @resolves object with email and auth level information
* @rejects on error
*/
async function verifyJWT(token){
return new Promise((resolve, reject)=>{
jwt.verify(token, configuration.cryptoKey, (err, result)=>{
if(err)
reject(err.message);
else if(!result)
reject("jwt error")
else
resolve({user: result.user, level: result.level, expiresIn: result.expiresIn});
}).catch((err)=>reject(err.message));
});
}