v2/models/auth.js   F
last analyzed

Complexity

Total Complexity 62
Complexity/F 2.3

Size

Lines of Code 430
Function Count 27

Duplication

Duplicated Lines 151
Ratio 35.12 %

Test Coverage

Coverage 77.86%

Importance

Changes 0
Metric Value
wmc 62
eloc 264
mnd 35
bc 35
fnc 27
dl 151
loc 430
ccs 109
cts 140
cp 0.7786
rs 3.44
bpm 1.2962
cpm 2.2961
noi 8
c 0
b 0
f 0

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('bcryptjs');
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 146
        if ( req.path == '/') {
21 1
            return next();
22
        }
23
24 145
        if ( req.path == '/v2') {
25
            return next();
26
        }
27
28 145
        if ( req.path == '/auth/api_key') {
29 1
            return next();
30
        }
31
32 144
        if ( req.path == '/auth/api_key/confirmation') {
33 13
            return next();
34
        }
35
36 131
        if ( req.path == '/auth/api_key/deregister') {
37 4
            return next();
38
        }
39
40 127
        if ( req.path == '/products/everything') {
41 2
            return next();
42
        }
43
44 125
        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...
45
    },
46
47
    isValidAPIKey: function(apiKey, next, path, res) {
48 125 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...
49 125
            if (err) {
50
                return res.status(500).json({
51
                    errors: {
52
                        status: 500,
53
                        source: path,
54
                        title: "Database error",
55
                        detail: err.message
56
                    }
57
                });
58
            }
59
60 125
            if (row !== undefined) {
61 114
                return next();
62
            }
63
64 11
            return res.status(401).json({
65
                errors: {
66
                    status: 401,
67
                    source: path,
68
                    title: "Valid API key",
69
                    detail: "No valid API key provided."
70
                }
71
            });
72
        });
73
    },
74
75
    getNewAPIKey: function(res, email) {
76 11
        let data = {
77
            apiKey: ""
78
        };
79
80 11
        if (email === undefined || !validator.validate(email)) {
81 1
            data.message = "A valid email address is required to obtain an API key.";
82 1
            data.email = email;
83
84 1
            return res.render("api_key/form", data);
85
        }
86
87 10
        db.get("SELECT email, key FROM apikeys WHERE email = ?", email, (err, row) => {
88 10
            if (err) {
89
                data.message = "Database error: " + err.message;
90
                data.email = email;
91
92
                return res.render("api_key/form", data);
93
            }
94
95 10
            if (row !== undefined) {
96
                data.apiKey = row.key;
97
98
                return res.render("api_key/confirmation", data);
99
            }
100
101 10
            return auth.getUniqueAPIKey(res, email);
102
        });
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...
103
    },
104
105
    getUniqueAPIKey: function(res, email) {
106 10
        const apiKey = hat();
107 10
        let data = {
108
            apiKey: ""
109
        };
110
111 10
        db.get("SELECT key FROM apikeys WHERE key = ?", apiKey, (err, row) => {
112 10
            if (err) {
113
                data.message = "Database error: " + err.message;
114
                data.email = email;
115
116
                return res.render("api_key/form", data);
117
            }
118
119 10
            if (row === undefined) {
120 10
                db.run("INSERT INTO apikeys (key, email) VALUES (?, ?)",
121
                    apiKey,
122
                    email, (err) => {
123 10
                        if (err) {
124
                            data.message = "Database error: " + err.message;
125
                            data.email = email;
126
127
                            return res.render("api_key/form", data);
128
                        }
129
130 10
                        data.apiKey = apiKey;
131
132 10
                        return res.render("api_key/confirmation", data);
133
                    });
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...
134
            } else {
135
                return auth.getUniqueAPIKey(res, email);
136
            }
137
        });
138
    },
139
140
    deregister: function(res, body) {
141 1
        const email = body.email;
142 1
        const apiKey = body.apikey;
143
144 1
        db.get("SELECT key FROM apikeys WHERE key = ? and email = ?",
145
            apiKey,
146
            email,
147
            (err, row) => {
148 2
                if (err) {
149
                    let data = {
150
                        message: "Database error: " + err.message,
151
                        email: email,
152
                        apikey: apiKey
153
                    };
154
155
                    return res.render("api_key/deregister", data);
156
                }
157
158 2
                if (row === undefined) {
159
                    let data = {
160
                        message: "The E-mail and API-key combination does not exist.",
161
                        email: email,
162
                        apikey: apiKey
163
                    };
164
165
                    return res.render("api_key/deregister", data);
166
                }
167
168 1
                return auth.deleteData(res, apiKey, email);
169
            });
170
    },
171
172
    deleteData: function(res, apiKey, email) {
173 1
        let errorMessages = [];
174
175 1
        db.run("DELETE FROM apikeys WHERE key = ?",
176
            apiKey,
177
            (err) => {
178 2
                if (err) {
179
                    errorMessages.push(err);
180
                }
181
182 1
                db.run("DELETE FROM deliveries WHERE apiKey = ?",
183
                    apiKey,
184
                    (err) => {
185 2
                        if (err) {
186
                            errorMessages.push(err);
187
                        }
188
189 1
                        db.run("DELETE FROM invoices WHERE apiKey = ?",
190
                            apiKey,
191
                            (err) => {
192 2
                                if (err) {
193
                                    errorMessages.push(err);
194
                                }
195
196 1
                                db.run("DELETE FROM orders WHERE apiKey = ?",
197
                                    apiKey,
198
                                    (err) => {
199 2
                                        if (err) {
200
                                            errorMessages.push(err);
201
                                        }
202
203 1
                                        db.run("DELETE FROM order_items WHERE apiKey = ?",
204
                                            apiKey,
205
                                            (err) => {
206 2
                                                if (err) {
207
                                                    errorMessages.push(err);
208
                                                }
209
210 1
                                                db.run("DELETE FROM products WHERE apiKey = ?",
211
                                                    apiKey,
212
                                                    (err) => {
213 2
                                                        if (err) {
214
                                                            errorMessages.push(err);
215
                                                        }
216
217 1
                                                        db.run("DELETE FROM users WHERE apiKey = ?",
218
                                                            apiKey,
219
                                                            (err) => {
220 2
                                                                if (err) {
221
                                                                    errorMessages.push(err);
222
                                                                }
223
224 1
                                                                return auth.afterDelete(
225
                                                                    res,
226
                                                                    apiKey,
227
                                                                    email,
228
                                                                    errorMessages
229
                                                                );
230
                                                            });
231
                                                    });
232
                                            });
233
                                    });
234
                            });
235
                    });
236
            });
237
    },
238
239
    afterDelete: function(res, apiKey, email, errorMessages) {
240 2
        if (errorMessages.length > 0) {
241
            let data = {
242
                message: "Could not delete data due to: " +
243
                    errorMessages.join(" | "),
244
                email: email,
245
                apikey: apiKey
246
            };
247
248
            return res.render("api_key/deregister", data);
249
        }
250
251 1
        let data = {
252
            message: "All data has been deleted",
253
            email: ""
254
        };
255
256 1
        return res.render("api_key/form", data);
257
    },
258
259
    login: function(res, body) {
260 7
        const email = body.email;
261 7
        const password = body.password;
262 7
        const apiKey = body.api_key;
263
264 7
        if (!email || !password) {
265 2
            return res.status(401).json({
266
                errors: {
267
                    status: 401,
268
                    source: "/login",
269
                    title: "Email or password missing",
270
                    detail: "Email or password missing in request"
271
                }
272
            });
273
        }
274
275 5
        db.get("SELECT * FROM users WHERE apiKey = ? AND email = ?",
276
            apiKey,
277
            email,
278 View Code Duplication
            (err, rows) => {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
279 5
                if (err) {
280
                    return res.status(500).json({
281
                        errors: {
282
                            status: 500,
283
                            source: "/login",
284
                            title: "Database error",
285
                            detail: err.message
286
                        }
287
                    });
288
                }
289
290 5
                if (rows === undefined) {
291 1
                    return res.status(401).json({
292
                        errors: {
293
                            status: 401,
294
                            source: "/login",
295
                            title: "User not found",
296
                            detail: "User with provided email not found."
297
                        }
298
                    });
299
                }
300
301 4
                const user = rows;
302
303 4
                bcrypt.compare(password, user.password, (err, result) => {
304 4
                    if (err) {
305
                        return res.status(500).json({
306
                            errors: {
307
                                status: 500,
308
                                source: "/login",
309
                                title: "bcrypt error",
310
                                detail: "bcrypt error"
311
                            }
312
                        });
313
                    }
314
315 4
                    if (result) {
316 3
                        let payload = { api_key: user.apiKey, email: user.email };
317 3
                        let jwtToken = jwt.sign(payload, jwtSecret, { expiresIn: '24h' });
318
319 3
                        return res.json({
320
                            data: {
321
                                type: "success",
322
                                message: "User logged in",
323
                                user: payload,
324
                                token: jwtToken
325
                            }
326
                        });
327
                    }
328
329 1
                    return res.status(401).json({
330
                        errors: {
331
                            status: 401,
332
                            source: "/login",
333
                            title: "Wrong password",
334
                            detail: "Password is incorrect."
335
                        }
336
                    });
337
                });
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...
338
            });
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...
339
    },
340
341
    register: function(res, body) {
342 6
        const email = body.email;
343 6
        const password = body.password;
344 6
        const apiKey = body.api_key;
345
346 6
        if (!email || !password) {
347 2
            return res.status(401).json({
348
                errors: {
349
                    status: 401,
350
                    source: "/register",
351
                    title: "Email or password missing",
352
                    detail: "Email or password missing in request"
353
                }
354
            });
355
        }
356
357 4 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...
358 4
            if (err) {
359
                return res.status(500).json({
360
                    errors: {
361
                        status: 500,
362
                        source: "/register",
363
                        title: "bcrypt error",
364
                        detail: "bcrypt error"
365
                    }
366
                });
367
            }
368
369 4
            db.run("INSERT INTO users (apiKey, email, password) VALUES (?, ?, ?)",
370
                apiKey,
371
                email,
372
                hash, (err) => {
373 4
                    if (err) {
374 1
                        return res.status(500).json({
375
                            errors: {
376
                                status: 500,
377
                                source: "/register",
378
                                title: "Database error",
379
                                detail: err.message
380
                            }
381
                        });
382
                    }
383
384 3
                    return res.status(201).json({
385
                        data: {
386
                            message: "User successfully registered."
387
                        }
388
                    });
389
                });
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...
390
        });
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...
391
    },
392
393
    checkToken: function(req, res, next) {
394 20
        var token = req.headers['x-access-token'];
395
396 20 View Code Duplication
        if (token) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
397 17
            jwt.verify(token, jwtSecret, function(err, decoded) {
398 17
                if (err) {
399
                    return res.status(500).json({
400
                        errors: {
401
                            status: 500,
402
                            source: req.path,
403
                            title: "Failed authentication",
404
                            detail: err.message
405
                        }
406
                    });
407
                }
408
409 17
                req.user = {};
410 17
                req.user.api_key = decoded.api_key;
411 17
                req.user.email = decoded.email;
412
413 17
                next();
414
415 17
                return undefined;
416
            });
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...
417
        } else {
418 3
            return res.status(401).json({
419
                errors: {
420
                    status: 401,
421
                    source: req.path,
422
                    title: "No token",
423
                    detail: "No token provided in request headers"
424
                }
425
            });
426
        }
427
    }
428
};
429
430
module.exports = auth;
431