diff --git a/controllers/auth/models.js b/controllers/auth/models.js new file mode 100644 index 0000000..3663271 --- /dev/null +++ b/controllers/auth/models.js @@ -0,0 +1,10 @@ +const model = require('model'); + +var user = new model.BaseModel("auth_user"); +user.addField(new model.SerialField("id")); +user.addField(new model.TextField("email")); +user.addField(new model.PasswordField("password")); +user.addField(new model.SmallIntegerField("resources")); +user.addField(new model.SmallIntegerField("project_id")); + +module.exports = model.createClass(user); diff --git a/controllers/auth/views.js b/controllers/auth/views.js index e810b6b..78d9746 100644 --- a/controllers/auth/views.js +++ b/controllers/auth/views.js @@ -1,3 +1,4 @@ +const User = require('./models.js'); const getUrl = require('create-url').getUrl; module.exports.login = function(req, res, render) { diff --git a/index.js b/index.js index 33e89a4..1eebfc8 100644 --- a/index.js +++ b/index.js @@ -3,13 +3,18 @@ require('app-module-path').addPath(config.BASE_DIR); require('app-module-path').addPath(config.UTILS_DIR); require('app-module-path').addPath(config.CONTROLLERS_DIR); +const repl = require('repl'); const express = require('express'); const pug = require('pug'); const http = require('http'); const path = require('path'); const log = require('log'); +const model = require('model'); -function main() { +// Load models +model.load(config.CONTROLLERS_DIR); + +function startServer() { let app = express(); let http = require('http').Server(app); @@ -69,6 +74,30 @@ function main() { }); } -if (require.main === module) { - main(); +const commands = { + runserver: startServer, + reinitdb: model.reinitialize, + shell: repl.start, +} + +function showHelp() { + console.log("Commands available:"); + for (let command in commands) { + console.log('\t' + command); + } +} + +if (require.main === module) { + if (process.argv.length != 3) { + showHelp(); + return; + } + + let command = process.argv[2]; + if (commands[command] === undefined) { + showHelp(); + return; + } + + commands[command](); } diff --git a/settings/private.js b/settings/private.js index 08736ee..599aedd 100644 --- a/settings/private.js +++ b/settings/private.js @@ -1,3 +1,10 @@ module.exports = { - 'SECRET_KEY': 'gi+u6x&1%*wa8e$)ngeg4v3_h044owr%i8-pao+z(_4-_if%7b' + 'SECRET_KEY': 'gi+u6x&1%*wa8e$)ngeg4v3_h044owr%i8-pao+z(_4-_if%7b', + 'DATABASE': { + 'HOST': 'localhost', + 'PORT': 5432, + 'USERNAME': 'adejs', + 'DBNAME': 'adejs', + 'PASSWORD':'97Cnqw023V1G95fQUJR8H7gwvqUke4', + } }; diff --git a/test.js b/test.js new file mode 100644 index 0000000..3ac7871 --- /dev/null +++ b/test.js @@ -0,0 +1,42 @@ +const config = require('./settings/config.js'); +require('app-module-path').addPath(config.BASE_DIR); +require('app-module-path').addPath(config.UTILS_DIR); +require('app-module-path').addPath(config.CONTROLLERS_DIR); + +const model = require('./utils/model.js'); +const User = require('./controllers/auth/models.js'); +const log = require('./utils/log'); + +model.reinitialize(() => { + + log.debug('Database reinitialized'); + + let user = new User(); + user.email = "toto"; + user.password = "tata"; + user.resources = 23; + user.project_id = 42; + user.save((err, u) => { + + log.debug('New user created'); + + // Test password + User.getById(1, (err, user) => { + if (model.PasswordField.testSync("tata", user.password)) { + log.debug("Password authentication succeed"); + } else { + log.error("Password should have succeeded but failed"); + } + + if (!model.PasswordField.testSync("toto", user.password)) { + log.debug("Password authentication failed as it was supposed to"); + } else { + log.error("Password should have failed but succeded"); + } + + process.exit(0); + + }); + }); + +}); diff --git a/utils/db.js b/utils/db.js new file mode 100644 index 0000000..0197022 --- /dev/null +++ b/utils/db.js @@ -0,0 +1,9 @@ +const db = require('settings/config').DATABASE; + +module.exports = new require('pg').Pool({ + database: db.DBNAME, + user: db.USERNAME, + password: db.PASSWORD, + host: db.HOST, + port: db.PORT, +}); diff --git a/utils/log.js b/utils/log.js index ed9c518..c77670f 100644 --- a/utils/log.js +++ b/utils/log.js @@ -100,6 +100,10 @@ log.pugerror = function(error) { log.write('[PER] ' + new Date() + ' ' + error, log.Color.RED); } +log.error = function(error) { + log.write('[ERR] ' + new Date() + ' ' + error, log.Color.RED); +} + log.warning = function(message) { log.write('[WRN] ' + new Date() + ' ' + message, log.Color.ORANGE); } diff --git a/utils/model.js b/utils/model.js new file mode 100644 index 0000000..4a4df11 --- /dev/null +++ b/utils/model.js @@ -0,0 +1,262 @@ +const fs = require('fs'); +const db = require('db'); +const log = require('log'); +const bc = require('bcryptjs'); + +let models = []; + +module.exports.BaseModel = class { + constructor(name) { + models.push(this); + this.name = name; + this.fields = []; + + } + + addField(field) { + this.fields.push(field); + } + + reinitialize(callback = () => {}) { + log.debug('Reinitializing ' + this.name); + db.query('DROP TABLE IF EXISTS ' + this.name + ' CASCADE') + .then(res => this.createTable(callback)) + .catch(err => log.dberror(err)); + } + + createTable(callback) { + let query = 'CREATE TABLE ' + this.name + '(\n'; + query += this.fields + .map(f => '\t' + f.getCreationString()) + .join(',\n'); + query += ');'; + + log.debug(query); + + db.query(query) + .then(res => { + log.debug("Table " + this.name + " created") + callback(); + }) + .catch(err => log.dberror(err)); + } +} + +module.exports.BaseField = class { + constructor(name) { + this.name = name; + } + + createMutators() { + return { + get: this.createGetter(), + set: this.createSetter(), + }; + } + + createGetter() { + var self = this; + return function() { + return this['_' + self.name].value; + } + } + + createSetter() { + var self = this; + return function(e) { + this['_' + self.name] = { + value: e, + changed: true, + }; + } + } +} + +module.exports.SmallIntegerField = class extends module.exports.BaseField { + constructor(name) { + super(name); + } + + getCreationString() { + return this.name + " SMALLINT"; + } +} + +module.exports.SerialField = class extends module.exports.BaseField { + constructor(name) { + super(name); + } + + getCreationString() { + return this.name + " SERIAL"; + } + +} + +module.exports.TextField = class extends module.exports.BaseField { + constructor(name) { + super(name); + } + + getCreationString() { + return this.name + " TEXT"; + } +} + +module.exports.PasswordField = class extends module.exports.BaseField { + constructor(name) { + super(name); + } + + getCreationString() { + return this.name + ' TEXT'; + } + + + createSetter() { + let self = this; + return function(e) { + this['_' + self.name] = { + value: bc.hashSync(e, 8), + changed: true, + }; + } + + } +} + +module.exports.PasswordField.testSync = function(password, hash) { + return bc.compareSync(password, hash); +} + + +module.exports.createClass = function(model) { + + let ret = function(param) { + this._persisted = false; + for (let field of model.fields) { + this['_' + field.name] = { + value: undefined, + changed: false, + } + Object.defineProperty(this, field.name, field.createMutators()); + } + }; + + ret.getById = function(id, callback) { + db + .query('SELECT * FROM ' + model.name + ' WHERE id=$1;', [id]) + .then(res => { + // Create the instance + let instance = new ret(); + instance._persisted = true; + instance._setFromDbResult(res); + callback(undefined, instance); + }) + .catch(err => callback(err)); + } + + ret.prototype._setFromDbResult = function(res) { + for (let field of model.fields) { + this['_' + field.name] = { + value: res.rows[0][field.name], + changed: false, + }; + } + } + + ret.prototype.save = function(callback = () => {}) { + let fieldsToSave = []; + for (let field of model.fields) { + if (this['_' + field.name].changed) { + fieldsToSave.push({ + field: field, + value: this[field.name], + }); + } + } + + fieldsToSave = fieldsToSave.filter((field) => { + return !(field.field instanceof module.exports.SerialField); + }); + + if (!this._persisted) { + // INSERT INTO + let queryBegin = 'INSERT INTO ' + model.name + '(' + let queryEnd = ' VALUES ('; + queryBegin += fieldsToSave + .map(f => f.field.name) + .join(','); + + queryEnd += fieldsToSave + .map((f, id) => '$' + (id + 1)) + .join(','); + + queryBegin += ')' + queryEnd += ') RETURNING *;' + + let query = queryBegin + queryEnd; + + db + .query(query, fieldsToSave.map(f => f.value)) + .then(res => { + this._setFromDbResult(res); + this._persisted = true; + callback(undefined, this); + }) + .catch(err => callback(err)); + + } else { + // UPDATE FROM + let query = 'UPDATE FROM ' + model.name + ' SET '; + + query += fieldsToSave.map(f => { + return f.field.name + '=' + f.value; + }).join(','); + + query += ') RETURNING *;' + + db + .query(query, fieldsToSave.map(f => f.value)) + .then(res => { + this._setFromDbResult(res); + this._persisted = true; + callback(undefined, this); + }) + .catch(err => callback(err)); + + + } + } + + return ret; +} + +module.exports.reinitialize = function(callback = () => {}) { + let finished = models.map(m => false); + for (let i = 0; i < models.length; i++) { + let model = models[i]; + model.reinitialize(() => { + finished[i] = true; + if (finished.reduce((a, b) => a && b, true)) { + callback(); + } + }); + } +} + +module.exports.load = function(controllersDir) { + + fs.readdirSync(controllersDir).forEach(function(name) { + + let path = controllersDir + '/' + name + '/models.js'; + try { + fs.accessSync(path); + } catch (e) { + return; + } + + require(path); + }); + +}