From 307874c5eef3f7097960b51459cb27ba4d38b3f1 Mon Sep 17 00:00:00 2001 From: Michiel de Jong Date: Mon, 11 Jan 2021 15:36:50 +0100 Subject: [PATCH 1/5] Establish https://localhost:8443/.well-known/pay --- lib/create-app.js | 2 ++ lib/payment-pointer-discovery.js | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 lib/payment-pointer-discovery.js diff --git a/lib/create-app.js b/lib/create-app.js index a9b9b7b83..7ea911f1c 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -15,6 +15,7 @@ const vhost = require('vhost') const EmailService = require('./services/email-service') const TokenService = require('./services/token-service') const capabilityDiscovery = require('./capability-discovery') +const paymentPointerDiscovery = require('./payment-pointer-discovery') const API = require('./api') const errorPages = require('./handlers/error-pages') const config = require('./server-config') @@ -166,6 +167,7 @@ function initHeaders (app) { }) app.use('/', capabilityDiscovery()) + app.use('/', paymentPointerDiscovery()) } /** diff --git a/lib/payment-pointer-discovery.js b/lib/payment-pointer-discovery.js new file mode 100644 index 000000000..2265e6cbb --- /dev/null +++ b/lib/payment-pointer-discovery.js @@ -0,0 +1,36 @@ +'use strict' +/** + * @module payment-pointer-discovery + */ +const express = require('express') + +module.exports = paymentPointerDiscovery + +/** + * Returns a set of routes to deal with server payment pointer discovery + * @method paymentPointerDiscovery + * @return {Router} Express router + */ +function paymentPointerDiscovery () { + const router = express.Router('/') + + // Advertise the server payment pointer discover endpoint + router.get('/.well-known/pay', paymentPointerDocument()) + return router +} + +/** + * Serves the service payment pointer document (containing server root URL, including + * any base path the user specified in config, server API endpoints, etc). + * @method paymentPointerDocument + * @param req + * @param res + * @param next + */ +function paymentPointerDocument () { + return (req, res) => { + res.json({ + hello: 'world' + }) + } +} From ff6fe9ae5ca382c365fe643d99ddc7451098c21d Mon Sep 17 00:00:00 2001 From: Michiel de Jong Date: Mon, 11 Jan 2021 16:08:13 +0100 Subject: [PATCH 2/5] Serve JSON from /settings/paymentPointer.json --- lib/payment-pointer-discovery.js | 39 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/payment-pointer-discovery.js b/lib/payment-pointer-discovery.js index 2265e6cbb..224490832 100644 --- a/lib/payment-pointer-discovery.js +++ b/lib/payment-pointer-discovery.js @@ -3,9 +3,13 @@ * @module payment-pointer-discovery */ const express = require('express') +const { promisify } = require('util') +const fs = require('fs') module.exports = paymentPointerDiscovery +const SETTING_FILE_PATH = '/settings/paymentPointer.json' + /** * Returns a set of routes to deal with server payment pointer discovery * @method paymentPointerDiscovery @@ -28,9 +32,36 @@ function paymentPointerDiscovery () { * @param next */ function paymentPointerDocument () { - return (req, res) => { - res.json({ - hello: 'world' - }) + return async (req, res) => { + try { + const ldp = req.app.locals.ldp + const url = ldp.resourceMapper.resolveUrl(req.hostname, SETTING_FILE_PATH) + const contentType = 'application/json' + const createIfNotExists = true + const { path } = await ldp.resourceMapper.mapUrlToFile({ url, contentType, createIfNotExists }) + let body + try { + // Read the file from disk + body = await promisify(fs.readFile)(path, { encoding: 'utf8' }) + } catch (e) { + if (e.message.startsWith('ENOENT: no such file or directory,')) { + res.json({ + error: `Please create ${SETTING_FILE_PATH} on your pod` + }) + } + } + let obj + try { + // Read the file from disk + obj = JSON.parse(body) + } catch (e) { + res.json({ + error: `Please make sure ${SETTING_FILE_PATH} contains valid JSON` + }) + } + res.json(obj) + } catch (e) { + res.json({ fail: e.message }) + } } } From 3ac3fede5965f4b1fc4c6426453e787bf73c696b Mon Sep 17 00:00:00 2001 From: Michiel de Jong Date: Mon, 11 Jan 2021 18:21:26 +0100 Subject: [PATCH 3/5] Use triple in profile --- lib/payment-pointer-discovery.js | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/payment-pointer-discovery.js b/lib/payment-pointer-discovery.js index 224490832..0ef7b09bf 100644 --- a/lib/payment-pointer-discovery.js +++ b/lib/payment-pointer-discovery.js @@ -5,10 +5,11 @@ const express = require('express') const { promisify } = require('util') const fs = require('fs') +const rdf = require('rdflib') module.exports = paymentPointerDiscovery -const SETTING_FILE_PATH = '/settings/paymentPointer.json' +const PROFILE_PATH = '/profile/card' /** * Returns a set of routes to deal with server payment pointer discovery @@ -35,9 +36,9 @@ function paymentPointerDocument () { return async (req, res) => { try { const ldp = req.app.locals.ldp - const url = ldp.resourceMapper.resolveUrl(req.hostname, SETTING_FILE_PATH) - const contentType = 'application/json' - const createIfNotExists = true + const url = ldp.resourceMapper.resolveUrl(req.hostname, PROFILE_PATH) + const contentType = 'text/turtle' + const createIfNotExists = false const { path } = await ldp.resourceMapper.mapUrlToFile({ url, contentType, createIfNotExists }) let body try { @@ -46,20 +47,31 @@ function paymentPointerDocument () { } catch (e) { if (e.message.startsWith('ENOENT: no such file or directory,')) { res.json({ - error: `Please create ${SETTING_FILE_PATH} on your pod` + error: `Please create ${PROFILE_PATH} on your pod` }) } } - let obj + const webid = rdf.Namespace(`${url}#`)('me') + const pp = rdf.Namespace('http://paymentpointers.org/ns#')('PaymentPointer') + let paymentPointer try { - // Read the file from disk - obj = JSON.parse(body) + const graph = rdf.graph() + // Parse the file as Turtle + rdf.parse(body, graph, url, contentType) + paymentPointer = graph.any(webid, pp) } catch (e) { + console.error(e) res.json({ - error: `Please make sure ${SETTING_FILE_PATH} contains valid JSON` + error: `Please make sure ${PROFILE_PATH} contains valid Turtle` }) } - res.json(obj) + if (paymentPointer === null) { + res.json({ fail: 'Add triple', subject: `<${webid.value}>`, predicate: `<${pp.value}>`, object: '$alice.example' }) + } + if (paymentPointer.value.startsWith('$')) { + paymentPointer.value = `https://${paymentPointer.value.substring(1)}` + } + res.redirect(paymentPointer.value) } catch (e) { res.json({ fail: e.message }) } From c7fbe577edc00e1eead5ad08808d7e0b0f6060e5 Mon Sep 17 00:00:00 2001 From: Michiel de Jong Date: Wed, 13 Jan 2021 11:27:45 +0100 Subject: [PATCH 4/5] Reenable capability-discovery-test --- test/integration/capability-discovery-test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/capability-discovery-test.js b/test/integration/capability-discovery-test.js index f0895b034..227ea0f83 100644 --- a/test/integration/capability-discovery-test.js +++ b/test/integration/capability-discovery-test.js @@ -7,8 +7,7 @@ const supertest = require('supertest') const expect = require('chai').expect // In this test we always assume that we are Alice -// FIXME #1502 -describe.skip('API', () => { +describe('API', () => { let alice const aliceServerUri = 'https://localhost:5000' From 4119a73b56ab0cb2965a3233697e5cd71a3aa03e Mon Sep 17 00:00:00 2001 From: Michiel de Jong Date: Wed, 13 Jan 2021 13:52:20 +0100 Subject: [PATCH 5/5] integration test, three cases --- lib/payment-pointer-discovery.js | 6 +- test/integration/payment-pointer-test.js | 149 ++++++++++++++++++ .../accounts-scenario/bob/profile/card$.ttl | 5 + .../charlie/profile/card$.ttl | 5 + 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 test/integration/payment-pointer-test.js create mode 100644 test/resources/accounts-scenario/bob/profile/card$.ttl create mode 100644 test/resources/accounts-scenario/charlie/profile/card$.ttl diff --git a/lib/payment-pointer-discovery.js b/lib/payment-pointer-discovery.js index 0ef7b09bf..683a0aab9 100644 --- a/lib/payment-pointer-discovery.js +++ b/lib/payment-pointer-discovery.js @@ -69,7 +69,11 @@ function paymentPointerDocument () { res.json({ fail: 'Add triple', subject: `<${webid.value}>`, predicate: `<${pp.value}>`, object: '$alice.example' }) } if (paymentPointer.value.startsWith('$')) { - paymentPointer.value = `https://${paymentPointer.value.substring(1)}` + let suffix = '' + if (paymentPointer.value.indexOf('/') === -1) { + suffix = '/.well-known/pay' + } + paymentPointer.value = `https://${paymentPointer.value.substring(1)}${suffix}` } res.redirect(paymentPointer.value) } catch (e) { diff --git a/test/integration/payment-pointer-test.js b/test/integration/payment-pointer-test.js new file mode 100644 index 000000000..028c4047f --- /dev/null +++ b/test/integration/payment-pointer-test.js @@ -0,0 +1,149 @@ +/* eslint-disable no-unused-expressions */ + +const Solid = require('../../index') +const path = require('path') +const { cleanDir } = require('../utils') +const supertest = require('supertest') +const expect = require('chai').expect + +describe('API', () => { + const configPath = path.join(__dirname, '../resources/config') + + const serverConfig = { + sslKey: path.join(__dirname, '../keys/key.pem'), + sslCert: path.join(__dirname, '../keys/cert.pem'), + auth: 'oidc', + dataBrowser: false, + webid: true, + multiuser: false, + configPath + } + + function startServer (pod, port) { + return new Promise((resolve) => { + pod.listen(port, () => { resolve() }) + }) + } + + describe('Payment Pointer Alice', () => { + let alice + const aliceServerUri = 'https://localhost:5000' + const aliceDbPath = path.join(__dirname, + '../resources/accounts-scenario/alice/db') + const aliceRootPath = path.join(__dirname, '../resources/accounts-scenario/alice') + + const alicePod = Solid.createServer( + Object.assign({ + root: aliceRootPath, + serverUri: aliceServerUri, + dbPath: aliceDbPath + }, serverConfig) + ) + + before(() => { + return Promise.all([ + startServer(alicePod, 5000) + ]).then(() => { + alice = supertest(aliceServerUri) + }) + }) + + after(() => { + alicePod.close() + cleanDir(aliceRootPath) + }) + + describe('GET Payment Pointer document', () => { + it('should show instructions to add a triple', (done) => { + alice.get('/.well-known/pay') + .expect(200) + .expect('content-type', /application\/json/) + .end(function (err, req) { + if (err) { + done(err) + } else { + expect(req.body).deep.equal({ + fail: 'Add triple', + subject: '', + predicate: '', + object: '$alice.example' + }) + done() + } + }) + }) + }) + }) + + describe('Payment Pointer Bob', () => { + let bob + const bobServerUri = 'https://localhost:5001' + const bobDbPath = path.join(__dirname, + '../resources/accounts-scenario/bob/db') + const bobRootPath = path.join(__dirname, '../resources/accounts-scenario/bob') + const bobPod = Solid.createServer( + Object.assign({ + root: bobRootPath, + serverUri: bobServerUri, + dbPath: bobDbPath + }, serverConfig) + ) + + before(() => { + return Promise.all([ + startServer(bobPod, 5001) + ]).then(() => { + bob = supertest(bobServerUri) + }) + }) + + after(() => { + bobPod.close() + cleanDir(bobRootPath) + }) + + describe('GET Payment Pointer document', () => { + it('should redirect to example.com', (done) => { + bob.get('/.well-known/pay') + .expect('location', 'https://bob.com/.well-known/pay') + .expect(302, done) + }) + }) + }) + + describe('Payment Pointer Charlie', () => { + let charlie + const charlieServerUri = 'https://localhost:5002' + const charlieDbPath = path.join(__dirname, + '../resources/accounts-scenario/charlie/db') + const charlieRootPath = path.join(__dirname, '../resources/accounts-scenario/charlie') + const charliePod = Solid.createServer( + Object.assign({ + root: charlieRootPath, + serverUri: charlieServerUri, + dbPath: charlieDbPath + }, serverConfig) + ) + + before(() => { + return Promise.all([ + startServer(charliePod, 5002) + ]).then(() => { + charlie = supertest(charlieServerUri) + }) + }) + + after(() => { + charliePod.close() + cleanDir(charlieRootPath) + }) + + describe('GET Payment Pointer document', () => { + it('should redirect to example.com/charlie', (done) => { + charlie.get('/.well-known/pay') + .expect('location', 'https://service.com/charlie') + .expect(302, done) + }) + }) + }) +}) diff --git a/test/resources/accounts-scenario/bob/profile/card$.ttl b/test/resources/accounts-scenario/bob/profile/card$.ttl new file mode 100644 index 000000000..3b685d688 --- /dev/null +++ b/test/resources/accounts-scenario/bob/profile/card$.ttl @@ -0,0 +1,5 @@ +@prefix : <#>. +@prefix pp: . +@prefix xsd: . + +:me pp:PaymentPointer "$bob.com"^^xsd:string . diff --git a/test/resources/accounts-scenario/charlie/profile/card$.ttl b/test/resources/accounts-scenario/charlie/profile/card$.ttl new file mode 100644 index 000000000..52a82be1c --- /dev/null +++ b/test/resources/accounts-scenario/charlie/profile/card$.ttl @@ -0,0 +1,5 @@ +@prefix : <#>. +@prefix pp: . +@prefix xsd: . + +:me pp:PaymentPointer "$service.com/charlie"^^xsd:string .