Merge pull request #42 from AlexisNP/issues/api-auth

Authentication system has been validated
This commit is contained in:
Joururi
2021-01-19 23:03:58 +01:00
committed by GitHub
32 changed files with 4405 additions and 4094 deletions

View File

@@ -6,7 +6,7 @@ USE auracle;
-- ROLES -- ROLES
CREATE TABLE IF NOT EXISTS `role` ( CREATE TABLE IF NOT EXISTS `role` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL, `name` VARCHAR(255) NOT NULL,
`description` VARCHAR(255) NOT NULL, `description` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`) PRIMARY KEY(`id`)
@@ -14,15 +14,15 @@ CREATE TABLE IF NOT EXISTS `role` (
-- PERMISSIONS -- PERMISSIONS
CREATE TABLE IF NOT EXISTS `permission` ( CREATE TABLE IF NOT EXISTS `permission` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`slug` VARCHAR(255) NOT NULL, `slug` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`) PRIMARY KEY(`id`)
); );
-- USERS -- USERS
CREATE TABLE IF NOT EXISTS `user` ( CREATE TABLE IF NOT EXISTS `user` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`uuid` VARCHAR(36) NOT NULL, `uuid` VARCHAR(36) NOT NULL UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Disciple", `name` VARCHAR(255) NOT NULL DEFAULT "Disciple",
`mail` VARCHAR(255) NOT NULL, `mail` VARCHAR(255) NOT NULL,
`avatar` VARCHAR(255), `avatar` VARCHAR(255),
@@ -37,16 +37,25 @@ CREATE TABLE IF NOT EXISTS `user` (
FOREIGN KEY(`role_id`) REFERENCES role(`id`) FOREIGN KEY(`role_id`) REFERENCES role(`id`)
); );
-- API_TOKENS
CREATE TABLE IF NOT EXISTS `api_token` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`value` VARCHAR(255) NOT NULL,
`user_uuid` VARCHAR(36) NOT NULL UNIQUE,
PRIMARY KEY(`id`),
FOREIGN KEY(`user_uuid`) REFERENCES user(`uuid`)
);
-- SPELLS -- SPELLS
CREATE TABLE IF NOT EXISTS `spell` ( CREATE TABLE IF NOT EXISTS `spell` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom du sort", `name` VARCHAR(255) NOT NULL DEFAULT "Nom du sort",
`description` VARCHAR(1000) NOT NULL DEFAULT "Description du sort", `description` VARCHAR(1000) NOT NULL DEFAULT "Description du sort",
`level` INT UNSIGNED DEFAULT 0, `level` INT UNSIGNED DEFAULT 0,
`charge` INT UNSIGNED DEFAULT 0, `charge` INT UNSIGNED DEFAULT 0,
`cost` VARCHAR(255) DEFAULT 0, `cost` VARCHAR(255) DEFAULT 0,
`is_ritual` BOOLEAN DEFAULT false, `is_ritual` BOOLEAN DEFAULT false,
`published` BOOLEAN DEFAULT true, `published` BOOLEAN DEFAULT false,
`public` BOOLEAN DEFAULT true, `public` BOOLEAN DEFAULT true,
`author_id` INT UNSIGNED NOT NULL DEFAULT 1, `author_id` INT UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@@ -55,7 +64,7 @@ CREATE TABLE IF NOT EXISTS `spell` (
-- META SCHOOLS -- META SCHOOLS
CREATE TABLE IF NOT EXISTS `meta_school` ( CREATE TABLE IF NOT EXISTS `meta_school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école mère", `name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école mère",
`description` VARCHAR(255) DEFAULT "Description de l'école mère", `description` VARCHAR(255) DEFAULT "Description de l'école mère",
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
@@ -63,9 +72,10 @@ CREATE TABLE IF NOT EXISTS `meta_school` (
-- SCHOOLS -- SCHOOLS
CREATE TABLE IF NOT EXISTS `school` ( CREATE TABLE IF NOT EXISTS `school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école", `name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école",
`description` VARCHAR(255) DEFAULT "Description de l'école", `description` VARCHAR(255) DEFAULT "Description de l'école",
`published` BOOLEAN DEFAULT false,
`meta_school_id` INT UNSIGNED NOT NULL, `meta_school_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
FOREIGN KEY(`meta_school_id`) REFERENCES meta_school(`id`) FOREIGN KEY(`meta_school_id`) REFERENCES meta_school(`id`)
@@ -73,16 +83,18 @@ CREATE TABLE IF NOT EXISTS `school` (
-- COMMON INGREDIENTS -- COMMON INGREDIENTS
CREATE TABLE IF NOT EXISTS `ingredient` ( CREATE TABLE IF NOT EXISTS `ingredient` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Langue de salamandre", `name` VARCHAR(255) NOT NULL DEFAULT "Langue de salamandre",
`description` VARCHAR(255) NOT NULL DEFAULT "Une langue de salamandre de feu encore chaude.", `description` VARCHAR(255) NOT NULL DEFAULT "Une langue de salamandre de feu encore chaude.",
`published` BOOLEAN DEFAULT false,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
-- COMMON VARIABLES -- COMMON VARIABLES
CREATE TABLE IF NOT EXISTS `variable` ( CREATE TABLE IF NOT EXISTS `variable` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`description` VARCHAR(255) NOT NULL DEFAULT "Nombre de créatures affectées", `description` VARCHAR(255) NOT NULL DEFAULT "Nombre de créatures affectées",
`published` BOOLEAN DEFAULT false,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
@@ -138,51 +150,43 @@ DELIMITER ;
/* =========== PRIMARY INSERTS =========== */ /* =========== PRIMARY INSERTS =========== */
SET NAMES utf8; SET NAMES utf8;
USE auracle;
-- ROLES -- CSV DATA
INSERT INTO `role` (name, description) VALUES LOAD DATA INFILE 'C:/temp/auracle_data/permission.csv'
("Visiteur", "Utilisateur normal, peut consulter les sorts."), INTO TABLE `permission`
("Scribe", "Gardiens des écrits, les scribes sont capables de soumettre des sortilèges."), FIELDS TERMINATED BY ','
("Arcanologue", "Maîtres de l'arcane, ils ont le pouvoir et la responsabilité de juger les sortilèges récents et de les supprimer, ou valider."), ENCLOSED BY '"'
("Augure", "Régents des grimoires, ils ont droit d'accès à l'intégralité des informations connues, et pouvoir absolu sur le savoir arcanique."); LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
-- PERMISSIONS LOAD DATA INFILE 'C:/temp/auracle_data/role.csv'
INSERT INTO `permission` (slug) VALUES INTO TABLE `role`
("SUBMIT_SPELLS"), FIELDS TERMINATED BY ','
("APPROVE_SPELLS"), ENCLOSED BY '"'
("MODIFY_SPELLS"), LINES TERMINATED BY '\n'
("DELETE_SPELLS"), IGNORE 1 ROWS;
("WARN_USERS"),
("BAN_USERS");
INSERT INTO `role_permission` (role_id, permission_id) VALUES LOAD DATA INFILE 'C:/temp/auracle_data/role_permission.csv'
(2, 1), INTO TABLE `role_permission`
(3, 1), FIELDS TERMINATED BY ','
(3, 2), ENCLOSED BY '"'
(3, 3), LINES TERMINATED BY '\n'
(4, 1), IGNORE 1 ROWS;
(4, 2),
(4, 3),
(4, 4),
(4, 5),
(4, 6);
-- USERS LOAD DATA INFILE 'C:/temp/auracle_data/user.csv'
INSERT INTO `user` (uuid, name, mail, avatar, gender, register_date, password, role_id, verified, banned) VALUES INTO TABLE `user`
("08e5d2cf-3f0b-454f-b03f-504350d6be85", "Izàc", "tymos@ambrose.edu", null, null, "2020-12-27 17:47:02", "$2b$10$8KWGfRmdQ/ya32fROZNrWugXIEOciDaZwLz.3.GzQa5xrJaGF9RP2", 4, 1, 0); FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
-- META SCHOOLS LOAD DATA INFILE 'C:/temp/auracle_data/api_token.csv'
INSERT INTO `meta_school` (name, description) VALUES INTO TABLE `api_token`
("Magies blanches", "Magies disciplinant les arts de soins et de lumières."), FIELDS TERMINATED BY ','
("Magies noires", "Magies disciplinant l'art de la mort et des secrets."), ENCLOSED BY '"'
("Magies élémentaires", "Magies disciplinant les éléments basiques tels que l'eau, la foudre et le feu, pour n'en citer que les plus populaires."), LINES TERMINATED BY '\n'
("Magies spirituelles", "Magies disciplinant l'esprit, tant pour le défendre que l'attaquer."), IGNORE 1 ROWS;
("Magies spatio-temporelles", "Magies régissant le temps et l'espace."),
("Magies affiliées", "Magies rattachées à une forme d'énergie magique particulière."),
("Magies autres", "Magies trop spécifiques et ne rentrant dans aucune autre grande école.");
-- CSV FILES
LOAD DATA INFILE 'C:/temp/auracle_data/spell.csv' LOAD DATA INFILE 'C:/temp/auracle_data/spell.csv'
INTO TABLE `spell` INTO TABLE `spell`
FIELDS TERMINATED BY ',' FIELDS TERMINATED BY ','
@@ -190,6 +194,13 @@ LOAD DATA INFILE 'C:/temp/auracle_data/spell.csv'
LINES TERMINATED BY '\n' LINES TERMINATED BY '\n'
IGNORE 1 ROWS; IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/meta_school.csv'
INTO TABLE `meta_school`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/school.csv' LOAD DATA INFILE 'C:/temp/auracle_data/school.csv'
INTO TABLE `school` INTO TABLE `school`
FIELDS TERMINATED BY ',' FIELDS TERMINATED BY ','
@@ -211,41 +222,9 @@ LOAD DATA INFILE 'C:/temp/auracle_data/variable.csv'
LINES TERMINATED BY '\n' LINES TERMINATED BY '\n'
IGNORE 1 ROWS; IGNORE 1 ROWS;
-- Insertions de masses LOAD DATA INFILE 'C:/temp/auracle_data/spell_school.csv'
DELIMITER $$ INTO TABLE `spell_school`
CREATE PROCEDURE insertIntoSchoolRange(IN delimiter_start INT, IN delimiter_end INT, IN id_school INT) FIELDS TERMINATED BY ','
BEGIN ENCLOSED BY '"'
SET @i = delimiter_start; LINES TERMINATED BY '\n'
WHILE @i <= delimiter_end DO IGNORE 1 ROWS;
INSERT INTO spell_school (spell_id, school_id) VALUES (@i, id_school);
SET @i = @i + 1;
END WHILE;
END$$
DELIMITER ;
CALL insertIntoSchoolRange(1, 33, 1);
CALL insertIntoSchoolRange(34, 67, 2);
CALL insertIntoSchoolRange(68, 90, 3);
CALL insertIntoSchoolRange(91, 119, 4);
CALL insertIntoSchoolRange(120, 135, 5);
CALL insertIntoSchoolRange(136, 139, 6);
CALL insertIntoSchoolRange(140, 165, 7);
CALL insertIntoSchoolRange(166, 195, 8);
CALL insertIntoSchoolRange(196, 222, 9);
CALL insertIntoSchoolRange(223, 247, 10);
CALL insertIntoSchoolRange(248, 252, 11);
CALL insertIntoSchoolRange(253, 274, 12);
CALL insertIntoSchoolRange(275, 292, 13);
CALL insertIntoSchoolRange(293, 312, 14);
CALL insertIntoSchoolRange(313, 323, 15);
CALL insertIntoSchoolRange(324, 343, 16);
CALL insertIntoSchoolRange(344, 364, 17);
CALL insertIntoSchoolRange(365, 366, 18);
CALL insertIntoSchoolRange(367, 383, 19);
CALL insertIntoSchoolRange(384, 385, 20);
CALL insertIntoSchoolRange(386, 403, 21);
CALL insertIntoSchoolRange(404, 435, 22);
CALL insertIntoSchoolRange(436, 438, 23);
CALL insertIntoSchoolRange(439, 443, 24);
CALL insertIntoSchoolRange(444, 455, 25);
CALL insertIntoSchoolRange(456, 462, 26);

View File

@@ -8,12 +8,9 @@ const morgan = require('morgan');
const cors = require('cors'); // module to format the json response const cors = require('cors'); // module to format the json response
const dotenv = require('dotenv').config(); const dotenv = require('dotenv').config();
// Creates instances of database connections
const connection = require('./database/bookshelf');
const db = connection.db;
// CONSTANTS // CONSTANTS
const port = 2814; const port = 2814;
const base_url = 'http://localhost:2814/api';
// Import routes // Import routes
const routes = require('./routes'); const routes = require('./routes');
@@ -33,19 +30,13 @@ app.use(helmet());
// Server // Server
app.listen(port, () => console.log(`App listening on port ${port}`)); app.listen(port, () => console.log(`App listening on port ${port}`));
// TEMP Auth guard // Entry route
const authguard = (req, res, next) => { app.use('/api/v1/', routes.auth);
// if (req.headers.auracle_key !== process.env.API_KEY_PUBLIC) {
// return res.status(401).send('The API key is either missing or incorrect.');
// } else {
next();
// }
}
// Routing // Routing
app.use('/api/spells', authguard, routes.spells); app.use('/api/v1/spells', routes.spells);
app.use('/api/schools', authguard, routes.schools); app.use('/api/v1/schools', routes.schools);
app.use('/api/meta_schools', authguard, routes.meta_schools); app.use('/api/v1/meta_schools', routes.meta_schools);
app.use('/api/variables', authguard, routes.variables); app.use('/api/v1/variables', routes.variables);
app.use('/api/ingredients', authguard, routes.ingredients); app.use('/api/v1/ingredients', routes.ingredients);
app.use('/api/users', authguard, routes.users); app.use('/api/v1/users', routes.users);

View File

@@ -0,0 +1,14 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf;
require('./user-model');
let APIToken = bookshelf.Model.extend({
tableName: 'api_token',
hidden: ['id'],
user() {
return this.belongsTo('User', 'user_uuid', 'uuid');
}
})
module.exports = bookshelf.model('APIToken', APIToken);

View File

@@ -6,7 +6,7 @@ require('./spell-model');
let User = bookshelf.Model.extend({ let User = bookshelf.Model.extend({
tableName: 'user', tableName: 'user',
hidden: [ 'password' ], hidden: ['password', 'role_id'],
role() { role() {
return this.belongsTo('Role'); return this.belongsTo('Role');
}, },

97
api/package-lock.json generated
View File

@@ -233,11 +233,6 @@
} }
} }
}, },
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"bytes": { "bytes": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -481,14 +476,6 @@
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
}, },
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"ee-first": { "ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -1040,9 +1027,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
}, },
"interpret": { "interpret": {
"version": "2.0.0", "version": "2.0.0",
@@ -1214,49 +1201,6 @@
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz", "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz",
"integrity": "sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA==" "integrity": "sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA=="
}, },
"jsonwebtoken": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
"requires": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^5.6.0"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"kind-of": { "kind-of": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -1334,41 +1278,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
}, },
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
},
"make-iterator": { "make-iterator": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",

View File

@@ -4,7 +4,7 @@
"description": "API for Auracle database", "description": "API for Auracle database",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"run": "node index.js" "start": "node index.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -35,7 +35,6 @@
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"helmet": "^3.22.0", "helmet": "^3.22.0",
"jsonschema": "^1.2.6", "jsonschema": "^1.2.6",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.1", "knex": "^0.21.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql": "^2.18.1", "mysql": "^2.18.1",

View File

@@ -5,9 +5,9 @@ const model = require('../models/ingredient-model')
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator
const v = new Validator() const validator = new Validator()
const IngredientValidation = require("../validations/IngredientValidation") const IngredientValidation = require("../validations/IngredientValidation")
v.addSchema(IngredientValidation, "/IngredientValidation") validator.addSchema(IngredientValidation, "/IngredientValidation")
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt
@@ -80,9 +80,9 @@ class IngredientRepository {
"message": "Le corps de la requête ne peut pas être vide.", "message": "Le corps de la requête ne peut pas être vide.",
"code": 403, "code": 403,
}); });
} else if (!v.validate(igr, IngredientValidation).valid) { } else if (!validator.validate(igr, IngredientValidation).valid) {
reject({ reject({
"message": `Le modèle d'ingrédient n'est pas respecté : ${v.validate(s, IngredientValidation).errors}`, "message": `Le modèle d'ingrédient n'est pas respecté : ${validator.validate(s, IngredientValidation).errors}`,
"code": 403, "code": 403,
}); });
} else if (isXSSAttempt(igr.description)) { } else if (isXSSAttempt(igr.description)) {
@@ -127,9 +127,9 @@ class IngredientRepository {
"message": "Le corps de la requête ne peut pas être vide.", "message": "Le corps de la requête ne peut pas être vide.",
"code": 403, "code": 403,
}); });
} else if (!v.validate(igr, IngredientValidation).valid) { } else if (!validator.validate(igr, IngredientValidation).valid) {
reject({ reject({
"message": `Le modèle d'ingrédient n'est pas respecté : ${v.validate(s, IngredientValidation).errors}`, "message": `Le modèle d'ingrédient n'est pas respecté : ${validator.validate(s, IngredientValidation).errors}`,
"code": 403, "code": 403,
}); });
} else if (isXSSAttempt(igr.description)) { } else if (isXSSAttempt(igr.description)) {

View File

@@ -5,9 +5,9 @@ const model = require('../models/meta-school-model')
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator
const v = new Validator() const validator = new Validator()
const MetaSchoolValidation = require("../validations/MetaSchoolValidation") const MetaSchoolValidation = require("../validations/MetaSchoolValidation")
v.addSchema(MetaSchoolValidation, "/MetaSchoolValidation") validator.addSchema(MetaSchoolValidation, "/MetaSchoolValidation")
// Validations // Validations
const regexXSS = RegExp(/<[^>]*script/) const regexXSS = RegExp(/<[^>]*script/)

View File

@@ -5,9 +5,9 @@ const model = require('../models/school-model')
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator
const v = new Validator() const validator = new Validator()
const SchoolValidation = require("../validations/SchoolValidation") const SchoolValidation = require("../validations/SchoolValidation")
v.addSchema(SchoolValidation, "/SchoolValidation") validator.addSchema(SchoolValidation, "/SchoolValidation")
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt
@@ -79,9 +79,9 @@ class SchoolRepository {
"message": "Le corps de la requête ne peut pas être vide.", "message": "Le corps de la requête ne peut pas être vide.",
"code": 403, "code": 403,
}); });
} else if (!v.validate(s, SchoolValidation).valid) { } else if (!validator.validate(s, SchoolValidation).valid) {
reject({ reject({
"message": `Le modèle d'école n'est pas respecté : ${v.validate(s, SchoolValidation).errors}`, "message": `Le modèle d'école n'est pas respecté : ${validator.validate(s, SchoolValidation).errors}`,
"code": 403, "code": 403,
}); });
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) {
@@ -127,9 +127,9 @@ class SchoolRepository {
"message": "Le corps de la requête ne peut pas être vide.", "message": "Le corps de la requête ne peut pas être vide.",
"code": 403, "code": 403,
}); });
} else if (!v.validate(s, SchoolValidation).valid) { } else if (!validator.validate(s, SchoolValidation).valid) {
reject({ reject({
"message": `Le modèle d'école n'est pas respecté : ${v.validate(s, SchoolValidation).errors}`, "message": `Le modèle d'école n'est pas respecté : ${validator.validate(s, SchoolValidation).errors}`,
"code": 403, "code": 403,
}); });
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) {

View File

@@ -5,9 +5,9 @@ const model = require('../models/spell-model')
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator
const v = new Validator() const validator = new Validator()
const SpellValidation = require("../validations/SpellValidation") const SpellValidation = require("../validations/SpellValidation")
v.addSchema(SpellValidation, "/SpellValidation") validator.addSchema(SpellValidation, "/SpellValidation")
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt
@@ -118,9 +118,9 @@ class SpellRepository {
"message": "Le corps de la requête ne peut pas être vide.", "message": "Le corps de la requête ne peut pas être vide.",
"code": 403, "code": 403,
}); });
} else if (!v.validate(s, SpellValidation).valid) { } else if (!validator.validate(s, SpellValidation).valid) {
reject({ reject({
"message": `Le modèle de sortilège n'est pas respecté : ${v.validate(s, SpellValidation).errors}`, "message": `Le modèle de sortilège n'est pas respecté : ${validator.validate(s, SpellValidation).errors}`,
"code": 403, "code": 403,
}); });
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
@@ -194,9 +194,9 @@ class SpellRepository {
"message": "Le corps de la requête ne peut pas être vide.", "message": "Le corps de la requête ne peut pas être vide.",
"code": 403, "code": 403,
}); });
} else if (!v.validate(s, SpellValidation).valid) { } else if (!validator.validate(s, SpellValidation).valid) {
reject({ reject({
"message": `Le modèle de sortilège n'est pas respecté : ${v.validate(s, SpellValidation).errors}`, "message": `Le modèle de sortilège n'est pas respecté : ${validator.validate(s, SpellValidation).errors}`,
"code": 403, "code": 403,
}); });
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {

View File

@@ -1,30 +1,37 @@
'use strict' 'use strict'
// Bookshelf // Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/user-model') const model = require('../models/user-model');
const token_model = require('../models/api-token-model');
// Hashing and passwords // Hashing and passwords
const bcrypt = require('bcrypt') const bcrypt = require('bcrypt');
const { v4: uuidv4 } = require('uuid') const { v4: uuidv4 } = require('uuid');
// Mailing methods // Mailing methods
const mails = require('../smtp/mails') const mails = require('../smtp/mails');
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator;
const v = new Validator() const validator = new Validator();
const UserValidation = require("../validations/UserValidation") const UserValidation = require("../validations/UserValidation");
v.addSchema(UserValidation, "/UserValidation") validator.addSchema(UserValidation, "/UserValidation");
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject const isEmptyObject = require('../functions').isEmptyObject;
class UserRepository { class UserRepository {
constructor() { constructor() {
} }
/**
* Fetches all users in the dabatase.
*
* @returns { Promise }
* Fulfilled data: Array of user objects.
*/
getAll() { getAll() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
@@ -42,8 +49,26 @@ class UserRepository {
}) })
} }
/**
* Fetches a user object associated with the uuid.
*
* @param { string } uuid
* @param { boolean } full
* Whether the password should also be fetched. (should never be true unless you want to log the user)
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
getOneByUUID(uuid, full) { getOneByUUID(uuid, full) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!(uuid)) {
reject({
"message": "La requête doit renseigner un uuid.",
"code": 400,
})
}
new model() new model()
.where({ 'uuid': uuid }) .where({ 'uuid': uuid })
.fetch({ withRelated: ['role.permissions'] }) .fetch({ withRelated: ['role.permissions'] })
@@ -59,8 +84,26 @@ class UserRepository {
}) })
} }
/**
* Fetches a user object associated with the mail address.
*
* @param { string } mail
* @param { boolean } full
* Whether the password should also be fetched. (should never be true unless you want to log the user)
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
getOneByEmail(mail, full) { getOneByEmail(mail, full) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!(mail)) {
reject({
"message": "La requête doit renseigner un email.",
"code": 400,
})
}
new model() new model()
.where({ 'mail': mail }) .where({ 'mail': mail })
.fetch({ withRelated: ['role'] }) .fetch({ withRelated: ['role'] })
@@ -76,8 +119,23 @@ class UserRepository {
}) })
} }
/**
* Fetches all spells linked to a user's uuid
*
* @param { string } uuid
*
* @returns
*/
getSpellsFromOne(uuid) { getSpellsFromOne(uuid) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!(uuid)) {
reject({
"message": "La requête doit renseigner un uuid.",
"code": 400,
})
}
new model() new model()
.where({ 'uuid': uuid }) .where({ 'uuid': uuid })
.fetch({ withRelated: ['role', 'spells.schools.meta_schools', 'spells.variables', 'spells.ingredients'] }) .fetch({ withRelated: ['role', 'spells.schools.meta_schools', 'spells.variables', 'spells.ingredients'] })
@@ -93,6 +151,15 @@ class UserRepository {
}) })
} }
/**
* Registers a user based on the model at ./models/user-model.js.
*
* @param { object} u
* User object
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
addOne(u) { addOne(u) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
@@ -101,9 +168,9 @@ class UserRepository {
"message": "Le corps de requête ne peut être vide.", "message": "Le corps de requête ne peut être vide.",
"code": 403, "code": 403,
}) })
} else if (!v.validate(u, UserValidation).valid) { } else if (!validator.validate(u, UserValidation).valid) {
reject({ reject({
"message": "Structure de la requête invalide - " + v.validate(u, UserValidation).errors, "message": "Structure de la requête invalide - " + validator.validate(u, UserValidation).errors,
"code": 403, "code": 403,
}) })
} else if (isXSSAttempt(u.name) || isXSSAttempt(u.password) || isXSSAttempt(u.mail)) { } else if (isXSSAttempt(u.name) || isXSSAttempt(u.password) || isXSSAttempt(u.mail)) {
@@ -112,9 +179,9 @@ class UserRepository {
"code": 403, "code": 403,
}) })
} else { } else {
let hash = await bcrypt.hash(u.password, 10) let hash = await bcrypt.hash(u.password, 10);
let uuid = uuidv4() let uuid = uuidv4();
let verification_token = uuidv4(); let verification_token = uuidv4();
this.checkIfEmailAvailable(u.mail) this.checkIfEmailAvailable(u.mail)
@@ -150,16 +217,17 @@ class UserRepository {
mail: newUser.mail, mail: newUser.mail,
token: verification_token, token: verification_token,
} }
}) });
// Then resolves the api call // Then resolves the api call
resolve({ resolve({
"message": `Compte utilisateur #${newUser.id} créé avec succès.`, "message": `Compte utilisateur #${newUser.id} créé avec succès.`,
"code": 201, "code": 201,
"user": newUser, "user": newUser,
}) });
}) })
.catch(err => { .catch(err => {
console.log(err);
resolve({ resolve({
"message": "Une erreur s'est produite en créant votre compte. Veuillez réessayer ultérieurement ou contactez l'administrateur.", "message": "Une erreur s'est produite en créant votre compte. Veuillez réessayer ultérieurement ou contactez l'administrateur.",
"code": 500, "code": 500,
@@ -173,6 +241,14 @@ class UserRepository {
}) })
} }
/**
* Verifies an account based on a private UUID token
*
* @param { string } token
* A UUID v4 identification token provided at registration and on special demands
*
* @returns Redirects to login page
*/
verifyUser(token) { verifyUser(token) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
@@ -188,7 +264,7 @@ class UserRepository {
transacting: t transacting: t
}) })
}) })
.then(v => { .then(() => {
resolve({ resolve({
"message": "Insérez ici une future redirection vers le client.", "message": "Insérez ici une future redirection vers le client.",
"code": 202, "code": 202,
@@ -204,52 +280,228 @@ class UserRepository {
}); });
} }
// Log user with an email address and a password /**
* Logs a user by comparing the dual mail/password inputs
*
* @param { string } mail
* @param { string } password
*
* @return { Promise }
* Fulfilled data: Queried user object.
*/
logUser(mail, password) { logUser(mail, password) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.getOneByEmail(mail, true) this.getOneByEmail(mail, true)
.then(async fetchedUser => { .then(async fetchedUser => {
let match = await bcrypt.compare(password, fetchedUser.password) let match = await bcrypt.compare(password, fetchedUser.password);
delete fetchedUser.id
delete fetchedUser.password
// Makes sure no hash gets out
delete fetchedUser.password;
// If you found a user...
if (match) { if (match) {
// If they're banned...
if (fetchedUser.banned) { if (fetchedUser.banned) {
reject({ reject({
"message": `L'utilisateur #${fetchedUser.name} a été banni, la connexion est impossible.`, "message": `L'utilisateur #${fetchedUser.name} a été banni, la connexion est impossible.`,
"code": 403, "code": 403,
}) });
// If they're not verified...
} else if (!fetchedUser.verified) { } else if (!fetchedUser.verified) {
reject({ reject({
"message": `L'utilisateur #${fetchedUser.name} n'as pas été vérifié, le compte doit être activé avant la connexion.`, "message": `L'utilisateur #${fetchedUser.name} n'as pas été vérifié, le compte doit être activé avant la connexion.`,
"code": 401, "code": 401,
}) });
} else { } else {
resolve({ resolve({
"message": `L'utilisateur #${fetchedUser.name} s'est connecté.`, "message": `L'utilisateur #${fetchedUser.name} s'est connecté.`,
"code": 200, "code": 200,
"user": fetchedUser, "user": fetchedUser,
}) });
} }
} else { } else {
reject({ reject({
"message": "Les informations de connexions sont erronées.", "message": "Les informations de connexions sont erronées.",
"code": 400, "code": 400,
}) });
} }
}) })
.catch(err => { .catch(err => {
reject(err) reject(err);
}) })
}) })
} }
// Check if one user already has that email /**
* Generate a token for the user to use the API
* Requires mail and password for verifying the user
*
* @param { string } mail
* @param { string } password
*
* @returns { Promise }
* Fulfilled data: A unique UUID token string.
*/
genAPIToken(mail, password) {
return new Promise((resolve, reject) => {
this.logUser(mail, password)
.then(v => {
let user = v.user;
let new_token = uuidv4();
bookshelf.transaction(t => {
return new token_model({
'value': new_token,
'user_uuid': user.uuid,
})
.save(null, {
transacting: t
})
.catch(err => {
// If the account already has an API key linked...
if (err.errno == 1062) {
this.fetchAPIKey(user.uuid)
.then(old_api_key => {
reject({
"message": "Votre compte a déjà généré une clé d'API.",
"code": 409,
"API_key": old_api_key.value,
});
});
// Default errors
} else {
throw err
}
});
})
.then(api_key => {
resolve({
"message": "La clé d'API a été généré.",
"code": 201,
"API_key": api_key,
})
})
.catch(err => {
console.log(err);
reject({
"message": "La génération de jeton d'API n'a pas pu être conclue.",
"code": 500,
});
})
})
.catch(err => {
reject(err);
})
})
}
checkAPITokenPerms(token, permissions) {
return new Promise(async (resolve, reject) => {
// If the request doesn't have a token, reject it.
if (!token) {
reject({
"message": "Vous devez utiliser un jeton d'API dans l'en-tête de votre requête.",
"code": 401,
})
}
// Fetches user from token
new token_model()
.where({ 'value': token })
.fetch({ withRelated: 'user.role.permissions' })
// Catches not found errors
.catch(err => {
if (err instanceof token_model.NotFoundError) {
reject({
"message": "Ce jeton n'est affilié à aucun compte.",
"code": 404,
});
} else {
reject({
"message": "Le jeton ou l'utilisateur n'a pas pu être récupéré.",
"code": 500,
});
}
})
.then(fullToken => {
token = fullToken.toJSON({ omitPivot: true });
// If the token is associated with a banned user...
if (token.user.banned) {
reject({
"message": "Le jeton est lié à un utilisateur banni, il n'est donc plus utilisable.",
"code": 401,
});
}
// If the token is associated with a non-verified user...
if (!token.user.verified) {
reject({
"message": "Le jeton est lié à un utilisateur non-vérifié, il n'est donc pas utilisable.",
"code": 401,
});
}
let user_permissions = token.user.role.permissions;
// Convert user_perm to array
user_permissions.forEach((user_perm, i) => {
user_permissions[i] = user_perm.slug;
});
// Loops to check if the person has all the permissions required...
permissions.forEach(perm => {
// If not, reject...
if (!user_permissions.includes(perm)) {
reject({
"message": "Permissions insuffisantes.",
"code": 401
})
}
});
// If everything went well, resolve the promise.
resolve({
"message": "Credentials accepted.",
"code": 200
});
})
// Unhandled errors
.catch(err => {
reject({
"message": "Une erreur inconnue est survenue.",
"code": 500,
});
});
})
}
/**
* Check if the email that was input is available for account creation.
*
* @param {string} mail
*
* @returns { Promise }
* Fulfilled: HTTP 200 if email is available.
* Rejected: HTTP 400-409 if email is already used.
*/
checkIfEmailAvailable(mail) { checkIfEmailAvailable(mail) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.validateEmail(mail)) { if (!(mail)) {
reject({
"message": "La requête doit renseigner un email.",
"code": 400,
})
}
if (!this.validateMail(mail)) {
reject({ reject({
"message": "La requête n'est pas un email valide.", "message": "La requête n'est pas un email valide.",
"code": 400, "code": 400,
@@ -272,9 +524,38 @@ class UserRepository {
}) })
} }
validateEmail(email) { /**
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; * Fetches the associated api_token from a user uuid.
return re.test(String(email).toLowerCase()); *
* @param { string } uuid
*
* @returns { Promise }
* Fulfilled data: Queried API token object
*/
fetchAPIKey(uuid) {
return new Promise((resolve, reject) => {
new token_model()
.where({ 'user_uuid': uuid })
.fetch()
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
console.log(err);
})
})
}
/**
* Whether a mail is correctly formed and ripe for receiving, ie: xxx@yyy.zzz.
*
* @param { string } mail
*
* @returns { boolean }
*/
validateMail(mail) {
const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regex.test(String(mail).toLowerCase());
} }
} }

View File

@@ -5,9 +5,9 @@ const model = require('../models/variable-model')
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator
const v = new Validator() const validator = new Validator()
const VariableValidation = require("../validations/VariableValidation") const VariableValidation = require("../validations/VariableValidation")
v.addSchema(VariableValidation, "/VariableValidation") validator.addSchema(VariableValidation, "/VariableValidation")
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt
@@ -80,9 +80,9 @@ class VariableRepository {
"message": "Le corps de la requête ne peut pas être vide.", "message": "Le corps de la requête ne peut pas être vide.",
"code": 403, "code": 403,
}); });
} else if (!v.validate(vr, VariableValidation).valid) { } else if (!validator.validate(vr, VariableValidation).valid) {
reject({ reject({
"message": `Le modèle de variable n'est pas respecté : ${v.validate(s, VariableValidation).errors}`, "message": `Le modèle de variable n'est pas respecté : ${validator.validate(s, VariableValidation).errors}`,
"code": 403, "code": 403,
}); });
} else if (isXSSAttempt(vr.description)) { } else if (isXSSAttempt(vr.description)) {
@@ -126,9 +126,9 @@ class VariableRepository {
"message": "Le corps de la requête ne peut pas être vide.", "message": "Le corps de la requête ne peut pas être vide.",
"code": 403, "code": 403,
}); });
} else if (!v.validate(vr, VariableValidation).valid) { } else if (!validator.validate(vr, VariableValidation).valid) {
reject({ reject({
"message": `Le modèle de variable n'est pas respecté : ${v.validate(s, VariableValidation).errors}`, "message": `Le modèle de variable n'est pas respecté : ${validator.validate(s, VariableValidation).errors}`,
"code": 403, "code": 403,
}); });
} else if (isXSSAttempt(vr.description)) { } else if (isXSSAttempt(vr.description)) {

29
api/routes/auth.js Normal file
View File

@@ -0,0 +1,29 @@
'use strict'
// Router
const express = require('express');
let router = express.Router();
// Repository
const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository();
// ROUTES
// GEN API TOKEN
const generateAPIToken = (mail, password) => {
return Users.genAPIToken(mail, password)
.catch(err => {
throw err
})
}
router.get('/genToken', async (req, res) => {
generateAPIToken(req.body.mail, req.body.password)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(err))
})
})
module.exports = router;

View File

@@ -1,11 +1,14 @@
const spells = require('./spells') const spells = require('./spells');
const schools = require('./schools') const schools = require('./schools');
const meta_schools = require('./meta_schools') const meta_schools = require('./meta_schools');
const variables = require('./variables') const variables = require('./variables');
const ingredients = require('./ingredients') const ingredients = require('./ingredients');
const users = require('./users') const users = require('./users');
const auth = require('./auth');
module.exports = { module.exports = {
auth,
spells, spells,
schools, schools,
meta_schools, meta_schools,

View File

@@ -1,17 +1,19 @@
'use strict' 'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
const functions = require('../functions')
// Repository // Repository
const IngredientRepository = require('../repositories/ingredient-repository'); const IngredientRepository = require('../repositories/ingredient-repository');
const Ingredients = new IngredientRepository(); const Ingredients = new IngredientRepository();
// Functions
const functions = require('../functions');
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getIngredients = () => { const getIngredients = () => {
@@ -21,7 +23,9 @@ const getIngredients = () => {
throw err throw err
}) })
} }
router.get('/', async (req, res) => { router.get(
'/',
async (req, res) => {
getIngredients() getIngredients()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -46,7 +50,9 @@ const getIngredient = (id) => {
throw err throw err
}) })
} }
router.get('/:id/', async (req, res) => { router.get(
'/:id/',
async (req, res) => {
getIngredient(req.params.id) getIngredient(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -71,7 +77,9 @@ const getSpellsFromOne = (id) => {
throw err throw err
}) })
} }
router.get('/:id/spells', async (req, res) => { router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(req.params.id) getSpellsFromOne(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -96,7 +104,10 @@ const addIngredient = (igr) => {
throw err throw err
}) })
} }
router.post('/', async (req, res) => { router.post(
'/',
authGuard(['SUBMIT_INGREDIENTS']),
async (req, res) => {
addIngredient(req.body) addIngredient(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -121,7 +132,10 @@ const updateIngredient = (id, igr) => {
throw err throw err
}) })
} }
router.put('/:id/', async (req, res) => { router.put(
'/:id/',
authGuard(['SUBMIT_INGREDIENTS', 'MODIFY_INGREDIENTS']),
async (req, res) => {
updateIngredient(req.params.id, req.body) updateIngredient(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -146,7 +160,10 @@ const deleteIngredient = (id) => {
throw err throw err
}) })
} }
router.delete('/:id/', async (req, res) => { router.delete(
'/:id/',
authGuard(['SUBMIT_INGREDIENTS', 'MODIFY_INGREDIENTS', 'DELETE_INGREDIENTS']),
async (req, res) => {
deleteIngredient(req.params.id) deleteIngredient(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')

View File

@@ -1,12 +1,11 @@
'use strict' 'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // Functions
const connection = require('../database/bookshelf') const functions = require('../functions');
const functions = require('../functions')
// Repository // Repository
const MetaSchoolRepository = require('../repositories/meta-school-repository'); const MetaSchoolRepository = require('../repositories/meta-school-repository');

View File

@@ -0,0 +1,23 @@
// Repository
const UserRepository = require('../../repositories/user-repository');
const Users = new UserRepository();
// AUTHGUARD
const authGuard = (permissions) => {
return async (req, res, next) => {
// Get token from headers
let api_token = req.headers['auracle_key'];
// Uses repo to validate the associated perms with the token
Users.checkAPITokenPerms(api_token, permissions)
.then(v => {
next();
})
.catch(err => {
res.status(err.code).send(JSON.stringify(err))
});
}
}
module.exports = authGuard;

View File

@@ -1,17 +1,19 @@
'use strict' 'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
const functions = require('../functions')
// Repository // Repository
const SchoolRepository = require('../repositories/school-repository'); const SchoolRepository = require('../repositories/school-repository');
const Schools = new SchoolRepository(); const Schools = new SchoolRepository();
// Functions
const functions = require('../functions');
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getSchools = () => { const getSchools = () => {
@@ -21,7 +23,9 @@ const getSchools = () => {
throw err throw err
}) })
} }
router.get('/', async (req, res) => { router.get(
'/',
async (req, res) => {
getSchools() getSchools()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -46,7 +50,9 @@ const getSchool = (id) => {
throw err throw err
}) })
} }
router.get('/:id/', async (req, res) => { router.get(
'/:id/',
async (req, res) => {
getSchool(req.params.id) getSchool(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -71,7 +77,9 @@ const getSpellsFromOne = (id) => {
throw err throw err
}) })
} }
router.get('/:id/spells', async (req, res) => { router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(req.params.id) getSpellsFromOne(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -96,7 +104,10 @@ const addSchool = (s) => {
throw err throw err
}) })
} }
router.post('/', async (req, res) => { router.post(
'/',
authGuard(['SUBMIT_SCHOOL']),
async (req, res) => {
addSchool(req.body) addSchool(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -120,7 +131,10 @@ const updateSchool = (id, s) => {
throw err throw err
}) })
} }
router.put('/:id/', async (req, res) => { router.put(
'/:id/',
authGuard(['SUBMIT_SCHOOLS', 'MODIFY_SCHOOLS']),
async (req, res) => {
updateSchool(req.params.id, req.body) updateSchool(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -145,7 +159,10 @@ const deleteSchool = (id) => {
throw err throw err
}) })
} }
router.delete('/:id/', async (req, res) => { router.delete(
'/:id/',
authGuard(['SUBMIT_SCHOOLS', 'MODIFY_SCHOOLS', 'DELETE_SCHOOLS']),
async (req, res) => {
deleteSchool(req.params.id) deleteSchool(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')

View File

@@ -1,17 +1,19 @@
'use strict' 'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
const functions = require('../functions')
// Repository // Repository
const SpellReposity = require('../repositories/spell-repository'); const SpellReposity = require('../repositories/spell-repository');
const Spells = new SpellReposity(); const Spells = new SpellReposity();
// Functions
const functions = require('../functions');
// ROUTES // ROUTES
// GET ALL PUBLIC ------------------ // GET ALL PUBLIC ------------------
const getPublicSpells = (name, description, level, charge, cost, ritual) => { const getPublicSpells = (name, description, level, charge, cost, ritual) => {
@@ -21,7 +23,9 @@ const getPublicSpells = (name, description, level, charge, cost, ritual) => {
throw err throw err
}) })
} }
router.get('//:name?/:description?/:level?/:charge?/:cost?/:ritual?/', async (req, res) => { router.get(
'//:name?/:description?/:level?/:charge?/:cost?/:ritual?/',
async (req, res) => {
getPublicSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual) getPublicSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -45,7 +49,10 @@ const getSpells = (name, description, level, charge, cost, ritual) => {
throw err throw err
}) })
} }
router.get('/private/:name?/:description?/:level?/:charge?/:cost?/:ritual?/', async (req, res) => { router.get(
'/private/:name?/:description?/:level?/:charge?/:cost?/:ritual?/',
authGuard(['SECRET_SPELLS']),
async (req, res) => {
getSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual) getSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -69,7 +76,9 @@ const getSomeSpells = (page) => {
throw err throw err
}) })
} }
router.get('/page/:page', async (req, res) => { router.get(
'/page/:page',
async (req, res) => {
getSomeSpells(req.params.page) getSomeSpells(req.params.page)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -93,7 +102,9 @@ const getSpell = (id) => {
throw err throw err
}) })
} }
router.get('/:id/', async (req, res) => { router.get(
'/:id/',
async (req, res) => {
getSpell(req.params.id) getSpell(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -118,7 +129,10 @@ const addSpell = (s) => {
throw err throw err
}) })
} }
router.post('/', async (req, res) => { router.post(
'/',
authGuard(['SUBMIT_SPELLS']),
async (req, res) => {
addSpell(req.body) addSpell(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -143,7 +157,10 @@ const updateSpell = (id, s) => {
throw err throw err
}) })
} }
router.put('/:id/', async (req, res) => { router.put(
'/:id/',
authGuard(['SUBMIT_SPELLS', 'MODIFY_SPELLS']),
async (req, res) => {
updateSpell(req.params.id, req.body) updateSpell(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -168,7 +185,10 @@ const deleteSpell = (id) => {
throw err throw err
}) })
} }
router.delete('/:id/', async (req, res) => { router.delete(
'/:id/',
authGuard(['SUBMIT_SPELLS', 'MODIFY_SPELLS', 'DELETE_SPELLS']),
async (req, res) => {
deleteSpell(req.params.id) deleteSpell(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')

View File

@@ -1,12 +1,11 @@
'use strict' 'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
const db = connection.db
// Repository // Repository
const UserRepository = require('../repositories/user-repository'); const UserRepository = require('../repositories/user-repository');

View File

@@ -1,17 +1,19 @@
'use strict' 'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
const functions = require('../functions')
// Repository // Repository
const VariableRepository = require('../repositories/variable-repository'); const VariableRepository = require('../repositories/variable-repository');
const Variables = new VariableRepository(); const Variables = new VariableRepository();
// Functions
const functions = require('../functions');
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getvariables = () => { const getvariables = () => {
@@ -21,7 +23,9 @@ const getvariables = () => {
throw err throw err
}) })
} }
router.get('/', async (req, res) => { router.get(
'/',
async (req, res) => {
getvariables() getvariables()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -46,7 +50,9 @@ const getVariable = (id) => {
throw err throw err
}) })
} }
router.get('/:id/', async (req, res) => { router.get(
'/:id/',
async (req, res) => {
getVariable(req.params.id) getVariable(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -71,7 +77,9 @@ const getSpellsFromOne = (id) => {
throw err throw err
}) })
} }
router.get('/:id/spells', async (req, res) => { router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(req.params.id) getSpellsFromOne(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -96,7 +104,10 @@ const addVariable = (vr) => {
throw err throw err
}) })
} }
router.post('/', async (req, res) => { router.post(
'/',
authGuard(['SUBMIT_VARIABLES']),
async (req, res) => {
addVariable(req.body) addVariable(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -121,7 +132,10 @@ const updateVariable = (id, vr) => {
throw err throw err
}) })
} }
router.put('/:id/', async (req, res) => { router.put(
'/:id/',
authGuard(['SUBMIT_VARIABLES', 'MODIFY_VARIABLES']),
async (req, res) => {
updateVariable(req.params.id, req.body) updateVariable(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -146,7 +160,10 @@ const deleteVariable = (id) => {
throw err throw err
}) })
} }
router.delete('/:id/', async (req, res) => { router.delete(
'/:id/',
authGuard(['SUBMIT_VARIABLES', 'MODIFY_VARIABLES', 'DELETE_VARIABLES']),
async (req, res) => {
deleteVariable(req.params.id) deleteVariable(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Template Email Auracle Inscription</title> <title>Template Email Auracle Inscription</title>
<style> <style>
@@ -44,6 +45,7 @@
padding: 10vh 5vw; padding: 10vh 5vw;
background: url('https://i.imgur.com/IxEKKEY.png') center center fixed repeat; background: url('https://i.imgur.com/IxEKKEY.png') center center fixed repeat;
} }
.wrapper:before { .wrapper:before {
display: block; display: block;
content: ''; content: '';
@@ -62,7 +64,8 @@
z-index: 5; z-index: 5;
} }
header, footer { header,
footer {
text-align: center; text-align: center;
color: var(--white); color: var(--white);
background-color: rgb(var(--black)); background-color: rgb(var(--black));
@@ -77,6 +80,7 @@
} }
</style> </style>
</head> </head>
<body> <body>
<div class="wrapper"> <div class="wrapper">
<div class="container"> <div class="container">
@@ -85,8 +89,17 @@
</header> </header>
<main> <main>
<p>Votre compte Auracle a bien été enregistré et est rattaché à cette adresse mail.</p> <p>Votre compte Auracle a bien été enregistré et est rattaché à cette adresse mail.</p>
<p>Cependant, afin de garantir la sécurité de vos données, votre compte doit être vérifié avant votre première connexion. Vous pouvez <a href="http://localhost:2814/api/users/verification/{{ user.token }}">suivre ce lien afin de confirmer votre inscription.</a></p> <p>Cependant, afin de garantir la sécurité de vos données, votre compte doit être vérifié avant votre
<p>Si vous rencontrez des problèmes, n'hésitez pas à envoyer un mail à <a href="mailto:support@auracle.io">support@auracle.io</a></p> première connexion. Vous pouvez
<a href="http: //localhost:2814/api/v1/users/verification/{{ user.token }}">
suivre ce lien afin de confirmer votre inscription.
</a>
</p>
<p>Si vous rencontrez des problèmes, n'hésitez pas à envoyer un mail à
<a href="mailto:support@auracle.io">
support@auracle.io
</a>
</p>
<p><strong>Merci de votre soutien !</strong></p> <p><strong>Merci de votre soutien !</strong></p>
<p class="italics">Bien sincèrement, Izàc Tymos.</p> <p class="italics">Bien sincèrement, Izàc Tymos.</p>
</main> </main>
@@ -97,4 +110,5 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>