Added authGuard function and its implementations

Also refactored small code mistakes.
This commit is contained in:
Alexis
2021-01-19 18:52:52 +01:00
parent 410a58fc09
commit e8ca2416b7
31 changed files with 4062 additions and 3947 deletions

View File

@@ -163,19 +163,24 @@ INSERT INTO `permission` (slug) VALUES
("MODIFY_SPELLS"), ("MODIFY_SPELLS"),
("DELETE_SPELLS"), ("DELETE_SPELLS"),
("WARN_USERS"), ("WARN_USERS"),
("BAN_USERS"); ("BAN_USERS"),
("SECRET_SPELLS"),
("SECRET_USERS");
INSERT INTO `role_permission` (role_id, permission_id) VALUES INSERT INTO `role_permission` (role_id, permission_id) VALUES
(2, 1), (2, 1),
(3, 1), (3, 1),
(3, 2), (3, 2),
(3, 3), (3, 3),
(3, 7),
(4, 1), (4, 1),
(4, 2), (4, 2),
(4, 3), (4, 3),
(4, 4), (4, 4),
(4, 5), (4, 5),
(4, 6); (4, 6),
(4, 7),
(4, 8);
-- USERS -- USERS
INSERT INTO `user` (uuid, name, mail, avatar, gender, register_date, password, role_id, verified, banned) VALUES INSERT INTO `user` (uuid, name, mail, avatar, gender, register_date, password, role_id, verified, banned) VALUES

View File

@@ -5,11 +5,11 @@ const fs = require('fs');
const knex = require('knex')({ const knex = require('knex')({
client: "mysql", client: "mysql",
connection: { connection: {
host : process.env.DB_HOST, host: process.env.DB_HOST,
user : process.env.DB_USER, user: process.env.DB_USER,
password : process.env.DB_PASSWORD, password: process.env.DB_PASSWORD,
database : process.env.DB_DATABASE, database: process.env.DB_DATABASE,
charset : "utf8" charset: "utf8"
}, },
}); });
const bookshelf = require('bookshelf')(knex); const bookshelf = require('bookshelf')(knex);

View File

@@ -31,7 +31,7 @@ app.use(helmet());
app.listen(port, () => console.log(`App listening on port ${port}`)); app.listen(port, () => console.log(`App listening on port ${port}`));
// Entry route // Entry route
app.use('/api/v1/', routes.auth ); app.use('/api/v1/', routes.auth);
// Routing // Routing
app.use('/api/v1/spells', routes.spells); app.use('/api/v1/spells', routes.spells);

View File

@@ -5,10 +5,10 @@ require('./user-model');
let APIToken = bookshelf.Model.extend({ let APIToken = bookshelf.Model.extend({
tableName: 'api_token', tableName: 'api_token',
hidden: [ 'id' ], hidden: ['id'],
user() { user() {
return this.hasOne( 'User', 'user_uuid' ); return this.belongsTo('User', 'user_uuid', 'uuid');
} }
}) })
module.exports = bookshelf.model( 'APIToken', APIToken ); module.exports = bookshelf.model('APIToken', APIToken);

View File

@@ -6,7 +6,7 @@ require('./spell-model')
let Ingredient = bookshelf.Model.extend({ let Ingredient = bookshelf.Model.extend({
tableName: 'ingredient', tableName: 'ingredient',
spells() { spells() {
return this.belongsToMany( 'Spell', 'spell_ingredient') return this.belongsToMany('Spell', 'spell_ingredient')
} }
}) })

View File

@@ -6,7 +6,7 @@ require('./school-model')
let MetaSchool = bookshelf.Model.extend({ let MetaSchool = bookshelf.Model.extend({
tableName: 'meta_school', tableName: 'meta_school',
schools() { schools() {
return this.hasMany( 'School' ) return this.hasMany('School')
} }
}) })

View File

@@ -4,10 +4,10 @@ require('./role-model')
let Permission = bookshelf.Model.extend({ let Permission = bookshelf.Model.extend({
tableName: 'permission', tableName: 'permission',
hidden: [ 'id' ], hidden: ['id'],
role() { role() {
return this.belongsToMany( 'Role', 'role_permission' ) return this.belongsToMany('Role', 'role_permission')
} }
}) })
module.exports = bookshelf.model( 'Permission', Permission ); module.exports = bookshelf.model('Permission', Permission);

View File

@@ -5,8 +5,8 @@ require('./permission-model')
let Role = bookshelf.Model.extend({ let Role = bookshelf.Model.extend({
tableName: 'role', tableName: 'role',
permissions() { permissions() {
return this.belongsToMany( 'Permission', 'role_permission' ); return this.belongsToMany('Permission', 'role_permission');
} }
}) })
module.exports = bookshelf.model( 'Role', Role ); module.exports = bookshelf.model('Role', Role);

View File

@@ -7,10 +7,10 @@ require('./meta-school-model')
let School = bookshelf.Model.extend({ let School = bookshelf.Model.extend({
tableName: 'school', tableName: 'school',
spells() { spells() {
return this.belongsToMany( 'Spell', 'spell_school' ) return this.belongsToMany('Spell', 'spell_school')
}, },
meta_schools() { meta_schools() {
return this.belongsTo( 'MetaSchool', 'meta_school_id' ) return this.belongsTo('MetaSchool', 'meta_school_id')
} }
}) })

View File

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

View File

@@ -6,7 +6,7 @@ require('./spell-model')
let Variable = bookshelf.Model.extend({ let Variable = bookshelf.Model.extend({
tableName: 'variable', tableName: 'variable',
spells() { spells() {
return this.belongsToMany( 'Spell', 'spell_variable') return this.belongsToMany('Spell', 'spell_variable')
} }
}) })

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
@@ -39,8 +39,8 @@ class IngredientRepository {
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({ withRelated: ['spells']}) .fetch({ withRelated: ['spells'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
@@ -57,8 +57,8 @@ class IngredientRepository {
getSpellsFromOne(id) { getSpellsFromOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools']}) .fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
@@ -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)) {
@@ -138,8 +138,8 @@ class IngredientRepository {
"code": 403, "code": 403,
}); });
} else { } else {
new model({id: id}) new model({ id: id })
.fetch({require: true, withRelated: ['spells']}) .fetch({ require: true, withRelated: ['spells'] })
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return v.save({ return v.save({
@@ -182,8 +182,8 @@ class IngredientRepository {
deleteOne(id) { deleteOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({require: true, withRelated: ['spells']}) .fetch({ require: true, withRelated: ['spells'] })
.then(v => { .then(v => {
v.spells().detach() v.spells().detach()
v.destroy() v.destroy()

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/)
@@ -37,8 +37,8 @@ class MetaSchoolRepository {
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({ withRelated: ['schools']}) .fetch({ withRelated: ['schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })

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
@@ -38,8 +38,8 @@ class SchoolRepository {
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({ withRelated: ['meta_schools']}) .fetch({ withRelated: ['meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
@@ -56,8 +56,8 @@ class SchoolRepository {
getSpellsFromOne(id) { getSpellsFromOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools']}) .fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
@@ -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)) {
@@ -138,8 +138,8 @@ class SchoolRepository {
"code": 403, "code": 403,
}); });
} else { } else {
new model({id: id}) new model({ id: id })
.fetch({require: true, withRelated: ['meta_schools']}) .fetch({ require: true, withRelated: ['meta_schools'] })
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return v.save({ return v.save({
@@ -183,8 +183,8 @@ class SchoolRepository {
deleteOne(id) { deleteOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({require: true, withRelated: ['spells', 'meta_schools']}) .fetch({ require: true, withRelated: ['spells', 'meta_schools'] })
.then(v => { .then(v => {
v.spells().detach() v.spells().detach()
v.destroy() v.destroy()

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
@@ -25,12 +25,12 @@ class SpellRepository {
if (name) { query.where('name', 'like', `%${name}%`) } if (name) { query.where('name', 'like', `%${name}%`) }
if (description) { query.where('description', 'like', `%${description}%`) } if (description) { query.where('description', 'like', `%${description}%`) }
if (level) { query.where({ 'level' : level }) } if (level) { query.where({ 'level': level }) }
if (charge) { query.where({ 'charge' : charge }) } if (charge) { query.where({ 'charge': charge }) }
if (cost) { query.where({ 'cost' : cost }) } if (cost) { query.where({ 'cost': cost }) }
if (ritual) { query.where({ 'is_ritual' : ritual }) } if (ritual) { query.where({ 'is_ritual': ritual }) }
query.fetchAll({ withRelated: [ 'schools.meta_schools', 'variables', 'ingredients', 'author' ] }) query.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
@@ -47,16 +47,16 @@ class SpellRepository {
getAllPublic(name, description, level, charge, cost, ritual) { getAllPublic(name, description, level, charge, cost, ritual) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let query = new model().where({ 'public' : 1 }) let query = new model().where({ 'public': 1 })
if (name) { query.where('name', 'like', `%${name}%`) } if (name) { query.where('name', 'like', `%${name}%`) }
if (description) { query.where('description', 'like', `%${description}%`) } if (description) { query.where('description', 'like', `%${description}%`) }
if (level) { query.where({ 'level' : level }) } if (level) { query.where({ 'level': level }) }
if (charge) { query.where({ 'charge' : charge }) } if (charge) { query.where({ 'charge': charge }) }
if (cost) { query.where({ 'cost' : cost }) } if (cost) { query.where({ 'cost': cost }) }
if (ritual) { query.where({ 'is_ritual' : ritual }) } if (ritual) { query.where({ 'is_ritual': ritual }) }
query.fetchAll({ withRelated: [ 'schools.meta_schools', 'variables', 'ingredients', 'author' ] }) query.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })); resolve(v.toJSON({ omitPivot: true }));
}) })
@@ -73,11 +73,11 @@ class SpellRepository {
getPage(page) { getPage(page) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'public' : 1 }) .where({ 'public': 1 })
.fetchPage({ .fetchPage({
pageSize: 20, pageSize: 20,
page: page, page: page,
withRelated: [ 'schools.meta_schools', 'variables', 'ingredients', 'author' ], withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'],
}) })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
@@ -95,8 +95,8 @@ class SpellRepository {
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({ withRelated: [ 'schools.meta_schools', 'variables', 'ingredients', 'author' ]}) .fetch({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
@@ -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)) {
@@ -134,9 +134,9 @@ class SpellRepository {
'name': s.name, 'name': s.name,
'description': s.description, 'description': s.description,
'level': s.level, 'level': s.level,
'charge' : s.charge, 'charge': s.charge,
'cost' : s.cost, 'cost': s.cost,
'is_ritual' : s.is_ritual 'is_ritual': s.is_ritual
}).save(null, { }).save(null, {
transacting: t transacting: t
}) })
@@ -170,7 +170,7 @@ class SpellRepository {
}) })
}) })
.then(v => { .then(v => {
return v.load([ 'schools.meta_schools', 'variables', 'ingredients', 'author' ]) return v.load(['schools.meta_schools', 'variables', 'ingredients', 'author'])
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id))
@@ -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)) {
@@ -205,17 +205,17 @@ class SpellRepository {
"code": 403, "code": 403,
}); });
} else { } else {
new model({id: id}) new model({ id: id })
.fetch({require: true, withRelated: [ 'schools.meta_schools', 'variables', 'ingredients', 'author' ]}) .fetch({ require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return v.save({ return v.save({
'name': s.name, 'name': s.name,
'description': s.description, 'description': s.description,
'level': s.level, 'level': s.level,
'charge' : s.charge, 'charge': s.charge,
'cost' : s.cost, 'cost': s.cost,
'is_ritual' : s.is_ritual 'is_ritual': s.is_ritual
}, { }, {
method: 'update', method: 'update',
transacting: t transacting: t
@@ -224,19 +224,19 @@ class SpellRepository {
.tap(spell => { .tap(spell => {
if (s.schools) { if (s.schools) {
let schools = spell.related('school'); let schools = spell.related('school');
return spell.schools().detach(schools, { transacting: t}); return spell.schools().detach(schools, { transacting: t });
} }
}) })
.tap(spell => { .tap(spell => {
if (s.variables) { if (s.variables) {
let variables = spell.related('variable'); let variables = spell.related('variable');
return spell.variables().detach(variables, { transacting: t}); return spell.variables().detach(variables, { transacting: t });
} }
}) })
.tap(spell => { .tap(spell => {
if (s.ingredients) { if (s.ingredients) {
let ingredients = spell.related('ingredient'); let ingredients = spell.related('ingredient');
return spell.ingredients().detach(ingredients, { transacting: t}); return spell.ingredients().detach(ingredients, { transacting: t });
} }
}) })
.tap(spell => { .tap(spell => {
@@ -269,7 +269,7 @@ class SpellRepository {
}) })
}) })
.then(v => { .then(v => {
return v.load([ 'schools.meta_schools', 'variables', 'ingredients', 'author' ]); return v.load(['schools.meta_schools', 'variables', 'ingredients', 'author']);
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)); resolve(this.getOne(v.id));
@@ -296,8 +296,8 @@ class SpellRepository {
deleteOne(id) { deleteOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({require: true, withRelated: [ 'schools.meta_schools', 'variables', 'ingredients', 'author' ]}) .fetch({ require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
v.schools().detach(); v.schools().detach();
v.variables().detach(); v.variables().detach();

View File

@@ -13,9 +13,9 @@ 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;
@@ -35,7 +35,7 @@ class UserRepository {
getAll() { getAll() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.fetchAll({ withRelated: [ 'role.permissions' ] }) .fetchAll({ withRelated: ['role.permissions'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })); resolve(v.toJSON({ omitPivot: true }));
}) })
@@ -70,8 +70,8 @@ class UserRepository {
} }
new model() new model()
.where({ 'uuid' : uuid }) .where({ 'uuid': uuid })
.fetch({ withRelated: [ 'role.permissions' ] }) .fetch({ withRelated: ['role.permissions'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true, visibility: !full })); resolve(v.toJSON({ omitPivot: true, visibility: !full }));
}) })
@@ -106,7 +106,7 @@ class UserRepository {
new model() new model()
.where({ 'mail': mail }) .where({ 'mail': mail })
.fetch({ withRelated: [ 'role' ] }) .fetch({ withRelated: ['role'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true, visibility: !full })); resolve(v.toJSON({ omitPivot: true, visibility: !full }));
}) })
@@ -138,7 +138,7 @@ class UserRepository {
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'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })); resolve(v.toJSON({ omitPivot: true }));
}) })
@@ -168,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)) {
@@ -252,7 +252,7 @@ class UserRepository {
verifyUser(token) { verifyUser(token) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'verification_token' : token }) .where({ 'verification_token': token })
.fetch() .fetch()
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
@@ -397,6 +397,91 @@ class UserRepository {
}) })
} }
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. * Check if the email that was input is available for account creation.
* *

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
@@ -39,8 +39,8 @@ class VariableRepository {
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({ withRelated: ['spells']}) .fetch({ withRelated: ['spells'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
@@ -57,8 +57,8 @@ class VariableRepository {
getSpellsFromOne(id) { getSpellsFromOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools']}) .fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
@@ -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)) {
@@ -137,8 +137,8 @@ class VariableRepository {
"code": 403, "code": 403,
}); });
} else { } else {
new model({id: id}) new model({ id: id })
.fetch({require: true, withRelated: ['spells']}) .fetch({ require: true, withRelated: ['spells'] })
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return v.save({ return v.save({
@@ -180,8 +180,8 @@ class VariableRepository {
deleteOne(id) { deleteOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new model() new model()
.where({ 'id' : id }) .where({ 'id': id })
.fetch({require: true, withRelated: ['spells']}) .fetch({ require: true, withRelated: ['spells'] })
.then(v => { .then(v => {
v.spells().detach() v.spells().detach()
v.destroy() v.destroy()

View File

@@ -1,5 +1,4 @@
'use strict' 'use strict'
// Router // Router
const express = require('express'); const express = require('express');
let router = express.Router(); let router = express.Router();
@@ -9,29 +8,6 @@ const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository(); const Users = new UserRepository();
// ROUTES // ROUTES
// ENTRY POINT
const authUser = (mail, password) => {
return Users.logUserToAPI(mail, password)
.catch(err => {
throw err
})
}
router.get('/login', async (req, res) => {
authUser(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(
{
"error": err.message,
"code": err.code
})
)
})
})
// GEN API TOKEN // GEN API TOKEN
const generateAPIToken = (mail, password) => { const generateAPIToken = (mail, password) => {
return Users.genAPIToken(mail, password) return Users.genAPIToken(mail, password)
@@ -50,4 +26,4 @@ router.get('/genToken', async (req, res) => {
}) })
}) })
module.exports = router module.exports = router;

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 // Connection
const connection = require('../database/bookshelf') const functions = require('../functions');
const functions = require('../functions')
// Repository // Repository
const IngredientRepository = require('../repositories/ingredient-repository'); const IngredientRepository = require('../repositories/ingredient-repository');

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 // Connection
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,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 // Connection
const connection = require('../database/bookshelf') const functions = require('../functions');
const functions = require('../functions')
// Repository // Repository
const SchoolRepository = require('../repositories/school-repository'); const SchoolRepository = require('../repositories/school-repository');

View File

@@ -1,12 +1,14 @@
'use strict' 'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // Connection
const connection = require('../database/bookshelf') const functions = require('../functions');
const functions = require('../functions')
// AuthGuard
const authGuard = require('./middleware/authGuard');
// Repository // Repository
const SpellReposity = require('../repositories/spell-repository'); const SpellReposity = require('../repositories/spell-repository');
@@ -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')
@@ -35,7 +39,7 @@ router.get('//:name?/:description?/:level?/:charge?/:cost?/:ritual?/', async (re
}) })
) )
}) })
}) })
// GET ALL ------------------ // GET ALL ------------------
const getSpells = (name, description, level, charge, cost, ritual) => { const getSpells = (name, description, level, charge, cost, ritual) => {
@@ -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')
@@ -59,7 +66,7 @@ router.get('/private/:name?/:description?/:level?/:charge?/:cost?/:ritual?/', as
}) })
) )
}) })
}) })
// GET SOME ------------------ // GET SOME ------------------
const getSomeSpells = (page) => { const getSomeSpells = (page) => {
@@ -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')
@@ -83,7 +92,7 @@ router.get('/page/:page', async (req, res) => {
}) })
) )
}) })
}) })
// GET ONE ------------------ // GET ONE ------------------
const getSpell = (id) => { const getSpell = (id) => {
@@ -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')
@@ -107,7 +118,7 @@ router.get('/:id/', async (req, res) => {
}) })
) )
}) })
}) })
// CREATE ONE ------------------ // CREATE ONE ------------------
@@ -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')
@@ -132,7 +146,7 @@ router.post('/', async (req, res) => {
}) })
) )
}) })
}) })
// UPDATE ONE ------------------ // UPDATE ONE ------------------
@@ -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')
@@ -157,7 +174,7 @@ router.put('/:id/', async (req, res) => {
}) })
) )
}) })
}) })
// DELETE ONE ------------------ // DELETE ONE ------------------
@@ -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')
@@ -182,7 +202,7 @@ router.delete('/:id/', async (req, res) => {
}) })
) )
}) })
}) })
// Param validations // Param validations
router.param('id', functions.paramIntCheck) router.param('id', functions.paramIntCheck)

View File

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

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 // Connection
const connection = require('../database/bookshelf') const functions = require('../functions');
const functions = require('../functions')
// Repository // Repository
const VariableRepository = require('../repositories/variable-repository'); const VariableRepository = require('../repositories/variable-repository');

View File

@@ -8,7 +8,7 @@ const sender = 'tymos@auracle.io';
// Fetches a HTML template file for parsing // Fetches a HTML template file for parsing
const getTemplateFile = (path) => { const getTemplateFile = (path) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.readFile(path, {encoding: 'utf-8'}, (err, html) => { fs.readFile(path, { encoding: 'utf-8' }, (err, html) => {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<head>
<title>Template Email Auracle Inscription</title> <title>Template Email Auracle Inscription</title>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&family=Playfair+Display:wght@700;800;900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&family=Playfair+Display:wght@700;800;900&display=swap');
@@ -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));
@@ -76,7 +79,8 @@
padding: 30px; padding: 30px;
} }
</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>