Passed
Push — master ( 6f3ec2...7ccabd )
by Emil
02:59
created

v2/models/auth.js   A

Complexity

Total Complexity 41
Complexity/F 2.56

Size

Lines of Code 303
Function Count 16

Duplication

Duplicated Lines 151
Ratio 49.83 %

Test Coverage

Coverage 81.63%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
eloc 186
nc 2
dl 151
loc 303
ccs 80
cts 98
cp 0.8163
crap 0
rs 9.1199
c 1
b 0
f 0
wmc 41
mnd 2
bc 42
fnc 16
bpm 2.625
cpm 2.5625
noi 8

7 Functions

Rating   Name   Duplication   Size   Complexity  
A auth.getUniqueAPIKey 0 34 1
A auth.register 34 51 3
A auth.checkToken 31 35 2
A auth.getNewAPIKey 0 29 3
A auth.isValidAPIKey 25 27 1
A auth.checkAPIKey 0 19 5
B auth.login 61 81 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like v2/models/auth.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1 1
const db = require("../db/database.js");
2 1
const hat = require("hat");
3 1
const validator = require("email-validator");
4
5 1
const bcrypt = require('bcrypt');
6 1
const jwt = require('jsonwebtoken');
7
8
let config;
9
10 1
try {
11 1
    config = require('../../config/config.json');
12
} catch (error) {
13 1
    console.error(error);
14
}
15
16 2
const jwtSecret = process.env.JWT_SECRET || config.secret;
17
18 1
const auth = {
19
    checkAPIKey: function (req, res, next) {
20 108
        if ( req.path == '/') {
21 1
            return next();
22
        }
23
24 107
        if ( req.path == '/v2') {
25
            return next();
26
        }
27
28 107
        if ( req.path == '/auth/api_key') {
29 1
            return next();
30
        }
31
32 106
        if ( req.path == '/auth/api_key/confirmation') {
33 11
            return next();
34
        }
35
36 95
        auth.isValidAPIKey(req.query.api_key || req.body.api_key, next, req.path, res);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
37
    },
38
39
    isValidAPIKey: function(apiKey, next, path, res) {
40 95 View Code Duplication
        db.get("SELECT email FROM apikeys WHERE key = ?", apiKey, (err, row) => {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
41 95
            if (err) {
42
                return res.status(500).json({
43
                    errors: {
44
                        status: 500,
45
                        source: path,
46
                        title: "Database error",
47
                        detail: err.message
48
                    }
49
                });
50
            }
51
52 95
            if (row !== undefined) {
53 86
                return next();
54
            }
55
56 9
            return res.status(401).json({
57
                errors: {
58
                    status: 401,
59
                    source: path,
60
                    title: "Valid API key",
61
                    detail: "No valid API key provided."
62
                }
63
            });
64
        });
65
    },
66
67
    getNewAPIKey: function(res, email) {
68 9
        let data = {
69
            apiKey: ""
70
        };
71
72 9
        if (email === undefined || !validator.validate(email)) {
73 1
            data.message = "A valid email address is required to obtain an API key.";
74 1
            data.email = email;
75
76 1
            return res.render("api_key/form", data);
77
        }
78
79 8
        db.get("SELECT email, key FROM apikeys WHERE email = ?", email, (err, row) => {
80 8
            if (err) {
81
                data.message = "Database error: " + err.message;
82
                data.email = email;
83
84
                return res.render("api_key/form", data);
85
            }
86
87 8
            if (row !== undefined) {
88
                data.apiKey = row.key;
89
90
                return res.render("api_key/confirmation", data);
91
            }
92
93 8
            return auth.getUniqueAPIKey(res, email);
94
        });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
95
    },
96
97
    getUniqueAPIKey: function(res, email) {
98 8
        const apiKey = hat();
99 8
        let data = {
100
            apiKey: ""
101
        };
102
103 8
        db.get("SELECT key FROM apikeys WHERE key = ?", apiKey, (err, row) => {
104 8
            if (err) {
105
                data.message = "Database error: " + err.message;
106
                data.email = email;
107
108
                return res.render("api_key/form", data);
109
            }
110
111 8
            if (row === undefined) {
112 8
                db.run("INSERT INTO apikeys (key, email) VALUES (?, ?)",
113
                    apiKey,
114
                    email, (err) => {
115 8
                        if (err) {
116
                            data.message = "Database error: " + err.message;
117
                            data.email = email;
118
119
                            return res.render("api_key/form", data);
120
                        }
121
122 8
                        data.apiKey = apiKey;
123
124 8
                        return res.render("api_key/confirmation", data);
125
                    });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
126
            } else {
127
                return auth.getUniqueAPIKey(res, email);
128
            }
129
        });
130
    },
131
132
    login: function(res, body) {
133 6
        const email = body.email;
134 6
        const password = body.password;
135 6
        const apiKey = body.api_key;
136
137 6
        if (!email || !password) {
138 2
            return res.status(401).json({
139
                errors: {
140
                    status: 401,
141
                    source: "/login",
142
                    title: "Email or password missing",
143
                    detail: "Email or password missing in request"
144
                }
145
            });
146
        }
147
148 4
        db.get("SELECT * FROM users WHERE apiKey = ? AND email = ?",
149
            apiKey,
150
            email,
151 View Code Duplication
            (err, rows) => {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
152 4
                if (err) {
153
                    return res.status(500).json({
154
                        errors: {
155
                            status: 500,
156
                            source: "/login",
157
                            title: "Database error",
158
                            detail: err.message
159
                        }
160
                    });
161
                }
162
163 4
                if (rows === undefined) {
164 1
                    return res.status(401).json({
165
                        errors: {
166
                            status: 401,
167
                            source: "/login",
168
                            title: "User not found",
169
                            detail: "User with provided email not found."
170
                        }
171
                    });
172
                }
173
174 3
                const user = rows;
175
176 3
                bcrypt.compare(password, user.password, (err, result) => {
177 3
                    if (err) {
178
                        return res.status(500).json({
179
                            errors: {
180
                                status: 500,
181
                                source: "/login",
182
                                title: "bcrypt error",
183
                                detail: "bcrypt error"
184
                            }
185
                        });
186
                    }
187
188 3
                    if (result) {
189 2
                        let payload = { api_key: user.apiKey, email: user.email };
190 2
                        let jwtToken = jwt.sign(payload, jwtSecret, { expiresIn: '24h' });
191
192 2
                        return res.json({
193
                            data: {
194
                                type: "success",
195
                                message: "User logged in",
196
                                user: payload,
197
                                token: jwtToken
198
                            }
199
                        });
200
                    }
201
202 1
                    return res.status(401).json({
203
                        errors: {
204
                            status: 401,
205
                            source: "/login",
206
                            title: "Wrong password",
207
                            detail: "Password is incorrect."
208
                        }
209
                    });
210
                });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
211
            });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
212
    },
213
214
    register: function(res, body) {
215 5
        const email = body.email;
216 5
        const password = body.password;
217 5
        const apiKey = body.api_key;
218
219 5
        if (!email || !password) {
220 2
            return res.status(401).json({
221
                errors: {
222
                    status: 401,
223
                    source: "/register",
224
                    title: "Email or password missing",
225
                    detail: "Email or password missing in request"
226
                }
227
            });
228
        }
229
230 3 View Code Duplication
        bcrypt.hash(password, 10, function(err, hash) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
231 3
            if (err) {
232
                return res.status(500).json({
233
                    errors: {
234
                        status: 500,
235
                        source: "/register",
236
                        title: "bcrypt error",
237
                        detail: "bcrypt error"
238
                    }
239
                });
240
            }
241
242 3
            db.run("INSERT INTO users (apiKey, email, password) VALUES (?, ?, ?)",
243
                apiKey,
244
                email,
245
                hash, (err) => {
246 3
                    if (err) {
247 1
                        return res.status(500).json({
248
                            errors: {
249
                                status: 500,
250
                                source: "/register",
251
                                title: "Database error",
252
                                detail: err.message
253
                            }
254
                        });
255
                    }
256
257 2
                    return res.status(201).json({
258
                        data: {
259
                            message: "User successfully registered."
260
                        }
261
                    });
262
                });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
263
        });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
264
    },
265
266
    checkToken: function(req, res, next) {
267 14
        var token = req.headers['x-access-token'];
268
269 14 View Code Duplication
        if (token) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
270 11
            jwt.verify(token, jwtSecret, function(err, decoded) {
271 11
                if (err) {
272
                    return res.status(500).json({
273
                        errors: {
274
                            status: 500,
275
                            source: req.path,
276
                            title: "Failed authentication",
277
                            detail: err.message
278
                        }
279
                    });
280
                }
281
282 11
                req.user = {};
283 11
                req.user.api_key = decoded.api_key;
284 11
                req.user.email = decoded.email;
285
286 11
                next();
287
288 11
                return undefined;
289
            });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
290
        } else {
291 3
            return res.status(401).json({
292
                errors: {
293
                    status: 401,
294
                    source: req.path,
295
                    title: "No token",
296
                    detail: "No token provided in request headers"
297
                }
298
            });
299
        }
300
    }
301
};
302
303
module.exports = auth;
304