diff --git a/lib/api/index.js b/lib/api/index.js index 1d7959e1e..6143e8bce 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -1,3 +1,4 @@ module.exports = { - accounts: require('./accounts') + accounts: require('./accounts'), + messages: require('./messages') } diff --git a/lib/api/messages/index.js b/lib/api/messages/index.js new file mode 100644 index 000000000..c2b631730 --- /dev/null +++ b/lib/api/messages/index.js @@ -0,0 +1,105 @@ +exports.send = send + +const error = require('../../http-error') +const debug = require('debug')('solid:api:messages') +const utils = require('../../utils') +const sym = require('rdflib').sym +const url = require('url') +const waterfall = require('run-waterfall') + +function send () { + return (req, res, next) => { + if (!req.session.userId) { + next(error(401, 'You need to be authenticated')) + return + } + + if (!req.body.message || req.body.message.length < 0) { + next(error(406, 'You need to specify a message')) + return + } + + if (!req.body.to) { + next(error(406, 'You need to specify a the destination')) + return + } + + if (req.body.to.split(':').length !== 2) { + next(error(406, 'Destination badly formatted')) + return + } + + waterfall([ + (cb) => getLoggedUserName(req, cb), + (displayName, cb) => { + const vars = { + me: displayName, + message: req.body.message + } + + if (req.body.to.split(':') === 'mailto' && req.app.locals.email) { + sendEmail(req, vars, cb) + } else { + cb(error(406, 'Messaging service not available')) + } + } + ], (err) => { + if (err) { + next(err) + return + } + + res.send('message sent') + }) + } +} + +function getLoggedUserName (req, callback) { + const ldp = req.app.locals.ldp + const baseUri = utils.uriAbs(req) + const webid = url.parse(req.session.userId) + + ldp.graph(webid.hostname, '/' + webid.pathname, baseUri, function (err, graph) { + if (err) { + debug('cannot find graph of the user', req.session.userId || ldp.root, err) + // TODO for now only users of this IDP can send emails + callback(error(403, 'Your user cannot perform this operation')) + return + } + + // TODO do a query + let displayName + graph + .statementsMatching(undefined, sym('http://xmlns.com/foaf/0.1/name')) + .some(function (statement) { + if (statement.object.value) { + displayName = statement.object.value + return true + } + }) + + if (!displayName) { + displayName = webid.hostname + } + callback(null, displayName) + }) +} + +function sendEmail (req, vars, callback) { + const emailService = req.app.locals.email + const emailData = { + from: 'no-reply@' + webid.hostname, + to: req.body.to.split(':')[1] + } + const webid = url.parse(req.session.userId) + var send = emailService.mailer.templateSender({ + subject: 'Message from {{me}}', + text: 'You have received a message from {{me}}:\n{{message}}', + html: '
You have received a message from{{me}}
{{message}}
' + }, { + from: emailData.from + }) + + // use template based sender to send a message + send({ to: emailData.to }, vars, callback) +} diff --git a/lib/create-app.js b/lib/create-app.js index 597d990b6..5b08fcb1e 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -15,6 +15,7 @@ const AccountRecovery = require('./account-recovery') const capabilityDiscovery = require('./capability-discovery') const bodyParser = require('body-parser') const API = require('./api') +var authentication = require('./handlers/authentication') var corsSettings = cors({ methods: [ @@ -55,7 +56,7 @@ function createApp (argv = {}) { // Setting options as local variable app.locals.ldp = ldp - app.locals.appUrls = argv.apps // used for service capability discovery + app.locals.appUrls = argv.apps // used for service capability discovery if (argv.email && argv.email.host) { app.locals.email = new EmailService(argv.email) @@ -136,6 +137,7 @@ function createApp (argv = {}) { app.use('/api/accounts', needsOverwrite) app.post('/api/accounts/signin', bodyParser.urlencoded({ extended: false }), API.accounts.signin()) app.post('/api/accounts/signout', API.accounts.signout()) + app.post('/api/messages', authentication, bodyParser.urlencoded({ extended: false }), API.messages.send()) } if (ldp.idp) { diff --git a/lib/ldp.js b/lib/ldp.js index f5a5d4f78..803a40bff 100644 --- a/lib/ldp.js +++ b/lib/ldp.js @@ -88,7 +88,7 @@ function LDP (argv) { LDP.prototype.stat = function (file, callback) { fs.stat(file, function (err, stats) { if (err) { - return callback(error(err, 'Can\'t read metadata')) + return callback(error(err, "Can't read metadata")) } return callback(null, stats) @@ -105,7 +105,7 @@ LDP.prototype.readFile = function (filename, callback) { { 'encoding': 'utf8' }, function (err, data) { if (err) { - return callback(error(err, 'Can\'t read file')) + return callback(error(err, "Can't read file")) } return callback(null, data) @@ -121,7 +121,7 @@ LDP.prototype.readContainerMeta = function (directory, callback) { ldp.readFile(directory + ldp.suffixMeta, function (err, data) { if (err) { - return callback(error(err, 'Can\'t read meta file')) + return callback(error(err, "Can't read meta file")) } return callback(null, data) @@ -129,7 +129,7 @@ LDP.prototype.readContainerMeta = function (directory, callback) { } LDP.prototype.listContainer = function (filename, uri, containerData, - contentType, callback) { + contentType, callback) { var ldp = this var host = url.parse(uri).hostname var root = !ldp.idp ? ldp.root : ldp.root + host + '/' @@ -141,7 +141,7 @@ LDP.prototype.listContainer = function (filename, uri, containerData, $rdf.parse(containerData, resourceGraph, baseUri, 'text/turtle') } catch (err) { debug.handlers('GET -- Error parsing data: ' + err) - return callback(error(500, 'Can\'t parse container')) + return callback(error(500, "Can't parse container")) } async.waterfall([ @@ -163,24 +163,24 @@ LDP.prototype.listContainer = function (filename, uri, containerData, next) } ], - function (err, data) { - if (err) { - return callback(error(500, 'Can\'t list container')) - } - // TODO 'text/turtle' is fixed, should be contentType instead - // This forces one more translation turtle -> desired - serialize(resourceGraph, null, 'text/turtle', function (err, result) { + function (err, data) { if (err) { - debug.handlers('GET -- Error serializing container: ' + err) - return callback(error(500, 'Can\'t serialize container')) + return callback(error(500, "Can't list container")) } - return callback(null, result) + // TODO 'text/turtle' is fixed, should be contentType instead + // This forces one more translation turtle -> desired + serialize(resourceGraph, null, 'text/turtle', function (err, result) { + if (err) { + debug.handlers('GET -- Error serializing container: ' + err) + return callback(error(500, "Can't serialize container")) + } + return callback(null, result) + }) }) - }) } LDP.prototype.post = function (hostname, containerPath, slug, stream, container, - callback) { + callback) { var ldp = this debug.handlers('POST -- On parent: ' + containerPath) @@ -258,6 +258,7 @@ LDP.prototype.graph = function (host, reqPath, baseUri, contentType, callback) { var root = ldp.idp ? ldp.root + host + '/' : ldp.root var filename = utils.uriToFilename(reqPath, root) + console.log(filename) async.waterfall([ // Read file @@ -274,7 +275,7 @@ LDP.prototype.graph = function (host, reqPath, baseUri, contentType, callback) { } LDP.prototype.get = function (host, reqPath, baseUri, includeBody, contentType, - callback) { + callback) { var ldp = this var root = !ldp.idp ? ldp.root : ldp.root + host + '/' var filename = utils.uriToFilename(reqPath, root) @@ -282,7 +283,7 @@ LDP.prototype.get = function (host, reqPath, baseUri, includeBody, contentType, ldp.stat(filename, function (err, stats) { // File does not exist if (err) { - return callback(error(err, 'Can\'t find resource requested')) + return callback(error(err, "Can't find resource requested")) } // Just return, since resource exists @@ -314,7 +315,7 @@ LDP.prototype.get = function (host, reqPath, baseUri, includeBody, contentType, stream .on('error', function (err) { debug.handlers('GET -- Read error:' + err.message) - return callback(error(err, 'Can\'t create file ' + err)) + return callback(error(err, "Can't create file " + err)) }) .on('open', function () { debug.handlers('GET -- Read Start.') @@ -334,7 +335,7 @@ LDP.prototype.delete = function (host, resourcePath, callback) { var filename = utils.uriToFilename(resourcePath, root) ldp.stat(filename, function (err, stats) { if (err) { - return callback(error(404, 'Can\'t find ' + err)) + return callback(error(404, "Can't find " + err)) } if (stats.isDirectory()) { diff --git a/package.json b/package.json index 46f9700fe..1a87cfb1e 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ }, "devDependencies": { "chai": "^3.0.0", + "hippie": "^0.5.0", "mocha": "^2.2.5", "nock": "^7.0.2", "rsvp": "^3.1.0", @@ -97,5 +98,7 @@ "bin": { "solid": "./bin/solid.js" }, - "engines" : { "node" : ">=6.0" } + "engines": { + "node": ">=6.0" + } } diff --git a/test/api-accounts.js b/test/api-accounts.js index 79ee200fd..468c185cc 100644 --- a/test/api-accounts.js +++ b/test/api-accounts.js @@ -7,7 +7,7 @@ const expect = require('chai').expect const nock = require('nock') // In this test we always assume that we are Alice -describe('API', () => { +describe('Accounts API', () => { let aliceServer let bobServer let alice @@ -68,7 +68,7 @@ describe('API', () => { if (bobServer) bobServer.close() }) - describe('APIs', () => { + describe('endpoints', () => { describe('/api/accounts/signin', () => { it('should complain if a URL is missing', (done) => { alice.post('/api/accounts/signin') @@ -81,18 +81,21 @@ describe('API', () => { .expect(400) .end(done) }) - it('should return a 400 if endpoint doesn\'t have Link Headers', (done) => { + it("should return a 400 if endpoint doesn't have Link Headers", (done) => { nock('https://amazingwebsite.tld').intercept('/', 'OPTIONS').reply(200) alice.post('/api/accounts/signin') .send('webid=https://amazingwebsite.tld/') .expect(400) .end(done) }) - it('should return a 400 if endpoint doesn\'t have oidc in the headers', (done) => { - nock('https://amazingwebsite.tld').intercept('/', 'OPTIONS').reply(200, '', { - 'Link': function (req, res, body) { - return '