UNPKG

51 kBJavaScriptView Raw
1/**
2* vim:set sw=2 ts=2 sts=2 ft=javascript expandtab:
3*
4* # API Module
5*
6* ## License
7*
8* Licensed to the Apache Software Foundation (ASF) under one
9* or more contributor license agreements. See the NOTICE file
10* distributed with this work for additional information
11* regarding copyright ownership. The ASF licenses this file
12* to you under the Apache License, Version 2.0 (the
13* "License"); you may not use this file except in compliance
14* with the License. You may obtain a copy of the License at
15*
16* http://www.apache.org/licenses/LICENSE-2.0
17*
18* Unless required by applicable law or agreed to in writing,
19* software distributed under the License is distributed on an
20* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21* KIND, either express or implied. See the License for the
22* specific language governing permissions and limitations
23* under the License.
24*
25* ## Description
26*
27* This module holds all public functions, used for the API of mypads.
28* Please refer to binded function when no details are given.
29*/
30
31var rFS = require('fs').readFileSync;
32// External dependencies
33var ld = require('lodash');
34var passport = require('passport');
35var jwt = require('jsonwebtoken');
36var express;
37var testMode = false;
38try {
39 // Normal case : when installed as a plugin
40 express = require('ep_etherpad-lite/node_modules/express');
41}
42catch (e) {
43 // Testing case : we need to mock the express dependency
44 testMode = true;
45 express = require('express');
46}
47var settings;
48try {
49 // Normal case : when installed as a plugin
50 settings = require('ep_etherpad-lite/node/utils/Settings');
51}
52catch (e) {
53 // Testing case : we need to mock the express dependency
54 if (process.env.TEST_LDAP) {
55 settings = {
56 'ep_mypads': {
57 'ldap': {
58 'url': 'ldap://rroemhild-test-openldap',
59 'bindDN': 'cn=admin,dc=planetexpress,dc=com',
60 'bindCredentials': 'GoodNewsEveryone',
61 'searchBase': 'ou=people,dc=planetexpress,dc=com',
62 'searchFilter': '(uid={{username}})',
63 'properties': {
64 'login': 'uid',
65 'email': 'mail',
66 'firstname': 'givenName',
67 'lastname': 'sn'
68 },
69 'defaultLang': 'fr'
70 }
71 }
72 };
73 } else {
74 settings = {};
75 }
76}
77var bodyParser = require('body-parser');
78var cookieParser = require('cookie-parser');
79var decode = require('js-base64').Base64.decode;
80// Local dependencies
81var conf = require('./configuration.js');
82var mail = require('./mail.js');
83var user = require('./model/user.js');
84var userCache = require('./model/user-cache.js');
85var group = require('./model/group.js');
86var pad = require('./model/pad.js');
87var auth = require('./auth.js');
88var perm = require('./perm.js');
89
90module.exports = (function () {
91 'use strict';
92
93 var api = {};
94 api.initialRoute = '/mypads/api/';
95 var authAPI;
96 var configurationAPI;
97 var userAPI;
98 var groupAPI;
99 var padAPI;
100 var cacheAPI;
101 var statsAPI;
102
103 /**
104 * `init` is the first function that takes an Express app as argument.
105 * It initializes authentication, permissions and also all API requirements,
106 * particularly mypads routes.
107 *
108 * It needs to be fast to finish *before* YAJSML plugin (which takes over all
109 * requests otherwise and refuse anything apart GET and HEAD HTTP methods.
110 * That's why api.init is used without any callback and is called before
111 * storage initialization.
112 */
113
114 api.init = function (app, callback) {
115 // Use this for .JSON storage
116 app.use(bodyParser.json());
117 app.use(cookieParser());
118 app.use('/mypads', express.static(__dirname + '/static'));
119 if (testMode) {
120 // Only allow functional testing in testing mode
121 app.use('/mypads/functest', express.static(__dirname + '/spec/frontend'));
122 }
123 var rFSOpts = { encoding: 'utf8' };
124 api.l10n = {
125 mail: {
126 de: JSON.parse(rFS(__dirname + '/templates/mail_de.json', rFSOpts)),
127 en: JSON.parse(rFS(__dirname + '/templates/mail_en.json', rFSOpts)),
128 es: JSON.parse(rFS(__dirname + '/templates/mail_es.json', rFSOpts)),
129 fr: JSON.parse(rFS(__dirname + '/templates/mail_fr.json', rFSOpts)),
130 }
131 };
132 auth.init(app);
133 authAPI(app);
134 configurationAPI(app);
135 userAPI(app);
136 groupAPI(app);
137 padAPI(app);
138 cacheAPI(app);
139 statsAPI(app);
140 perm.init(app);
141
142 /**
143 * `index.html` is a simple lodash template
144 */
145 var idxTpl = ld.template(rFS(__dirname + '/templates/index.html', rFSOpts));
146 var idxHandle = function (req, res) {
147 var cssTag = (conf.get('hideHelpBlocks')) ? '<link href="css/mypads-hide-help-blocks.css" rel="stylesheet">' : '';
148 return res.send(idxTpl({ HTMLExtraHead: conf.get('HTMLExtraHead'), hideHelpBlocks: cssTag }));
149 };
150 app.get('/mypads/index.html', idxHandle);
151 app.get('/mypads/', idxHandle);
152
153 callback(null);
154 };
155
156 /**
157 * ## Internal functions helpers
158 *
159 * These functions are not private like with closures, for testing purposes,
160 * but they are expected be used only internally by other MyPads functions.
161 */
162
163 var fn = {};
164
165 /**
166 * `get` internal takes a mandatory `module` argument to call its `get` method.
167 * It will use `req.params.key` to get the database record and returns an
168 * according response. By the way, an optional `cond`ition can be passed. It
169 * is a function that takes the result of the module.get and *true* or
170 * *false*. If *false*, an error will be returned.
171 */
172
173 fn.get = function (module, req, res, next, cond) {
174 try {
175 module.get(req.params.key, function (err, val) {
176 if (err) {
177 return res.status(404).send({
178 error: err.message,
179 key: req.params.key
180 });
181 }
182 if (cond && !cond(val)) {
183 if (conf.get('allPadsPublicsAuthentifiedOnly')) {
184 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.MUST_BE');
185 } else {
186 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.DENIED_RECORD');
187 }
188 } else {
189 return res.send({ key: req.params.key, value: val });
190 }
191 });
192 }
193 catch (e) {
194 res.status(400).send({ error: e.message });
195 }
196 };
197
198 /**
199 * `set` internal takes :
200 *
201 * - a `setFn` bounded function targetted the original `set` from the module
202 * used in the case of this public API
203 * - `key` and `value` that has been given to the `setFn` function
204 * - `req` and `res` express request and response
205 */
206
207 fn.set = function (setFn, key, value, req, res) {
208 try {
209 setFn(function (err, data) {
210 if (err) { return res.status(400).send({ error: err.message }); }
211 res.send({ success: true, key: key || data._id, value: data || value });
212 });
213 }
214 catch (e) {
215 res.status(400).send({ error: e.message });
216 }
217 };
218
219 /**
220 * `del` internal takes four arguments :
221 *
222 * - `delFn` bounded function targetted the original `del` method from the
223 * module used
224 * - classical `req` and `res` express parameters, with mandatory
225 * *req.params.key*.
226 */
227
228 fn.del = function (delFn, req, res) {
229 var key = req.params.key;
230 delFn(key, function (err) {
231 if (err) { return res.status(404).send({ error: err.message }); }
232 res.send({ success: true, key: key });
233 });
234 };
235
236 /**
237 * `denied` is an internal helper that just takes `res` express response and
238 * an `errorCode` string. It returns an unothorized status.
239 */
240
241 fn.denied = function (res, errCode) {
242 return res
243 .status(401)
244 .send({ error: errCode });
245 };
246
247 /**
248 * `mailMessage` is an internal helper that computed email templates. It takes
249 * the `tpl` key of the template and the `data` needed. Optionally, a
250 * `lang`uage can be defined.
251 * It returns the computed template.
252 */
253
254 fn.mailMessage = function (tpl, data, lang) {
255 lang = lang || conf.get('defaultLanguage');
256 tpl = ld.template(api.l10n.mail[lang][tpl]);
257 return tpl(data);
258 };
259
260
261 /**
262 * `ensureAuthenticated` internal is an Express middleware takes `req`,
263 * `res` and `next`. It returns error or lets the next middleware go.
264 * It relies on `passport.authenticate` with default strategy : *jwt*.
265 */
266
267 fn.ensureAuthenticated = passport.authenticate('jwt', { session: false });
268
269 /**
270 * `isAdmin` internal is a function that checks if current connnected user is
271 * an Etherpad instance admin or not. It returns a boolean.
272 */
273
274 fn.isAdmin = function (req) {
275 var token = req.query.auth_token || req.body.auth_token;
276 if (!token) { return false; }
277 try {
278 var jwt_payload = jwt.verify(token, auth.secret);
279 var admin = auth.adminTokens[jwt_payload.login];
280 return (admin && (admin.key === jwt_payload.key));
281 }
282 catch (e) { return false; }
283 };
284
285 /**
286 * `ensureAdmin` internal is an Express middleware that takes classic `req`,
287 * `res` and `next`. It returns an error if the connected user is not
288 * an Etherpad instance admin.
289 */
290
291 fn.ensureAdmin = function (req, res, next) {
292 if (!fn.isAdmin(req)) {
293 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.ADMIN');
294 } else {
295 return next();
296 }
297 };
298
299 /**
300 * `ensureAdminOrSelf` internal Express middleware takes the classic `req`,
301 * `res` and `next`. It returns an error if the connected user tries to manage
302 * users other than himself and he is not an Etherpad logged admin.
303 */
304
305 fn.ensureAdminOrSelf = function (req, res, next) {
306 var isAdmin = fn.isAdmin(req);
307
308 var token = (req.body.auth_token || req.query.auth_token);
309 var login = req.params.key;
310 var u = auth.fn.getUser(token);
311 var isSelf = (u && login === u.login);
312 if (isSelf) { req.mypadsLogin = u.login; }
313
314 if (!isAdmin && !isSelf) {
315 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.DENIED');
316 } else {
317 return next();
318 }
319 };
320
321 /**
322 * ## Authentication API
323 */
324
325 authAPI = function (app) {
326 var authRoute = api.initialRoute + 'auth';
327
328 /**
329 * POST method : check, method returning success or error if given *login*
330 * and *password* do not match to what is stored into database
331 *
332 * Sample URL:
333 * http://etherpad.ndd/mypads/api/auth/check
334 */
335
336 app.post(authRoute + '/check', fn.ensureAuthenticated,
337 function (req, res) {
338 auth.fn.localFn(req.body.login, req.body.password, function (err, u) {
339 if (err) { return res.status(400).send({ error: err.message }); }
340 if (!u) {
341 var emsg = 'BACKEND.ERROR.AUTHENTICATION.PASSWORD_INCORRECT';
342 return res.status(400).send({ error: emsg });
343 }
344 res.status(200).send({ success: true });
345 });
346 }
347 );
348
349 /**
350 * GET method : login/cas, method redirecting to home if auth
351 * is a success, plus fixes a `login` session.
352 * Only used with CAS authentication
353 *
354 * Sample URL:
355 * http://etherpad.ndd/mypads/api/auth/login/cas
356 */
357
358 app.post(authRoute + '/login/cas', function (req, res) {
359 auth.fn.casAuth(req, res, function(err, infos) {
360 if (err) { return res.status(400).send({ error: err.message }); }
361 // JWTFn false arg = non-admin authentication
362 auth.fn.JWTFn(req, infos, false, function (err, u, info) {
363 if (err) { return res.status(400).send({ error: err.message }); }
364 if (!u) { return res.status(400).send({ error: info.message }); }
365 if (u.active) {
366 var token = {
367 login: u.login,
368 key: auth.tokens[u.login].key
369 };
370 return res.status(200).send({
371 success: true,
372 user: ld.omit(u, 'password'),
373 token: jwt.sign(token, auth.secret)
374 });
375 } else {
376 var msg = 'BACKEND.ERROR.AUTHENTICATION.ACTIVATION_NEEDED';
377 return fn.denied(res, msg);
378 }
379 });
380 });
381 });
382
383 /**
384 * POST method : login, method returning user object minus password if auth
385 * is a success, plus fixes a `login` session.
386 *
387 * Sample URL:
388 * http://etherpad.ndd/mypads/api/auth/login
389 */
390
391 app.post(authRoute + '/login', function (req, res) {
392 var eplAuthorToken;
393 if (typeof(req.body.login) !== 'undefined' && typeof(req.cookies['token-'+req.body.login]) !== 'undefined') {
394 eplAuthorToken = req.cookies['token-'+req.body.login];
395 } else if (typeof(req.cookies.token) !== 'undefined') {
396 eplAuthorToken = req.cookies.token;
397 }
398 auth.fn.JWTFn(req, req.body, false, function (err, u, info) {
399 if (err) { return res.status(400).send({ error: err.message }); }
400 if (!u) { return res.status(400).send({ error: info.message }); }
401 if (u.active) {
402 var token = {
403 login: u.login,
404 key: auth.tokens[u.login].key
405 };
406 if (!u.eplAuthorToken && typeof(eplAuthorToken) !== 'undefined') {
407 u.eplAuthorToken = eplAuthorToken;
408 var uToUpdate = ld.cloneDeep(u);
409 uToUpdate.password = req.body.password;
410 user.set(uToUpdate, function (err) {
411 if (err) { return res.status(400).send({ error: err.message }); }
412 return res.status(200).send({
413 success: true,
414 user: ld.omit(u, 'password'),
415 token: jwt.sign(token, auth.secret)
416 });
417 });
418 } else {
419 return res.status(200).send({
420 success: true,
421 user: ld.omit(u, 'password'),
422 token: jwt.sign(token, auth.secret)
423 });
424 }
425 } else {
426 var msg = 'BACKEND.ERROR.AUTHENTICATION.ACTIVATION_NEEDED';
427 return fn.denied(res, msg);
428 }
429 });
430 });
431
432 /**
433 * GET method : logout, method that destroy current cached token
434 *
435 * Sample URL:
436 * http://etherpad.ndd/mypads/api/auth/logout
437 */
438
439 app.get(authRoute + '/logout', fn.ensureAuthenticated, function (req, res) {
440 delete auth.tokens[req.mypadsLogin];
441 res.status(200).send({ success: true });
442 });
443
444 /**
445 * POST method : admin login, method that checks credentials and create
446 * admin token in case of success
447 *
448 * Sample URL:
449 * http://etherpad.ndd/mypads/api/auth/admin/login
450 */
451
452 app.post(authRoute + '/admin/login', function (req, res) {
453 auth.fn.JWTFn(req, req.body, true, function (err, u, info) {
454 if (err) { return res.status(400).send({ error: err.message }); }
455 if (!u) { return res.status(400).send({ error: info.message }); }
456 var token = {
457 login: u.login,
458 key: auth.adminTokens[u.login].key
459 };
460 return res.status(200).send({
461 success: true,
462 token: jwt.sign(token, auth.secret)
463 });
464 });
465 });
466
467 /**
468 * GET method : admin logout, method that destroy current admin token
469 *
470 * Sample URL:
471 * http://etherpad.ndd/mypads/api/auth/admin/logout
472 */
473
474 app.get(authRoute + '/admin/logout', fn.ensureAdmin, function (req, res) {
475 delete auth.adminTokens[req.mypadsLogin];
476 return res.status(200).send({ success: true });
477 });
478
479 };
480
481 /**
482 * ## Configuration API
483 */
484
485 configurationAPI = function (app) {
486 var confRoute = api.initialRoute + 'configuration';
487
488 /**
489 * GET method : get all configuration plus user info if logged, else config
490 * public fields.
491 *
492 * Sample URL:
493 * http://etherpad.ndd/mypads/api/configuration
494 */
495
496 app.get(confRoute, function (req, res) {
497 var u = auth.fn.getUser(req.query.auth_token);
498 var isAdmin = fn.isAdmin(req);
499 var action = (isAdmin === true) ? 'all' : 'public';
500 var value = conf[action]();
501 var resp = { value: value };
502 resp.auth = isAdmin || !!u;
503 if (u) { resp.user = u; }
504 res.send(resp);
505 });
506
507 /**
508 * GET method : `configuration.get` key
509 * Reserved to Etherpad administrators
510 *
511 * Sample URL:
512 * http://etherpad.ndd/mypads/api/configuration/something
513 */
514
515 app.get(confRoute + '/:key', fn.ensureAdmin, function (req, res) {
516 var value = conf.get(req.params.key);
517 if (ld.isUndefined(value)) {
518 return res.status(404).send({
519 error: 'BACKEND.ERROR.CONFIGURATION.KEY_NOT_FOUND',
520 key: req.params.key
521 });
522 }
523 res.send({ key: req.params.key, value: value });
524 });
525
526 /**
527 * POST/PUT methods : `configuration.set` key and value on initial
528 * Reserved to Etherpad administrators
529 *
530 * Sample URL for POST:
531 * http://etherpad.ndd/mypads/api/configuration
532 * for PUT
533 * http://etherpad.ndd/mypads/api/configuration/something
534 */
535
536 var _set = function (req, res) {
537 var key = (req.method === 'POST') ? req.body.key : req.params.key;
538 var value = req.body.value;
539 var setFn = ld.partial(conf.set, key, value);
540 fn.set(setFn, key, value, req, res);
541 };
542
543 app.post(confRoute, fn.ensureAdmin, _set);
544 app.put(confRoute + '/:key', fn.ensureAdmin, _set);
545
546 /**
547 * DELETE method : `configuration.del` key
548 * Reserved to Etherpad administrators
549 *
550 * Sample URL:
551 * http://etherpad.ndd/mypads/api/configuration/something
552 */
553
554 app.delete(confRoute + '/:key', fn.ensureAdmin,
555 ld.partial(fn.del, conf.del));
556
557 /**
558 * GET method
559 *
560 * Return the value of useFirstLastNameInPads configuration setting
561 *
562 * Sample URL:
563 * http://etherpad.ndd/mypads/api/configuration/public/usefirstlastname
564 */
565
566 app.get(confRoute + '/public/usefirstlastname', function (req, res) {
567 return res.send({ success: true, usefirstlastname: conf.get('useFirstLastNameInPads') });
568 });
569
570 /**
571 * GET method
572 *
573 * Return the value of allPadsPublicsAuthentifiedOnly configuration setting
574 * + given pad's group if allPadsPublicsAuthentifiedOnly is true
575 *
576 * Sample URL:
577 * http://etherpad.ndd/mypads/api/configuration/public/allpadspublicsauthentifiedonly
578 */
579
580 app.get(confRoute + '/public/allpadspublicsauthentifiedonly', function (req, res) {
581 var confValue = conf.get('allPadsPublicsAuthentifiedOnly');
582 var data = {
583 success: true,
584 allpadspublicsauthentifiedonly: confValue
585 };
586 if (confValue) {
587 pad.get(req.query.pid, function (err, p) {
588 if (err) {
589 return res.send({ success: false, error: err });
590 }
591 data.group = p.group;
592 return res.send(data);
593 });
594 } else {
595 return res.send(data);
596 }
597 });
598
599 };
600
601 /**
602 * ## User API
603 *
604 * Most methods need `fn.ensureAdminOrSelf`
605 */
606
607 userAPI = function (app) {
608 var userRoute = api.initialRoute + 'user';
609 var allUsersRoute = api.initialRoute + 'all-users';
610 var userlistRoute = api.initialRoute + 'userlist';
611
612 /**
613 * GET method : `user.userlist` with crud fixed to *get* and current login.
614 * Returns user userlists
615 * ensureAuthenticated needed
616 *
617 * Sample URL:
618 * http://etherpad.ndd/mypads/api/userlist
619 */
620
621 app.get(userlistRoute, fn.ensureAuthenticated,
622 function (req, res) {
623 var opts = { crud: 'get', login: req.mypadsLogin };
624 user.userlist(opts, function (err, u) {
625 if (err) { return res.status(400).send({ error: err.message }); }
626 res.send({ value: u.userlists });
627 });
628 }
629 );
630
631 /**
632 * POST method : `user.userlist` creation with crud fixed to *add*, current
633 * login and userlist parameters, name and optionnally user logins
634 * ensureAuthenticated needed
635 *
636 * Sample URL :
637 * http://etherpad.ndd/mypads/api/userlist
638 */
639
640 app.post(userlistRoute, fn.ensureAuthenticated,
641 function (req, res) {
642 try {
643 var users = { absent: [], present: [] };
644 var lm = req.body.loginsOrEmails;
645 if (lm) { users = userCache.fn.getIdsFromLoginsOrEmails(lm); }
646 var opts = {
647 crud: 'add',
648 login: req.mypadsLogin,
649 name: req.body.name
650 };
651 if (users.uids) { opts.uids = users.uids; }
652 user.userlist(opts, function (err, u) {
653 if (err) { return res.status(400).send({ error: err.message }); }
654 return res.send({
655 success: true,
656 value: u.userlists,
657 present: users.present,
658 absent: users.absent
659 });
660 });
661 }
662 catch (e) { return res.status(400).send({ error: e.message }); }
663 }
664 );
665
666 /**
667 * PUT method : `user.userlist` update with crud fixed to *set*, current
668 * login, mandatory `ulistid` via request parameter and userlist arguments,
669 * `name` or user `logins`
670 * ensureAuthenticated needed
671 *
672 * Sample URL :
673 * http://etherpad.ndd/mypads/api/userlist/xxx
674 */
675
676 app.put(userlistRoute + '/:key', fn.ensureAuthenticated,
677 function (req, res) {
678 try {
679 var users = { absent: [], present: [] };
680 var lm = req.body.loginsOrEmails;
681 if (lm) { users = userCache.fn.getIdsFromLoginsOrEmails(lm); }
682 var opts = {
683 crud: 'set',
684 login: req.mypadsLogin,
685 ulistid: req.params.key,
686 name: req.body.name
687 };
688 if (users.uids) { opts.uids = users.uids; }
689 user.userlist(opts, function (err, u) {
690 if (err) { return res.status(400).send({ error: err.message }); }
691 return res.send({
692 success: true,
693 value: u.userlists,
694 present: users.present,
695 absent: users.absent
696 });
697 });
698 }
699 catch (e) { return res.status(400).send({ error: e.message }); }
700 }
701 );
702
703 /**
704 * DELETE method : `user.userlist` removal with crud fixed to *del*, current
705 * login, mandatory `ulistid` via request parameter
706 * ensureAuthenticated needed
707 *
708 * Sample URL :
709 * http://etherpad.ndd/mypads/api/userlist/xxx
710 */
711
712 app.delete(userlistRoute + '/:key', fn.ensureAuthenticated,
713 function (req, res) {
714 try {
715 var opts = {
716 crud: 'del',
717 login: req.mypadsLogin,
718 ulistid: req.params.key
719 };
720 user.userlist(opts, function (err, u) {
721 if (err) { return res.status(400).send({ error: err.message }); }
722 res.send({ success: true, value: u.userlists });
723 });
724 }
725 catch (e) { return res.status(400).send({ error: e.message }); }
726 }
727 );
728
729
730 /**
731 * GET method : `user.get` login (key)
732 *
733 * Sample URL:
734 * http://etherpad.ndd/mypads/api/user/someone
735 */
736
737 app.get(userRoute + '/:key', fn.ensureAdminOrSelf,
738 ld.partial(fn.get, user));
739
740 /**
741 * GET method : get all users from cache
742 *
743 * exemple: {
744 * usersCount: 1,
745 * users: {
746 * foo: {
747 * email: foo@bar.org,
748 * firstname: Foo,
749 * lastname: Bar
750 * }
751 * }
752 * }
753 *
754 * Sample URL:
755 * http://etherpad.ndd/mypads/api/all-users
756 */
757
758 app.get(allUsersRoute, fn.ensureAdmin,
759 function (req, res) {
760 var emails = ld.reduce(userCache.emails, function (result, n, key) {
761 result[n] = key;
762 return result;
763 }, {});
764 var users = ld.reduce(userCache.logins, function (result, n, key) {
765 result[key] = {
766 email: emails[n],
767 firstname: userCache.firstname[n],
768 lastname: userCache.lastname[n]
769 };
770 return result;
771 }, {});
772 res.send({ users: users, usersCount: ld.size(users) });
773 }
774 );
775
776 // `set` for POST and PUT, see below
777 var _set = function (req, res) {
778 var key;
779 var value = req.body;
780 var stop;
781 if (req.method === 'POST') {
782 if (conf.isNotInternalAuth() || !conf.get('openRegistration')) {
783 stop = true;
784 res.status(400).send({ error: 'BACKEND.ERROR.AUTHENTICATION.NO_REGISTRATION' });
785 } else {
786 key = req.body.login;
787 if (conf.get('checkMails')) {
788 var token = mail.genToken({ login: key, action: 'accountconfirm' });
789 var url = conf.get('rootUrl') +
790 '/mypads/index.html?/accountconfirm/' + token;
791 console.log(url);
792 var lang = (function () {
793 if (ld.includes(ld.keys(conf.cache.languages), req.body.lang)) {
794 return req.body.lang;
795 } else {
796 return conf.get('defaultLanguage');
797 }
798 })();
799 var subject = fn.mailMessage('ACCOUNT_CONFIRMATION_SUBJECT', {
800 title: conf.get('title') });
801 var message = fn.mailMessage('ACCOUNT_CONFIRMATION', {
802 login: key,
803 title: conf.get('title'),
804 url: url,
805 duration: conf.get('tokenDuration')
806 }, lang);
807 mail.send(req.body.email, subject, message, function (err) {
808 if (err) {
809 stop = true;
810 return res.status(501).send({ error: err });
811 }
812 }, lang);
813 }
814 }
815 } else {
816 key = req.params.key;
817 value.login = req.body.login || key;
818 value._id = userCache.logins[key];
819 }
820 // Update needed session values
821 if (!stop) {
822 var u = auth.fn.getUser(req.body.auth_token);
823 if (u) {
824 auth.tokens[u.login].color = req.body.color || u.color;
825 if (!ld.isUndefined(req.body.useLoginAndColorInPads)) {
826 auth.tokens[u.login].useLoginAndColorInPads =
827 req.body.useLoginAndColorInPads;
828 }
829 }
830 if (fn.isAdmin(req)) {
831 user.get(value.login, function (err, u) {
832 if (err) { return res.status(400).send({ error: err.message }); }
833 ld.assign(u, value);
834 var setFn = ld.partial(user.fn.set, u);
835 fn.set(setFn, key, u, req, res);
836 });
837 } else {
838 var setFn = ld.partial(user.set, value);
839 fn.set(setFn, key, value, req, res);
840 }
841 }
842 };
843
844 /**
845 * POST method : `user.set` with user value for user creation
846 * Only method without permission verification
847 *
848 * Sample URL:
849 * http://etherpad.ndd/mypads/api/user
850 */
851
852 app.post(userRoute, _set);
853
854 /**
855 * PUT method : `user.set` with user key/login plus value for existing user
856 *
857 * Sample URL:
858 * http://etherpad.ndd/mypads/api/user/someone
859 */
860
861 app.put(userRoute + '/:key', fn.ensureAdminOrSelf, _set);
862
863 /**
864 * DELETE method : `user.del` with user key/login
865 * Destroy session if self account removal
866 *
867 * Sample URL:
868 * http://etherpad.ndd/mypads/api/user/someone
869 */
870
871 app.delete(userRoute + '/:key', fn.ensureAdminOrSelf, function (req, res) {
872 var isSelf = (req.params.key === req.mypadsLogin);
873 if (isSelf) { delete auth.tokens[req.mypadsLogin]; }
874 fn.del(user.del, req, res);
875 });
876
877 /**
878 * POST method : `user.mark` with user session login, bookmark type and key
879 * Only need ensureAuthenticated because use own login for marking
880 *
881 * Sample URL:
882 * http://etherpad.ndd/mypads/api/usermark
883 */
884
885 app.post(userRoute + 'mark', fn.ensureAuthenticated,
886 function (req, res) {
887 try {
888 user.mark(req.mypadsLogin, req.body.type, req.body.key,
889 function (err) {
890 if (err) { return res.status(404).send({ error: err.message }); }
891 res.send({ success: true });
892 }
893 );
894 }
895 catch (e) { res.status(400).send({ error: e.message }); }
896 }
897 );
898
899 /**
900 * POST method : special password recovery with mail sending.
901 * Need to have the email address into the body
902 *
903 * Sample URL:
904 * http://etherpad.ndd/mypads/api/passrecover
905 */
906
907 app.post(api.initialRoute + 'passrecover', function (req, res) {
908 var email = req.body.email;
909 var err;
910 if (conf.isNotInternalAuth()) {
911 err = 'BACKEND.ERROR.AUTHENTICATION.NO_RECOVER';
912 return res.status(400).send({ error: err });
913 }
914 if (!ld.isEmail(email)) {
915 err = 'BACKEND.ERROR.TYPE.MAIL';
916 return res.status(400).send({ error: err });
917 }
918 if (!userCache.emails[email]) {
919 err = 'BACKEND.ERROR.USER.NOT_FOUND';
920 return res.status(404).send({ error: err });
921 }
922 if (conf.get('rootUrl').length === 0) {
923 err = 'BACKEND.ERROR.CONFIGURATION.ROOTURL_NOT_CONFIGURED';
924 return res.status(501).send({ error: err });
925 }
926 user.get(email, function (err, u) {
927 if (err) { return res.status(400).send({ error: err }); }
928 var token = mail.genToken({ login: u.login, action: 'passrecover' });
929 console.log(conf.get('rootUrl') + '/mypads/index.html?/passrecover/' +
930 token);
931 var subject = fn.mailMessage('PASSRECOVER_SUBJECT', {
932 title: conf.get('title') }, u.lang);
933 var message = fn.mailMessage('PASSRECOVER', {
934 login: u.login,
935 title: conf.get('title'),
936 url: conf.get('rootUrl') + '/mypads/index.html?/passrecover/' + token,
937 duration: conf.get('tokenDuration')
938 }, u.lang);
939 mail.send(u.email, subject, message, function (err) {
940 if (err) { return res.status(501).send({ error: err }); }
941 return res.send({ success: true });
942 });
943 });
944 });
945
946 /**
947 * PUT method : password recovery with token and new password
948 * Need to have the login into the body
949 *
950 * Sample URL:
951 * http://etherpad.ndd/mypads/api/passrecover
952 */
953
954 app.put(api.initialRoute + 'passrecover/:token', function (req, res) {
955 var val = mail.tokens[req.params.token];
956 var err;
957 var badLogin = (!val || !val.login || !userCache.logins[val.login]);
958 var badAction = (!val || !val.action || (val.action !== 'passrecover'));
959 if (conf.isNotInternalAuth()) {
960 err = 'BACKEND.ERROR.AUTHENTICATION.NO_RECOVER';
961 return res.status(400).send({ error: err });
962 }
963 if (badLogin || badAction) {
964 err = 'BACKEND.ERROR.TOKEN.INCORRECT';
965 return res.status(400).send({ error: err });
966 }
967 if (!mail.isValidToken(req.params.token)) {
968 err = 'BACKEND.ERROR.TOKEN.EXPIRED';
969 return res.status(400).send({ error: err });
970 }
971 var pass = req.body.password;
972 var passC = req.body.passwordConfirm;
973 if (!pass || (pass !== passC)) {
974 err = 'USER.ERR.PASSWORD_MISMATCH';
975 return res.status(400).send({ error: err });
976 }
977 user.get(val.login, function (err, u) {
978 if (err) { return res.status(400).send({ error: err.message }); }
979 u.password = pass;
980 if (!u.active) { u.active = true; }
981 user.set(u, function (err) {
982 if (err) { return res.status(400).send({ error: err.message }); }
983 res.send({ success: true, login: val.login });
984 });
985 });
986 });
987
988 /**
989 * POST method : account confirmation with token on body
990 *
991 * Sample URL:
992 * http://etherpad.ndd/mypads/api/accountconfirm
993 */
994
995 app.post(api.initialRoute + 'accountconfirm', function (req, res) {
996 var val = mail.tokens[req.body.token];
997 var err;
998 if (conf.isNotInternalAuth()) {
999 err = 'BACKEND.ERROR.AUTHENTICATION.NO_RECOVER';
1000 return res.status(400).send({ error: err });
1001 }
1002 if (!val || !val.action || (val.action !== 'accountconfirm')) {
1003 err = 'BACKEND.ERROR.TOKEN.INCORRECT';
1004 return res.status(400).send({ error: err });
1005 }
1006 if (!mail.isValidToken(req.body.token)) {
1007 err = 'BACKEND.ERROR.TOKEN.EXPIRED';
1008 return res.status(400).send({ error: err });
1009 }
1010 user.get(val.login, function (err, u) {
1011 if (err) { return res.status(400).send({ error: err.message }); }
1012 u.active = true;
1013 user.fn.set(u, function (err) {
1014 if (err) { return res.status(400).send({ error: err.message }); }
1015 res.send({ success: true, login: val.login });
1016 });
1017 });
1018 });
1019
1020 };
1021
1022 /**
1023 * ## Group API
1024 *
1025 * All methods except creation need special permissions.
1026 */
1027
1028 groupAPI = function (app) {
1029 var groupRoute = api.initialRoute + 'group';
1030
1031 /**
1032 * GET method : `group.getByUser` via user login. passwords are omitted
1033 * Returns all groups and pads, filtered from sensitive data.
1034 *
1035 * Only for authenticated users.
1036 *
1037 * Sample URL:
1038 * http://etherpad.ndd/mypads/api/group
1039 */
1040
1041 app.get(groupRoute, fn.ensureAuthenticated,
1042 function (req, res) {
1043 user.get(req.mypadsLogin, function (err, u) {
1044 if (err) { return res.status(400).send({ error: err }); }
1045 try {
1046 group.getByUser(u, true, function (err, data) {
1047 if (err) {
1048 return res.status(404).send({
1049 error: err.message
1050 });
1051 }
1052 data.groups = ld.transform(data.groups,
1053 function (memo, val, key) {
1054 memo[key] = ld.omit(val, 'password');
1055 }
1056 );
1057 data.pads = ld.transform(data.pads,
1058 function (memo, val, key) {
1059 memo[key] = ld.omit(val, 'password');
1060 }
1061 );
1062 data.users = ld.transform(data.users, function (memo, val, key) {
1063 memo[key] = ld.pick(val, '_id', 'login', 'firstname',
1064 'lastname', 'email');
1065 });
1066 data.admins = ld.transform(data.admins,
1067 function (memo, val, key) {
1068 memo[key] = ld.pick(val, '_id', 'login', 'firstname',
1069 'lastname', 'email');
1070 }
1071 );
1072 data.bookmarks = { groups: {}, pads: {} };
1073 group.getBookmarkedGroupsByUser(u, function (err, bookmarks) {
1074 if (err) {
1075 return res.status(404).send({
1076 error: err.message
1077 });
1078 }
1079 data.bookmarks.groups = ld.transform(bookmarks,
1080 function (memo, val, key) {
1081 memo[key] = ld.omit(val, 'password');
1082 }
1083 );
1084 pad.getBookmarkedPadsByUser(u, function (err, bookmarks) {
1085 if (err) {
1086 return res.status(404).send({
1087 error: err.message
1088 });
1089 }
1090 data.bookmarks.pads = ld.transform(bookmarks,
1091 function (memo, val, key) {
1092 memo[key] = ld.omit(val, 'password');
1093 }
1094 );
1095 res.send({ value: data });
1096 });
1097 });
1098 });
1099 }
1100 catch (e) {
1101 res.status(400).send({ error: e.message });
1102 }
1103 });
1104 }
1105 );
1106
1107 /**
1108 * GET method : `group.getWithPads` unique id
1109 * Returns pads too because useful for public groups and unauth users.
1110 *
1111 * Sample URL:
1112 * http://etherpad.ndd/mypads/api/group/xxxx
1113 */
1114
1115 app.get(groupRoute + '/:key', function (req, res) {
1116 try {
1117 var key = req.params.key;
1118 group.getWithPads(key, function (err, g, pads) {
1119 if (err) {
1120 return res.status(404).send({ key: key, error: err.message });
1121 }
1122 var u = auth.fn.getUser(req.query.auth_token);
1123 var isAdmin = fn.isAdmin(req);
1124 var isUser = (u && ld.includes(ld.union(g.admins, g.users), u._id));
1125 var isAllowedForPublic = (g.visibility === 'public');
1126
1127 // allPadsPublicsAuthentifiedOnly feature
1128 if (conf.get('allPadsPublicsAuthentifiedOnly')) {
1129 isAllowedForPublic = false;
1130 if (u && !isAdmin) {
1131 isUser = true;
1132 }
1133 }
1134
1135 if (isAllowedForPublic && !isAdmin && !isUser) {
1136 pads = ld.transform(pads, function (memo, p, key) {
1137 if (!p.visibility || p.visibility === g.visibility) {
1138 memo[key] = p;
1139 }
1140 });
1141 }
1142 if (isAdmin || isUser || isAllowedForPublic) {
1143 return res.send({ key: key, value: g, pads: pads });
1144 }
1145 var isPrivate = (g.visibility === 'private' && !conf.get('allPadsPublicsAuthentifiedOnly'));
1146 if (isPrivate) {
1147 if (req.query.password) {
1148 var pwd = (ld.isUndefined(req.query.password)) ? undefined : decode(req.query.password);
1149 auth.fn.isPasswordValid(g, pwd,
1150 function (err, valid) {
1151 if (!err && !valid) {
1152 err = { message: 'BACKEND.ERROR.PERMISSION.UNAUTHORIZED' };
1153 }
1154 if (err) {
1155 return res.status(401)
1156 .send({ key: key, error: err.message });
1157 }
1158 if (isPrivate && !isAdmin && !isUser) {
1159 pads = ld.transform(pads, function (memo, p, key) {
1160 if (!p.visibility || p.visibility === g.visibility) {
1161 memo[key] = p;
1162 }
1163 });
1164 }
1165 return res.send({ key: key, value: g, pads: pads });
1166 }
1167 );
1168 } else {
1169 var value = ld.pick(g, 'name', 'visibility');
1170 return res.send({ key: req.params.key, value: value });
1171 }
1172 } else {
1173 if (conf.get('allPadsPublicsAuthentifiedOnly')) {
1174 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.MUST_BE');
1175 } else {
1176 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.DENIED_RECORD');
1177 }
1178 }
1179 });
1180 }
1181 catch (e) { res.status(400).send({ error: e.message }); }
1182 });
1183
1184 /**
1185 * `canEdit` is an asynchronous internal group helper to check common
1186 * permissions for edit methods used here (set && delete ones). It ensures
1187 * that the current user is either an Etherpad admin or a group admin.
1188 *
1189 * It takes the `req` and `res` request and response Express objects, and a
1190 * `successFn` function, called if the user is allowed. Otherwise, it uses
1191 * response to return statusCode and Error messages.
1192 */
1193
1194 var canEdit = function (req, res, successFn) {
1195 var isAdmin = fn.isAdmin(req);
1196 if (isAdmin) { return successFn(); }
1197 var u = auth.fn.getUser(req.body.auth_token);
1198 if (!u) {
1199 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.NOT_AUTH');
1200 }
1201 group.get(req.params.key, function (err, g) {
1202 if (err) { return res.status(400).send({ error: err.message }); }
1203 var isAllowed = ld.includes(g.admins, auth.tokens[u.login]._id);
1204 if (isAllowed) {
1205 return successFn();
1206 } else {
1207 return fn.denied(res,
1208 'BACKEND.ERROR.AUTHENTICATION.DENIED_RECORD_EDIT');
1209 }
1210 });
1211 };
1212
1213 // `set` for POST and PUT, see below
1214 var _set = function (req, res) {
1215 var setFn = ld.partial(group.set, req.body);
1216 fn.set(setFn, req.body._id, req.body, req, res);
1217 };
1218
1219 /**
1220 * POST method : `group.set` with user value for group creation
1221 *
1222 * Sample URL:
1223 * http://etherpad.ndd/mypads/api/group
1224 */
1225
1226 app.post(groupRoute, function (req, res) {
1227 var isAdmin = fn.isAdmin(req);
1228 var u = auth.fn.getUser(req.body.auth_token);
1229 if (!u && !isAdmin) {
1230 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.NOT_AUTH');
1231 }
1232 if (!isAdmin) { req.body.admin = u._id; }
1233 _set(req, res);
1234 });
1235
1236 /**
1237 * PUT method : `group.set` with group id plus value for existing group
1238 *
1239 * Sample URL:
1240 * http://etherpad.ndd/mypads/api/group/xxx
1241 */
1242
1243 app.put(groupRoute + '/:key', function (req, res) {
1244 canEdit(req, res, ld.partial(_set, req, res));
1245 });
1246
1247 /**
1248 * DELETE method : `group.del` with group id
1249 *
1250 * Sample URL:
1251 * http://etherpad.ndd/mypads/api/group/xxxx
1252 */
1253
1254 app.delete(groupRoute + '/:key', function (req, res) {
1255 canEdit(req, res, ld.partial(fn.del, group.del, req, res));
1256 }
1257 );
1258
1259 /**
1260 * POST method : `group.inviteOrShare` with gid group id, array of all
1261 * concerned loginsOrEmails and invite boolean
1262 * This method is open to all authenticated users.
1263 *
1264 * Sample URL:
1265 * http://etherpad.ndd/mypads/api/group/invite
1266 */
1267
1268 app.post(groupRoute + '/invite', function (req, res) {
1269 if (!req.body.gid) {
1270 return res.status(400)
1271 .send({ error: 'BACKEND.ERROR.TYPE.PARAMS_REQUIRED' });
1272 }
1273 req.params.key = req.body.gid;
1274 var successFn = ld.partial(function (req, res) {
1275 try {
1276 group.inviteOrShare(req.body.invite, req.body.gid,
1277 req.body.loginsOrEmails, function (err, g, uids) {
1278 if (err) {
1279 return res.status(401).send({ error: err.message });
1280 }
1281 return res.send(ld.assign({ success: true, value: g }, uids));
1282 });
1283 }
1284 catch (e) { res.status(400).send({ error: e.message }); }
1285 }, req, res);
1286 canEdit(req, res, successFn);
1287 });
1288
1289 /**
1290 * POST method : `group.resign` with gid group id and current session uid.
1291 *
1292 * Sample URL:
1293 * http://etherpad.ndd/mypads/api/group/resign
1294 */
1295
1296 app.post(groupRoute + '/resign', fn.ensureAuthenticated,
1297 function (req, res) {
1298 try {
1299 group.resign(req.body.gid, auth.tokens[req.mypadsLogin]._id,
1300 function (err, g) {
1301 if (err) { return res.status(400).send({ error: err.message }); }
1302 return res.send({ success: true, value: g });
1303 }
1304 );
1305 }
1306 catch (e) { return res.status(400).send({ error: e.message }); }
1307 }
1308 );
1309
1310 };
1311
1312 /**
1313 * ## Pad API
1314 *
1315 * All methods needs special permissions
1316 */
1317
1318 padAPI = function (app) {
1319 var padRoute = api.initialRoute + 'pad';
1320
1321 /**
1322 * `canAct` is a pad internal function that checks permissions to allow or
1323 * not interaction with the pad object.
1324 *
1325 * It takes :
1326 *
1327 * - `edit` boolean, if the pad should be only read or updated
1328 * - `successFn` functional, to be called if allowed, that takes req, res
1329 * and value
1330 * - classic `req`uest and `res`ponse Express objects
1331 */
1332
1333 var canAct = function (edit, successFn, req, res) {
1334 pad.get(req.params.key, function (err, p) {
1335 var key = req.params.key;
1336 if (err) { return res.status(404).send({ error: err.message }); }
1337 var isAdmin = fn.isAdmin(req);
1338 if (isAdmin) { return successFn(req, res, p); }
1339
1340 // allPadsPublicsAuthentifiedOnly feature
1341 if (conf.get('allPadsPublicsAuthentifiedOnly') && req.route.method === 'get') {
1342 var token = req.body.auth_token || req.query.auth_token;
1343 var u = auth.fn.getUser(token);
1344 return (!u) ? fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.MUST_BE') : successFn(req, res, p);
1345 }
1346
1347 if (!edit && (p.visibility === 'public')) {
1348 return successFn(req, res, p);
1349 }
1350 if (!edit && (p.visibility === 'private')) {
1351 var pwd = (ld.isUndefined(req.query.password)) ? undefined : decode(req.query.password);
1352 auth.fn.isPasswordValid(p, pwd, function (err, valid) {
1353 if (!err && !valid) {
1354 err = { message: 'BACKEND.ERROR.PERMISSION.UNAUTHORIZED' };
1355 }
1356 if (err) {
1357 return res.status(401).send({ key: key, error: err.message });
1358 }
1359 return successFn(req, res, p);
1360 });
1361 } else {
1362 group.get(p.group, function (err, g) {
1363 if (err) { return res.status(400).send({ error: err.message }); }
1364 if (!edit && (g.visibility === 'public')) {
1365 return successFn(req, res, p);
1366 } else if (!edit && (g.visibility === 'private')) {
1367 var pwd = (ld.isUndefined(req.query.password)) ? undefined : decode(req.query.password);
1368 auth.fn.isPasswordValid(g, pwd,
1369 function (err, valid) {
1370 if (!err && !valid) {
1371 err = { message: 'BACKEND.ERROR.PERMISSION.UNAUTHORIZED' };
1372 }
1373 if (err) {
1374 return res.status(401)
1375 .send({ key: key, error: err.message });
1376 }
1377 return successFn(req, res, p);
1378 }
1379 );
1380 } else {
1381 var token = req.body.auth_token || req.query.auth_token;
1382 var u = auth.fn.getUser(token);
1383 if (!u) {
1384 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.MUST_BE');
1385 }
1386 var users = edit ? g.admins : ld.union(g.admins, g.users);
1387 var uid = auth.tokens[u.login]._id;
1388 var isAllowed = ld.includes(users, uid);
1389 if (isAllowed) {
1390 return successFn(req, res, p);
1391 } else {
1392 var msg = edit ? 'DENIED_RECORD_EDIT' : 'DENIED_RECORD';
1393 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.' + msg);
1394 }
1395 }
1396 });
1397 }
1398 });
1399 };
1400
1401 /**
1402 * GET method : `pad.get` unique id
1403 * Only for group admin or users, and global admin
1404 *
1405 * Sample URL:
1406 * http://etherpad.ndd/mypads/api/pad/xxxx
1407 */
1408
1409 // TODO: + admin, no pass needed...
1410 app.get(padRoute + '/:key',
1411 ld.partial(canAct, false, function (req, res, val) {
1412 return res.send({ key: req.params.key, value: val });
1413 }));
1414
1415 // `set` for POST and PUT, see below
1416 var _set = function (req, res) {
1417 var setFn = ld.partial(pad.set, req.body);
1418 fn.set(setFn, req.body._id, req.body, req, res);
1419 };
1420
1421 /**
1422 * POST method : `pad.set` with user value for pad creation
1423 *
1424 * Sample URL:
1425 * http://etherpad.ndd/mypads/api/pad
1426 */
1427
1428 app.post(padRoute, function (req, res) {
1429 var isAdmin = fn.isAdmin(req);
1430 var u;
1431 if (!isAdmin) { u = auth.fn.getUser(req.body.auth_token); }
1432 if (!isAdmin && !u) {
1433 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.NOT_AUTH');
1434 }
1435 if (isAdmin) {
1436 _set(req, res);
1437 } else {
1438 if (req.body.group) {
1439 group.get(req.body.group, function (err, g) {
1440 if (err) {
1441 if (ld.isEqual(err, new Error('BACKEND.ERROR.CONFIGURATION.KEY_NOT_FOUND'))) {
1442 err = 'BACKEND.ERROR.PAD.ITEMS_NOT_FOUND';
1443 }
1444 return res.status(400).send({ success: false, error: err });
1445 }
1446 if (ld.isUndefined(g)) {
1447 return res.status(400).send({ success: false, error: 'BACKEND.ERROR.PAD.ITEMS_NOT_FOUND'});
1448 } else {
1449 if (ld.indexOf(g.admins, u._id) !== -1 ||
1450 (g.allowUsersToCreatePads && ld.indexOf(g.users, u._id) !== -1)) {
1451 _set(req, res);
1452 } else {
1453 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.DENIED');
1454 }
1455 }
1456 });
1457 } else {
1458 return res.status(400).send({ success: false, error: 'BACKEND.ERROR.TYPE.PARAM_STR'});
1459 }
1460 }
1461 });
1462
1463 /**
1464 * PUT method : `pad.set` with group id plus value for existing pad
1465 *
1466 * Sample URL:
1467 * http://etherpad.ndd/mypads/api/pad/xxx
1468 */
1469
1470 app.put(padRoute + '/:key', ld.partial(canAct, true, _set));
1471
1472 /**
1473 * DELETE method : `pad.del` with pad id
1474 *
1475 * Sample URL:
1476 * http://etherpad.ndd/mypads/api/pad/xxxx
1477 */
1478
1479 app.delete(padRoute + '/:key', function (req, res) {
1480 var isAdmin = fn.isAdmin(req);
1481 var u;
1482 if (!isAdmin) { u = auth.fn.getUser(req.body.auth_token); }
1483 if (!isAdmin && !u) {
1484 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.NOT_AUTH');
1485 }
1486 canAct(true, ld.partial(fn.del, pad.del), req, res);
1487 });
1488
1489 /**
1490 * DELETE method : `pad.delChatHistory` with pad id
1491 *
1492 * Sample URL:
1493 * http://etherpad.ndd/mypads/api/pad/chathistory/xxxx
1494 */
1495
1496 app.delete(padRoute + '/chathistory/:key', function (req, res) {
1497 var isAdmin = fn.isAdmin(req);
1498 var u;
1499 if (!isAdmin) { u = auth.fn.getUser(req.body.auth_token); }
1500 if (!isAdmin && !u) {
1501 return fn.denied(res, 'BACKEND.ERROR.AUTHENTICATION.NOT_AUTH');
1502 }
1503 canAct(true, ld.partial(fn.del, pad.delChatHistory), req, res);
1504 });
1505
1506 /**
1507 * GET method : with pad id
1508 *
1509 * Return true if the pad is public
1510 *
1511 * Sample URL:
1512 * http://etherpad.ndd/mypads/api/pad/ispublic/xxxx
1513 */
1514
1515 app.get(padRoute + '/ispublic/:key', function (req, res) {
1516 pad.get(req.params.key, function(err, p) {
1517 if (err) {
1518 return res.send({ success: false, error: err });
1519 } else if (p.visibility !== null) {
1520 return res.send({ success: true, key: req.params.key, ispublic: (p.visibility === 'public') });
1521 } else {
1522 group.get(p.group, function(err, g) {
1523 if (err) { return res.send({ success: false, error: err }); }
1524 return res.send({ success: true, key: req.params.key, ispublic: (g.visibility === 'public') });
1525 });
1526 }
1527 });
1528 });
1529 };
1530
1531 cacheAPI = function (app) {
1532 var cacheRoute = api.initialRoute + 'cache';
1533
1534 /**
1535 * GET method : check, method returning information about the end of users
1536 * cache loading
1537 *
1538 * exemple: { "userCacheReady": true }
1539 *
1540 * Sample URL:
1541 * http://etherpad.ndd/mypads/api/cache/check
1542 */
1543
1544 app.get(cacheRoute + '/check', function (req, res) {
1545 return res.send({ userCacheReady: userCache.userCacheReady });
1546 });
1547
1548 };
1549
1550 statsAPI = function (app) {
1551 var statsRoute = api.initialRoute + 'stats';
1552
1553 /**
1554 * GET method : stats.json, method returning some stats about MyPads
1555 * instance usage
1556 *
1557 * exemple: { "timestamp":1524035674, "users":3, "pad":3, "groups":3 }
1558 *
1559 * Sample URL:
1560 * http://etherpad.ndd/mypads/api/stats/stats.json
1561 */
1562
1563 app.get(statsRoute + '/stats.json', function (req, res) {
1564 var time = Math.floor(Date.now() / 1000);
1565
1566 pad.count(function(err, pcount) {
1567 if (err) { return res.send({ timestamp: time, err: err }); }
1568 group.count(function(err, gcount) {
1569 if (err) { return res.send({ timestamp: time, err: err }); }
1570 return res.send({
1571 timestamp: time,
1572 users: ld.size(userCache.logins),
1573 pad: pcount,
1574 groups: gcount
1575 });
1576 });
1577 });
1578 });
1579 };
1580
1581 return api;
1582
1583}).call(this);