diff --git a/api/controllers/CodesController.js b/api/controllers/CodesController.js index f5905b3..1d65b45 100644 --- a/api/controllers/CodesController.js +++ b/api/controllers/CodesController.js @@ -73,7 +73,7 @@ module.exports = { // Add the rewards to the user const promises = Object.keys(codeEntity.loots).map((lootName) => { return fetch( - `${process.env.ARENA_URL}/wizzard/reward/${req.user_id}`, + `${process.env.ARENA_URL}/wizard/${req.user_id}/reward`, { method: 'POST', body: JSON.stringify({ diff --git a/api/controllers/ReportController.js b/api/controllers/ReportController.js new file mode 100644 index 0000000..6986e64 --- /dev/null +++ b/api/controllers/ReportController.js @@ -0,0 +1,87 @@ +/** + * RulesController + * + * @description :: Server-side actions for handling incoming requests. + * @help :: See https://sailsjs.com/docs/concepts/actions + */ + +module.exports = { + + async index(req, res) { + const id = parseInt(req.param('id')); + if (id === 0 || isNaN(id)) { + return res.notFound(); + } + + return res.view( + 'pages/report.ejs', + { + ...await sails.helpers.layoutConfig(req.user_id), + errors: [], + } + ); + }, + + async report(req, res) { + const id = parseInt(req.param('id')); + if (id === 0 || isNaN(id)) { + return res.notFound(); + } + + const reason = req.body.reason; + const details = req.body.details; + + const errors = []; + if (!['cheat', 'inappropriate-behavior-insults', 'harassment-or-intimidation'].includes(reason)) { + errors.push('report.errorInvalidReason'); + } + if (typeof(details) !== "string" || details.length < 10) { + errors.push('report.errorMoreDetails'); + } + + if (errors.length) { + return res.view( + 'pages/report.ejs', + { + ...await sails.helpers.layoutConfig(req.user_id), + errors, + reason, + details, + } + ); + } + + const nodemailer = require('nodemailer'); + const smtpTransport = require('nodemailer-smtp-transport'); + const transport = nodemailer.createTransport(smtpTransport(process.env.SMTP_TRANSPORT)); + var mailOptions = { + from: 'TFS report abuse form ', // sender address + to: process.env.REPORT_TO, // list of receivers + subject: 'Someone submitted an abuse ticket', // Subject line + text: `Player ID of sumbmitter: "${req.user_id}" +Targetted player ID: "${id}" + +Reason: ${reason} + +Details: ${details} +`, // plaintext body + }; + + transport.sendMail(mailOptions, function(error, info){ + if(error) { + console.log(error); + } else { + console.log('Message sent: ' , info); + } + }); + + return res.view( + 'pages/report.ejs', + { + ...await sails.helpers.layoutConfig(req.user_id), + message: 'report.messageReportSent', + } + ); + }, + +}; diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js index b476090..04ae3e7 100644 --- a/api/controllers/UserController.js +++ b/api/controllers/UserController.js @@ -249,7 +249,7 @@ module.exports = { const response = await fetch( - `${process.env.ARENA_URL}/wizzard`, + `${process.env.ARENA_URL}/wizard`, { method: 'GET', headers: { @@ -277,7 +277,7 @@ module.exports = { const errors = []; const response = await fetch( - `${process.env.ARENA_URL}/wizzard`, + `${process.env.ARENA_URL}/wizard`, { method: 'GET', headers: { diff --git a/api/policies/is-admin.js b/api/policies/is-admin.js index 83b4321..da9bb0c 100644 --- a/api/policies/is-admin.js +++ b/api/policies/is-admin.js @@ -21,6 +21,6 @@ module.exports = async function (req, res, proceed) { //--• // Otherwise, this request did not come from a logged-in user. - return res.forbidden(); + return res.forbidden('not-admin'); }; diff --git a/api/policies/is-logged-in.js b/api/policies/is-logged-in.js index 17b18a2..412a6ed 100644 --- a/api/policies/is-logged-in.js +++ b/api/policies/is-logged-in.js @@ -13,6 +13,6 @@ module.exports = async function (req, res, proceed) { //--• // Otherwise, this request did not come from a logged-in user. - return res.forbidden(); + return res.forbidden('not-logged-in'); }; diff --git a/api/responses/forbidden.js b/api/responses/forbidden.js new file mode 100644 index 0000000..af3bc93 --- /dev/null +++ b/api/responses/forbidden.js @@ -0,0 +1,73 @@ +/** + * forbidden.js + * + * A custom response. + * + * Example usage: + * ``` + * return res.forbidden(); + * // -or- + * return res.forbidden(optionalData); + * ``` + * + * Or with actions2: + * ``` + * exits: { + * somethingHappened: { + * responseType: 'forbidden' + * } + * } + * ``` + * + * ``` + * throw 'somethingHappened'; + * // -or- + * throw { somethingHappened: optionalData } + * ``` + */ + +const { option } = require("grunt"); + +module.exports = function forbidden(optionalData) { + + // Get access to `req` and `res` + const req = this.req; + const res = this.res; + + // Define the status code to send in the response. + var statusCodeToSet = 403; + + // If no data was provided, use res.sendStatus(). + if (optionalData === undefined) { + sails.log.info('Ran custom response: res.forbidden()'); + return res.sendStatus(statusCodeToSet); + } + // Else if the provided data is an Error instance, if it has + // a toJSON() function, then always run it and use it as the + // response body to send. Otherwise, send down its `.stack`, + // except in production use res.sendStatus(). + else if (_.isError(optionalData)) { + sails.log.info('Custom response `res.forbidden()` called with an Error:', optionalData); + + // If the error doesn't have a custom .toJSON(), use its `stack` instead-- + // otherwise res.json() would turn it into an empty dictionary. + // (If this is production, don't send a response body at all.) + if (!_.isFunction(optionalData.toJSON)) { + if (process.env.NODE_ENV === 'production') { + return res.sendStatus(statusCodeToSet); + } + else { + return res.status(statusCodeToSet).send(optionalData.stack); + } + } + } + // Redirect in case of 'not-logged-in' reason + else if (optionalData === 'not-logged-in') { + return res.redirect(`/login?redirect=${req.originalUrl.substr(1)}`); + } + // Set status code and send response data. + else { + return res.status(statusCodeToSet).send(optionalData); + } + +}; diff --git a/config/locales/en.json b/config/locales/en.json index 770c269..b8b5287 100644 --- a/config/locales/en.json +++ b/config/locales/en.json @@ -299,5 +299,27 @@ "error-length": "Password must be at least 8 characters long.", "error-unkown": "Password change failed.", "message-passwordChanged": "Password has been changed." + }, + "report": { + "title": "Report a player", + "form": "Reporting form", + "reason": "Reason for reporting", + "chose": "Choose...", + "cheat": "Cheat", + "inappropriate-behavior-insults": "Inappropriate behavior, insults", + "harassment-or-intimidation": "Harassment or intimidation", + "moreDetails": "Give us more details about your report", + "detailsPlaceholder": "Be as specific as possible, with timetables and copies of your transcripts. We will not be able to take the necessary measures if the slightest doubt remains.", + "reportPlayer": "Report this player", + "sanctions": "The sanctions applied", + "sanctionsText": "Here are the floor sanctions we apply. If we find aggravating circumstances (installing spyware on another player's client, for example), we reserve the right to add curses or apply a permanent ban immediately.", + "penalty": "Penalty without recurrence", + "penaltyRecurrence": "Penalty if recurrence", + "penaltyRecurrence2": "Penalty for second recidivism", + "curse": "Curse", + "banishment": "Banishment", + "errorInvalidReason": "Invalid reason", + "errorMoreDetails": "Please give more details", + "messageReportSent": "Your report was sent!" } } \ No newline at end of file diff --git a/config/locales/fr.json b/config/locales/fr.json index ab8dc06..240e815 100644 --- a/config/locales/fr.json +++ b/config/locales/fr.json @@ -299,5 +299,27 @@ "error-length": "Le mot de passe doit avoir au moins 8 caractères.", "error-unkown": "Le changement de mot de passe a échoué.", "message-passwordChanged": "Le mot de passe a été modifié." + }, + "report": { + "title": "Signaler un joueur", + "form": "Formulaire de signalement", + "reason": "Raison du signalement", + "chose": "Choisissez...", + "cheat": "Triche", + "inappropriate-behavior-insults": "Comportement inapproprié, injures", + "harassment-or-intimidation": "Harcèlement ou intimidation", + "moreDetails": "Donnez-nous plus de détails à propos de votre signalement", + "detailsPlaceholder": "Soyez le plus précis possible, avec des horaires et des copies de vos transcriptions. Nous ne pourrons pas prendre les mesures nécessaires si le moindre doute subsiste.", + "reportPlayer": "Signaler ce joueur", + "sanctions": "Les sanctions appliquées", + "sanctionsText": "Voici les sanctions plancher que nous appliquons. Si nous constatons des circonstances aggravantes (installation d'un logiciel espion sur le client d'un autre joueur par exemple), nous nous réservons le droit d'ajouter des malédictions ou d'appliquer un bannissement définitif immédiatement.", + "penalty": "Sanction sans récidive", + "penaltyRecurrence": "Sanction si récidive", + "penaltyRecurrence2": "Sanction à la seconde récidive", + "curse": "Malédiction", + "banishment": "Bannissement définitif", + "errorInvalidReason": "Raison invalide", + "errorMoreDetails": "Donnez plus de détails", + "messageReportSent": "Votre signalement a été envoyé !" } } \ No newline at end of file diff --git a/config/policies.js b/config/policies.js index 65d06a4..d798e83 100644 --- a/config/policies.js +++ b/config/policies.js @@ -30,6 +30,10 @@ module.exports.policies = { 'submitSubscription': ['load-user', 'set-locale'], }, + ReportController: { + '*': ['load-user', 'set-locale', 'is-logged-in'], + }, + // Blueprint API 'news/*':['load-user', 'is-logged-in', 'is-admin'], 'news/find':[], diff --git a/config/routes.js b/config/routes.js index 6d8454a..76d6925 100644 --- a/config/routes.js +++ b/config/routes.js @@ -75,5 +75,8 @@ module.exports.routes = { 'GET /codes': 'CodesController.viewForm', 'GET /codes/use/:code': 'CodesController.useCode', + // Report + 'GET /report/:id': 'ReportController.index', + 'POST /report/:id': 'ReportController.report', }; diff --git a/package-lock.json b/package-lock.json index 333c540..d46529c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1936,6 +1936,27 @@ "sshpk": "^1.7.0" } }, + "httpntlm": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", + "integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=", + "requires": { + "httpreq": ">=0.4.22", + "underscore": "~1.7.0" + }, + "dependencies": { + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" + } + } + }, + "httpreq": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz", + "integrity": "sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=" + }, "i": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", @@ -2962,6 +2983,39 @@ } } }, + "nodemailer": { + "version": "6.4.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.10.tgz", + "integrity": "sha512-j+pS9CURhPgk6r0ENr7dji+As2xZiHSvZeVnzKniLOw1eRAyM/7flP0u65tCnsapV8JFu+t0l/5VeHsCZEeh9g==" + }, + "nodemailer-fetch": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", + "integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=" + }, + "nodemailer-shared": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", + "integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=", + "requires": { + "nodemailer-fetch": "1.6.0" + } + }, + "nodemailer-smtp-transport": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.4.tgz", + "integrity": "sha1-DYmvAZoUSkgP2OzJmZfZ+DjxNoU=", + "requires": { + "nodemailer-shared": "1.1.0", + "nodemailer-wellknown": "0.1.10", + "smtp-connection": "2.12.0" + } + }, + "nodemailer-wellknown": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", + "integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=" + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -7798,6 +7852,15 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "smtp-connection": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", + "integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=", + "requires": { + "httpntlm": "1.6.1", + "nodemailer-shared": "1.1.0" + } + }, "socket.io": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.2.0.tgz", diff --git a/package.json b/package.json index 7047711..c9d3f11 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "moment": "^2.27.0", "node-fetch": "^2.6.0", "node-sass": "^4.14.1", + "nodemailer": "^6.4.10", + "nodemailer-smtp-transport": "^2.7.4", "sails": "^1.2.4", "sails-hook-grunt": "^4.0.0", "sails-hook-orm": "^2.1.1", diff --git a/views/pages/report.ejs b/views/pages/report.ejs new file mode 100644 index 0000000..6511744 --- /dev/null +++ b/views/pages/report.ejs @@ -0,0 +1,93 @@ +<%- include('../elements/nav.ejs') %> + +
+
+
+

<%= __("report.title") %>

+
+
+ +
+ +
+
+
+ <% if(typeof(errors) !== 'undefined' && errors && errors.length) { %> + <% errors.forEach(function(error){ %> +
+
+ <%= __(error) %> +
+
+ <% }); %> + <% } %> + <% if(typeof(message) !== 'undefined') { %> +
+
+ <%= __(message) %> +
+
+ <% } %> +

<%= __("report.form") %>

+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+
+ " /> +
+
+

<%= __("report.sanctions") %>

+

<%= __("report.sanctionsText") %>

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%= __("report.reason") %><%= __("report.penalty") %><%= __("report.penaltyRecurrence") %><%= __("report.penaltyRecurrence2") %>
<%= __("report.cheat") %><%= __("report.curse") %> x 2<%= __("report.curse") %> x 4<%= __("report.banishment") %>
<%= __("report.inappropriate-behavior-insults") %><%= __("report.curse") %> x 3<%= __("report.banishment") %><%= __("report.banishment") %>
<%= __("report.harassment-or-intimidation") %><%= __("report.banishment") %><%= __("report.banishment") %><%= __("report.banishment") %>
+
+
+
+
+ +<%- include('../elements/footer.ejs') %>