OWASP/SSO_Project

View on GitHub
js-backend/utils/user.js

Summary

Maintainability
C
7 hrs
Test Coverage
const pwUtilLib = require("./password.js").PwUtil;

const actionMap = {
    "registration": 0,
    "login": 1,
    "change": 2,
};

class User {
    constructor(dbConnection) {
        this.db = dbConnection;
        this.pwUtil = new pwUtilLib(dbConnection);
    }    
    requestEmailActivation(username, ip, action) {
        return new Promise((resolve, reject) => {
            if(!(action in actionMap)) return reject("Action unknown");
            const actionId = actionMap[action];
        
            this.db.execute("SELECT * FROM users WHERE username = ?", [username], (err, results) => {
                if(err) return reject(err);
                if(results.length && action == "registration") {
                    return reject("Email address already registered");
                } else if(!results.length && action != "registration") {
                    return reject("User unknown");
                }
                
                this.pwUtil.createRandomString(30).then(token => {
                    this.db.execute("INSERT INTO emailConfirm (username, token, request_ip, action) VALUES (?, ?, ?, ?)", [username, token, ip, actionId], err => {
                        if(err) return reject(err);
                        
                        resolve(token);
                    });
                }).catch(reject);
            });
        });
    }
    resolveEmailActivation(token, ip, action, noInvalidate) {
        return new Promise((resolve, reject) => {
            if(!(action in actionMap)) return reject("Action unknown");
            const actionId = actionMap[action];
            
            this.db.execute("SELECT * FROM emailConfirm WHERE token = ? AND action = ?", [token, actionId], (err, results) => {
                if(err) return reject(err);
                if(!results.length) return reject("Token not found");
                const confirmation = results.pop();
            
                if(confirmation.request_ip != ip) {
                    console.warn("Email confirmation IPs do not match:", confirmation.request_ip, ip);
                    return reject("Your IP must stay the same from request to confirmation");
                }
                
                // Invalidate token already
                if(typeof noInvalidate == "undefined" || !noInvalidate) {
                    this.db.execute("DELETE FROM emailConfirm WHERE id = ?", [confirmation.id], () => {});
                }
                
                const nowDate = new Date();
                if(nowDate-confirmation.added > 24*60*60*1000) {
                    return reject("Token expired");
                }
                
                resolve(confirmation);
            });
        });
    }
    manualInvalidateToken(id) {
        return new Promise((resolve, reject) => {
            this.db.execute("DELETE FROM emailConfirm WHERE id = ?", [id], (err) => {
                if(err) return reject(err);
                resolve();
            });
        });
    }
    addUser(username, password) {
        return new Promise((resolve, reject) => {
            if(password == null) {
                this.db.execute("INSERT INTO users (username, created) VALUES (?, NOW())", [username], (err, result) => {
                    if(err) return reject(err);
                    
                    const userId = result.insertId;
                    resolve(userId);
                });
            } else {
                this.pwUtil.checkPassword(null, password).then(() => {
                    this.db.execute("INSERT INTO users (username, created) VALUES (?, NOW())", [username], (err, result) => {
                        if(err) return reject(err);
                        
                        const userId = result.insertId;
                        this.pwUtil.hashPassword(password).then(hash => {
                            this.db.execute("INSERT INTO passwords (userId, password) VALUES(?, ?)", [userId, hash], (err) => {
                                if(err) return reject(err);
                                
                                resolve(userId);
                            });
                        }).catch(reject);
                    });
                }).catch(reject);
            }
        });
    }
    changePassword(userId, newPassword) {
        return new Promise((resolve, reject) => {

            this.pwUtil.hashPassword(newPassword).then(hash => {
                this.db.execute("INSERT INTO passwords (userId, password) VALUES(?, ?)", [userId, hash], (err) => {
                    if(err) return reject(err);
                    
                    resolve(userId);
                });
            }).catch(reject);
        });
    }
    findUserById(userId) {
        return this.findUserByAttribute("id", userId);
    }
    findUserByName(userName) {
        return this.findUserByAttribute("username", userName);
    }
    findUserByAttribute(attributeName, attributeVal) {
        return new Promise((resolve, reject) => {
            this.db.execute("SELECT * FROM users WHERE "+attributeName+" = ?", [attributeVal], (err, results) => {
                if(err) return reject(err);
                if(!results.length) return reject("User not found");
                
                const user = results.pop();
                Promise.all([
                    this.findAuthenticatorByUser(user.id),
                    this.findPasswordByUser(user.id),
                ]).then(userDetails => {
                    const [authenticators, passHash] = userDetails;
                    
                    user.authenticators = authenticators;
                    user.password = passHash;
                    
                    resolve(user);
                }).catch(reject);
            });
        });
    }
    updateLoginTime(userId) {
        return new Promise((resolve, reject) => {
            this.db.execute("UPDATE users SET last_login = NOW() WHERE id = ?", [userId], (err) => {
                if(err) return reject(err);
                resolve();
            });
        });
    }
    createSession(userId) {
        return new Promise((resolve, reject) => {
            this.pwUtil.createRandomString(30).then(token => {
                this.db.execute("INSERT INTO userSessions (userId, token, added) VALUES (?, ?, NOW())", [userId, token], (err, result) => {
                    if(err) return reject(err);
                    resolve(token);
                });
            }).catch(reject);
        });
    }
    validateSession(token) {
        return new Promise((resolve, reject) => {
            if(!token) return reject("Token empty");
            this.db.execute("SELECT * FROM userSessions WHERE token = ?", [token], (err, results) => {
                if(err) return reject(err);
                if(!results.length) return reject("Token not known");
                
                const result = results.pop();
                this.db.execute("UPDATE userSessions SET last_seen = NOW() WHERE id = ?", [result.id], err => {
                    if(err) return reject(err);
                    resolve(result);
                });
            });
        });
    }
    cleanSession(userId, surviveToken) {
        return new Promise((resolve, reject) => {
            this.db.execute("DELETE FROM userSessions WHERE userId = ? AND token != ?", [userId, surviveToken], e => {
                if(e) return reject(e);
                resolve();
            });
        });
    }
    deleteSession(token) {
        return new Promise((resolve, reject) => {
            this.db.execute("DELETE FROM userSessions WHERE id = ?", [token], () => {
                resolve();
            });
        });
    }
    findPasswordByUser(userId) {
        return new Promise((resolve, reject) => {
            this.db.execute("SELECT password FROM passwords WHERE userId = ? ORDER BY created DESC LIMIT 1", [userId], (err, results) => {
                if(err) return reject(err);
                if(!results.length) return resolve(null);
                const passwordObj = results.pop();
                resolve(passwordObj.password);
            });
        });
    }
    addAuthenticator(type, username, label, authenticatorObj) {
        return new Promise((resolve, reject) => {
            const {userCounter, userHandle, userKey} = authenticatorObj;
            
            this.findUserByName(username).then(user => {
                this.db.execute("INSERT INTO authenticators (userId, label, handle, counter, publicKey, type) VALUES (?,?,?,?,?, ?)", [user.id, label, userHandle, userCounter, userKey, type], (err) => {
                    if(err) return reject(err);
                    return resolve();
                });
            }).catch(reject);
        });
    }
    removeAuthenticator(type, userId, handle) {
        return new Promise((resolve, reject) => {
            this.db.execute("DELETE FROM authenticators WHERE userId = ? AND handle = ? and type = ?", [userId, handle, type], e => {
                if(e) return reject(e);
                resolve();
            });
        });
    }
    updateAuthenticatorCounter(type, handle, newCounter) {
        return new Promise((resolve, reject) => {
            this.db.execute("UPDATE authenticators SET counter = ? WHERE handle = ? AND type = ?", [newCounter, handle, type], (err) => {
                if(err) return reject(err);
                return resolve();
            });
        });
    }
    findAuthenticatorByUser(userId) {
        return new Promise((resolve, reject) => {
            this.db.execute("SELECT * FROM authenticators WHERE userId = ?", [userId], (err, results) => {
                if(err) return reject(err);
                
                const handleList = [];
                for(let i=0;i<results.length;i++) {
                    const item = results[i];
                    handleList.push({
                        label: item.label,
                        userHandle: item.handle, 
                        userCounter: item.counter, 
                        userKey: item.publicKey,
                        type: item.type,
                    });
                }
                
                return resolve(handleList);
            });
        });
    }
}

exports.User = User;