Commit 78447232 authored by Nelson Gonçalves's avatar Nelson Gonçalves
Browse files

New etherpad plugin for laclasse:

- Handles import/export via apiKey
- WIP Webhook to trigger autosave
parent 3eb7bc77
......@@ -10,5 +10,6 @@ COPY ./settings.json.tmpl /app/settings.json.tmpl
COPY ./ /
COPY ./nginx/default.conf.tmpl /etc/nginx/conf.d/default.conf.tmpl
COPY ./pad.js /app/src/static/custom/pad.js
COPY ./ep_laclasse/ /app/node_modules/ep_laclasse/
CMD [ "/" ]
......@@ -45,6 +45,9 @@ while [[ $loop_count -lt 10 ]] && [[ $success != 1 ]]; do
# sym links from etherpad
ln -s /app/node_modules/ep_etherpad-lite/node_modules /app/node_modules/ep_laclasse/node_modules
set -e
export NODE_ENV=production
## ep_laclasse
This plugin extends etherpad's functionality to better integrate with laclasse
With this plugin you can:
* Authenticate with SSO
* Import and Export Pad using the same API_KEY that's used in Etherpad HTTP API. This allows us to use the .etherpad file format (a JSON file containing an history of all the revisions)
* Detect when user leave in order to trigger an save from laclasse-service
\ No newline at end of file
"name": "ep_laclasse_plugin",
"userLeave": "ep_laclasse/userLeave.js",
"expressCreateServer": "ep_laclasse/httpApi.js"
\ No newline at end of file
var settings = require('ep_etherpad-lite/node/utils/Settings');
var padManager = require("ep_etherpad-lite/node/db/PadManager");
const rateLimit = require("express-rate-limit");
var exportHandler = require('ep_etherpad-lite/node/handler/ExportHandler');
var importHandler = require('ep_etherpad-lite/node/handler/ImportHandler');
const absolutePaths = require('ep_etherpad-lite/node/utils/AbsolutePaths');
const fs = require("fs");
const log4js = require('log4js');
var createHTTPError = require('http-errors');
laclasseLogger = log4js.getLogger("ep_laclasse");
//ensure we have an apikey
var apikey = null;
var apikeyFilename = absolutePaths.makeAbsolute("./APIKEY.txt");
try {
apikey = fs.readFileSync(apikeyFilename,"utf8");`Api key file read from: "${apikeyFilename}"`);
} catch(e) {`Api key file "${apikeyFilename}" not found. Laclasse specific API are disabled`);
settings.importExportRateLimiting.onLimitReached = function(req, res, options) {
// when the rate limiter triggers, write a warning in the logs
laclasseLogger.warn(`Import/Export rate limiter triggered on "${req.originalUrl}" for IP address ${req.ip}`);
var limiter = rateLimit(settings.importExportRateLimiting);
exports.expressCreateServer = function (hook_name, args, cb) {
// handle export requests'/laclasse/:pad/:rev?/export/:type', limiter);'/laclasse/:pad/:rev?/export/:type', async function(req, res, next) {
var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"];
//send a 404 if we don't support this filetype
if (types.indexOf(req.params.type) == -1) {
return next();
// if abiword is disabled, and this is a format we only support with abiword, output a message
if (settings.exportAvailable() == "no" &&
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) {
laclasseLogger.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format. There is no converter configured`);
// ACHTUNG: do not include req.params.type in res.send() because there is no HTML escaping and it would lead to an XSS
res.send("This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature");
res.header("Access-Control-Allow-Origin", "*");
const apiKeyParam = req.query.apikey || req.query.api_key;
if (apiKeyParam !== apikey.trim()) {
res.statusCode = 401;
return res.send({ code: 4, message: 'no or wrong API Key', data: null });
let exists = await padManager.doesPadExists(req.params.pad);
if (!exists) {
laclasseLogger.warn(`Laclasse tried to export a pad that doesn't exist (${req.params.pad})`);
return next();
}`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
exportHandler.doExport(req, res, req.params.pad, req.params.type);
// handle import requests'/laclasse/:pad/import', limiter);'/laclasse/:pad/import', async function(req, res, next) {
if (!(await padManager.doesPadExists(req.params.pad))) {
laclasseLogger.warn(`Laclasse tried to import into a pad that doesn't exist (${req.params.pad})`);
return next();
const apiKeyParam = req.params.apikey || req.params.api_key;
if (apiKeyParam !== apikey.trim()) {
res.statusCode = 401;
return res.send({ code: 4, message: 'no or wrong API Key', data: null });
importHandler.doImport(req, res, req.params.pad);
"name": "ep_laclasse",
"description": "Provides laclasse specific features: ",
"version": "0.0.1",
"author": {
"name": "Nelson Dinis Gonçalves",
"email": "",
"url": ""
"repository": {
"type": "git",
"url": ""
"contributors": [],
"peerDependencies": {
"express-rate-limit": "5.1.X",
"http-errors": "1.7.X",
"log4js": "0.6.X"
"license": "MIT"
\ No newline at end of file
var PadMessageHandler = require("ep_etherpad-lite/node/handler/PadMessageHandler.js");
exports.userLeave = function(hook, session, callback) {
const userCount = PadMessageHandler.padUsersCount(session.padId).padUsersCount
console.log('%s left pad %s - %d user(s) still editing the pad',, session.padId, userCount);
* 2- If no author call a webhook on laclasse-service to trigger saving of file
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment