From 80eb0df83c05954a817d53d27a8f122826df41c0 Mon Sep 17 00:00:00 2001 From: nicola Date: Tue, 10 May 2016 16:57:57 -0400 Subject: [PATCH 01/24] implemented signout --- lib/create-app.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/create-app.js b/lib/create-app.js index a0e3f8558..b84a592ff 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -115,6 +115,14 @@ function createApp (argv = {}) { } app.use('/accounts', needsOverwrite) app.use('/', corsSettings, idp.get.bind(idp)) + app.get('/api/accounts/signin', (req, res, next) => { + res.status(200).send() + }) + app.post('/api/accounts/signout', (req, res, next) => { + req.session.userId = '' + req.session.identified = false + res.status(200).send() + }) } if (ldp.idp) { From 279fe4c0928561e591817cae7f40160f953daf7e Mon Sep 17 00:00:00 2001 From: nicola Date: Wed, 11 May 2016 14:27:42 -0400 Subject: [PATCH 02/24] designing the scenario for accounts with OIDC --- test/accounts.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 test/accounts.js diff --git a/test/accounts.js b/test/accounts.js new file mode 100644 index 000000000..5478787e0 --- /dev/null +++ b/test/accounts.js @@ -0,0 +1,73 @@ +const Solid = require('../') +const parallel = require('run-parallel') +const waterfall = require('run-waterfall') +const path = require('path') +const supertest = require('supertest') + +// In this test we always assume that we are Alice + +function getBobFoo (alice, bob, done) { + bob.get('/foo') + .expect(401) + .end(done) +} + +function postBobDiscoverSignIn (alice, bob, done) { + done() +} + +function entersPasswordAndConsent (alice, bob, done) { + +} + +describe('OIDC flow', () => { + let aliceServer + let bobServer + let alice + let bob + + const solid = Solid.createServer({ + root: path.join(__dirname, '/resources'), + sslKey: path.join(__dirname, '/keys/key.pem'), + sslCert: path.join(__dirname, '/keys/cert.pem'), + webid: true + }) + + before(function (done) { + parallel([ + (cb) => { + aliceServer = solid.listen(3456, cb) + alice = supertest('https://localhost:3456') + }, + (cb) => { + bobServer = solid.listen(3457, cb) + bob = supertest('https://localhost:3457') + } + ], done) + }) + + after(function () { + if (aliceServer) aliceServer.close() + if (bobServer) bobServer.close() + }) + + it('step1: User tries to get /foo and gets 401 and meta redirect', (done) => { + getBobFoo(alice, bob, done) + }) + + it('step2: User enters webId to signin', (done) => { + postBobDiscoverSignIn(alice, bob, done) + }) + + it('step3: User enters password', (done) => { + entersPasswordAndConsent(alice, bob, done) + }) + + it('entire flow', (done) => { + waterfall([ + (cb) => getBobFoo(alice, bob, cb), + (cb) => postBobDiscoverSignIn(alice, bob, cb), + (cb) => entersPasswordAndConsent(alice, bob, cb) + ], done) + }) +}) From 6a65bfa5c09d5a5b1d5026d38d01ff54b7c4fd68 Mon Sep 17 00:00:00 2001 From: nicola Date: Wed, 11 May 2016 14:43:25 -0400 Subject: [PATCH 03/24] adding waterfall --- package.json | 1 + test/accounts.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 61f815ba2..1ed997e49 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "mocha": "^2.2.5", "nock": "^7.0.2", "rsvp": "^3.1.0", + "run-waterfall": "^1.1.3", "sinon": "^1.17.4", "standard": "^7.0.1", "supertest": "^1.0.1" diff --git a/test/accounts.js b/test/accounts.js index 5478787e0..d4b84ca9a 100644 --- a/test/accounts.js +++ b/test/accounts.js @@ -7,7 +7,7 @@ const supertest = require('supertest') // In this test we always assume that we are Alice function getBobFoo (alice, bob, done) { - bob.get('/foo') + bob.get('/') .expect(401) .end(done) } @@ -17,7 +17,7 @@ function postBobDiscoverSignIn (alice, bob, done) { } function entersPasswordAndConsent (alice, bob, done) { - + done() } describe('OIDC flow', () => { From d89c713fb070cf9d26ee96e0069e7df5fc12da2b Mon Sep 17 00:00:00 2001 From: nicola Date: Wed, 11 May 2016 15:21:53 -0400 Subject: [PATCH 04/24] adding scenario workflow --- test/scenarios.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/scenarios.md diff --git a/test/scenarios.md b/test/scenarios.md new file mode 100644 index 000000000..44e942dcc --- /dev/null +++ b/test/scenarios.md @@ -0,0 +1,23 @@ +- Full tests (Solid) + - with registered user, user is logged out + - (1) User tries to get a resource + - GET BOB/foo + - sends 401 with redirect in HTML header + - redirect GET BOB/discoverSignin + - (2) User enters the webId so that the authorization endpoint is discovered + - POST BOB/signin with WebID + - response is a 302 to ALICE/authorize?callback=BOB/api/oidc/rp + - (3) User is prompted password? and consent + - (user enters password)? + - user presses conset + - form submit to ALICE/authorize?callback=BOB/api/oidc/rp + - response is a 302 to BOB/api/oidc/rp + - BOB/api/oidc/rp redirects to BOB/foo + + + - needing registration + - (0) User registers an account + - POST ALICE/api/accounts/new + - gives User + - set the cookie + - send an email (for verfication) From 7ebff6e80eadc6e035a94b233c4270c3e5c96fe6 Mon Sep 17 00:00:00 2001 From: nicola Date: Wed, 11 May 2016 15:35:38 -0400 Subject: [PATCH 05/24] put all the APIs under /api --- lib/account-recovery.js | 8 ++++---- lib/create-app.js | 12 ++++++++++-- lib/identity-provider.js | 2 +- test/identity-provider.js | 18 +++++++++--------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/account-recovery.js b/lib/account-recovery.js index 6e4c307d5..c5b00b665 100644 --- a/lib/account-recovery.js +++ b/lib/account-recovery.js @@ -20,7 +20,7 @@ function AccountRecovery (corsSettings, options = {}) { text: 'Hello,\n' + 'You asked to retrieve your account: ' + account + '\n' + 'Copy this address in your browser addressbar:\n\n' + - 'https://' + path.join(host, '/recovery/confirm?token=' + token) // TODO find a way to get the full url + 'https://' + path.join(host, '/api/accounts/validateToken?token=' + token) // TODO find a way to get the full url // html: '' } } @@ -29,12 +29,12 @@ function AccountRecovery (corsSettings, options = {}) { router.use(corsSettings) } - router.get('/request', function (req, res, next) { + router.get('/recover', function (req, res, next) { res.set('Content-Type', 'text/html') res.sendFile(path.join(__dirname, '../static/account-recovery.html')) }) - router.post('/request', bodyParser.urlencoded({ extended: false }), function (req, res, next) { + router.post('/recover', bodyParser.urlencoded({ extended: false }), function (req, res, next) { debug('getting request for account recovery', req.body.webid) const ldp = req.app.locals.ldp const emailService = req.app.locals.email @@ -85,7 +85,7 @@ function AccountRecovery (corsSettings, options = {}) { }) }) - router.get('/confirm', function (req, res, next) { + router.get('/validateToken', function (req, res, next) { if (!req.query.token) { res.status(406).send('Token is required') return diff --git a/lib/create-app.js b/lib/create-app.js index b84a592ff..da1cc7001 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -89,7 +89,10 @@ function createApp (argv = {}) { if (ldp.webid) { var accountRecovery = AccountRecovery(corsSettings, { redirect: '/' }) - app.use('/recovery', accountRecovery) + // adds GET /api/accounts/recover + // adds POST /api/accounts/recover + // adds GET /api/accounts/validateToken + app.use('/api/accounts/', accountRecovery) } // Adding Multi-user support @@ -113,11 +116,16 @@ function createApp (argv = {}) { } }) } - app.use('/accounts', needsOverwrite) + + // adds POST /api/accounts/new + // adds POST /api/accounts/newCert + app.use('/api/accounts', needsOverwrite) app.use('/', corsSettings, idp.get.bind(idp)) + app.get('/api/accounts/signin', (req, res, next) => { res.status(200).send() }) + app.post('/api/accounts/signout', (req, res, next) => { req.session.userId = '' req.session.identified = false diff --git a/lib/identity-provider.js b/lib/identity-provider.js index 66bca2b00..5f25fcfbb 100644 --- a/lib/identity-provider.js +++ b/lib/identity-provider.js @@ -587,7 +587,7 @@ IdentityProvider.prototype.middleware = function (corsSettings, firstUser) { router.all('/*', function (req, res) { var host = uriAbs(req) // TODO replace the hardcoded link with an arg - res.redirect('https://solid.github.io/solid-signup/?acc=accounts/new&crt=accounts/cert&domain=' + host) + res.redirect('https://solid.github.io/solid-signup/?acc=api/accounts/new&crt=api/accounts/cert&domain=' + host) }) router.use(errorHandler) diff --git a/test/identity-provider.js b/test/identity-provider.js index e7b674658..d3141f347 100644 --- a/test/identity-provider.js +++ b/test/identity-provider.js @@ -33,7 +33,7 @@ describe('Identity Provider', function () { var server = supertest(address) it('should redirect to signup on GET /accounts', function (done) { - server.get('/accounts') + server.get('/api/accounts') .expect(302, done) }) @@ -56,13 +56,13 @@ describe('Identity Provider', function () { it('should generate a certificate if spkac is valid', function (done) { var spkac = read('example_spkac.cnf') var subdomain = supertest.agent('https://nicola.' + host) - subdomain.post('/accounts/new') + subdomain.post('/api/accounts/new') .send('username=nicola') .expect(200) .end(function (err, req) { if (err) return done(err) - subdomain.post('/accounts/cert') + subdomain.post('/api/accounts/cert') .send('spkac=' + spkac + '&webid=https%3A%2F%2Fnicola.localhost%3A3457%2Fprofile%2Fcard%23me') .expect('Content-Type', /application\/x-x509-user-cert/) .expect(200) @@ -72,14 +72,14 @@ describe('Identity Provider', function () { it('should not generate a certificate if spkac is not valid', function (done) { var subdomain = supertest('https://nicola.' + host) - subdomain.post('/accounts/new') + subdomain.post('/api/accounts/new') .send('username=nicola') .expect(200) .end(function (err) { if (err) return done(err) var spkac = '' - subdomain.post('/accounts/cert') + subdomain.post('/api/accounts/cert') .send('webid=https://nicola.' + host + '/profile/card#me&spkac=' + spkac) .expect(500, done) }) @@ -97,7 +97,7 @@ describe('Identity Provider', function () { it('should return create WebID if only username is given', function (done) { var subdomain = supertest('https://nicola.' + host) - subdomain.post('/accounts/new') + subdomain.post('/api/accounts/new') .send('username=nicola') .expect(200) .end(function (err) { @@ -107,14 +107,14 @@ describe('Identity Provider', function () { it('should not create a WebID if it already exists', function (done) { var subdomain = supertest('https://nicola.' + host) - subdomain.post('/accounts/new') + subdomain.post('/api/accounts/new') .send('username=nicola') .expect(200) .end(function (err) { if (err) { return done(err) } - subdomain.post('/accounts/new') + subdomain.post('/api/accounts/new') .send('username=nicola') .expect(406) .end(function (err) { @@ -125,7 +125,7 @@ describe('Identity Provider', function () { it('should create the default folders', function (done) { var subdomain = supertest('https://nicola.' + host) - subdomain.post('/accounts/new') + subdomain.post('/api/accounts/new') .send('username=nicola') .expect(200) .end(function (err) { From cb92cbbab96b752a37943e193ba15650480d9477 Mon Sep 17 00:00:00 2001 From: Nicola Greco Date: Wed, 11 May 2016 17:00:42 -0400 Subject: [PATCH 06/24] Update scenarios.md --- test/scenarios.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/scenarios.md b/test/scenarios.md index 44e942dcc..5436c0861 100644 --- a/test/scenarios.md +++ b/test/scenarios.md @@ -3,14 +3,14 @@ - (1) User tries to get a resource - GET BOB/foo - sends 401 with redirect in HTML header - - redirect GET BOB/discoverSignin + - redirect GET BOB/api/accounts/signin - (2) User enters the webId so that the authorization endpoint is discovered - POST BOB/signin with WebID - - response is a 302 to ALICE/authorize?callback=BOB/api/oidc/rp + - response is a 302 to oidc.ALICE/authorize?callback=BOB/api/oidc/rp - (3) User is prompted password? and consent - (user enters password)? - user presses conset - - form submit to ALICE/authorize?callback=BOB/api/oidc/rp + - form submit to oidc.ALICE/authorize?callback=BOB/api/oidc/rp - response is a 302 to BOB/api/oidc/rp - BOB/api/oidc/rp redirects to BOB/foo From 1c6d5f09fd86466d727f2c50cf0cd62a19f740c1 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 09:31:42 -0400 Subject: [PATCH 07/24] adding scenario folders --- test/accounts.js | 27 +++++++++++++------- test/resources/accounts-scenario/alice/.acl | 5 ++++ test/resources/accounts-scenario/bob/.acl | 5 ++++ test/resources/accounts-scenario/bob/foo | 1 + test/resources/accounts-scenario/bob/foo.acl | 5 ++++ 5 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 test/resources/accounts-scenario/alice/.acl create mode 100644 test/resources/accounts-scenario/bob/.acl create mode 100644 test/resources/accounts-scenario/bob/foo create mode 100644 test/resources/accounts-scenario/bob/foo.acl diff --git a/test/accounts.js b/test/accounts.js index d4b84ca9a..53a1738cf 100644 --- a/test/accounts.js +++ b/test/accounts.js @@ -3,11 +3,10 @@ const parallel = require('run-parallel') const waterfall = require('run-waterfall') const path = require('path') const supertest = require('supertest') - // In this test we always assume that we are Alice function getBobFoo (alice, bob, done) { - bob.get('/') + bob.get('/foo') .expect(401) .end(done) } @@ -26,22 +25,32 @@ describe('OIDC flow', () => { let alice let bob - const solid = Solid.createServer({ - root: path.join(__dirname, '/resources'), + const alicePod = Solid.createServer({ + root: path.join(__dirname, '/resources/accounts-scenario/alice'), + sslKey: path.join(__dirname, '/keys/key.pem'), + sslCert: path.join(__dirname, '/keys/cert.pem'), + auth: 'oidc', + dataBrowser: false, + fileBrowser: false + }) + const bobPod = Solid.createServer({ + root: path.join(__dirname, '/resources/accounts-scenario/bob'), sslKey: path.join(__dirname, '/keys/key.pem'), sslCert: path.join(__dirname, '/keys/cert.pem'), - webid: true + auth: 'oidc', + dataBrowser: false, + fileBrowser: false }) before(function (done) { parallel([ (cb) => { - aliceServer = solid.listen(3456, cb) - alice = supertest('https://localhost:3456') + aliceServer = alicePod.listen(5000, cb) + alice = supertest('https://localhost:5000') }, (cb) => { - bobServer = solid.listen(3457, cb) - bob = supertest('https://localhost:3457') + bobServer = bobPod.listen(5001, cb) + bob = supertest('https://localhost:5001') } ], done) }) diff --git a/test/resources/accounts-scenario/alice/.acl b/test/resources/accounts-scenario/alice/.acl new file mode 100644 index 000000000..9362b71cf --- /dev/null +++ b/test/resources/accounts-scenario/alice/.acl @@ -0,0 +1,5 @@ +<#Owner> + a ; + <./>; + ; + , , . \ No newline at end of file diff --git a/test/resources/accounts-scenario/bob/.acl b/test/resources/accounts-scenario/bob/.acl new file mode 100644 index 000000000..49a249208 --- /dev/null +++ b/test/resources/accounts-scenario/bob/.acl @@ -0,0 +1,5 @@ +<#Owner> + a ; + <./>; + ; + , , . \ No newline at end of file diff --git a/test/resources/accounts-scenario/bob/foo b/test/resources/accounts-scenario/bob/foo new file mode 100644 index 000000000..191028156 --- /dev/null +++ b/test/resources/accounts-scenario/bob/foo @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/test/resources/accounts-scenario/bob/foo.acl b/test/resources/accounts-scenario/bob/foo.acl new file mode 100644 index 000000000..4cf18c1c8 --- /dev/null +++ b/test/resources/accounts-scenario/bob/foo.acl @@ -0,0 +1,5 @@ +<#Alice> + a ; + <./foo>; + ; + , , . \ No newline at end of file From 275110b3ad9f04b2918bc9ddc66bb2639169f310 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 09:34:20 -0400 Subject: [PATCH 08/24] enabling webid --- test/accounts.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/accounts.js b/test/accounts.js index 53a1738cf..3ec15501a 100644 --- a/test/accounts.js +++ b/test/accounts.js @@ -31,7 +31,8 @@ describe('OIDC flow', () => { sslCert: path.join(__dirname, '/keys/cert.pem'), auth: 'oidc', dataBrowser: false, - fileBrowser: false + fileBrowser: false, + webid: true }) const bobPod = Solid.createServer({ root: path.join(__dirname, '/resources/accounts-scenario/bob'), @@ -39,7 +40,8 @@ describe('OIDC flow', () => { sslCert: path.join(__dirname, '/keys/cert.pem'), auth: 'oidc', dataBrowser: false, - fileBrowser: false + fileBrowser: false, + webid: true }) before(function (done) { From 4067b415fce8f2710171af5fe712a1244f98d386 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 09:37:32 -0400 Subject: [PATCH 09/24] starting writing first test --- test/accounts.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/accounts.js b/test/accounts.js index 3ec15501a..baa639896 100644 --- a/test/accounts.js +++ b/test/accounts.js @@ -3,12 +3,17 @@ const parallel = require('run-parallel') const waterfall = require('run-waterfall') const path = require('path') const supertest = require('supertest') +const expect = require('chai').expect // In this test we always assume that we are Alice function getBobFoo (alice, bob, done) { bob.get('/foo') .expect(401) - .end(done) + .end((err, res) => { + if (err) return done(err) + expect(res).to.match(/META http-equiv="refresh"/) + done() + }) } function postBobDiscoverSignIn (alice, bob, done) { From 3937364f201335810edd180087871ec8df41a5b0 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 10:13:16 -0400 Subject: [PATCH 10/24] rename to test/api-accounts --- test/{accounts.js => api-accounts.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{accounts.js => api-accounts.js} (100%) diff --git a/test/accounts.js b/test/api-accounts.js similarity index 100% rename from test/accounts.js rename to test/api-accounts.js From 7fb0ea94e48dad7a4bbd4d974641233b8da36bb8 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 10:14:03 -0400 Subject: [PATCH 11/24] skipping tests for now --- test/api-accounts.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/api-accounts.js b/test/api-accounts.js index baa639896..24f848628 100644 --- a/test/api-accounts.js +++ b/test/api-accounts.js @@ -67,19 +67,19 @@ describe('OIDC flow', () => { if (bobServer) bobServer.close() }) - it('step1: User tries to get /foo and gets 401 and meta redirect', (done) => { + it.skip('step1: User tries to get /foo and gets 401 and meta redirect', (done) => { getBobFoo(alice, bob, done) }) - it('step2: User enters webId to signin', (done) => { + it.skip('step2: User enters webId to signin', (done) => { postBobDiscoverSignIn(alice, bob, done) }) - it('step3: User enters password', (done) => { + it.skip('step3: User enters password', (done) => { entersPasswordAndConsent(alice, bob, done) }) - it('entire flow', (done) => { + it.skip('entire flow', (done) => { waterfall([ (cb) => getBobFoo(alice, bob, cb), (cb) => postBobDiscoverSignIn(alice, bob, cb), From ab15290dcd404a697a878bc1289587d12498bf6f Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 10:16:38 -0400 Subject: [PATCH 12/24] first implementation of signin endpoint --- lib/create-app.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/create-app.js b/lib/create-app.js index da1cc7001..8383b6c78 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -13,6 +13,10 @@ var path = require('path') var EmailService = require('./email-service') const AccountRecovery = require('./account-recovery') const capabilityDiscovery = require('./capability-discovery') +const validUrl = require('valid-url') +const request = require('request') +const li = require('li') +const bodyParser = require('body-parser') var corsSettings = cors({ methods: [ @@ -122,7 +126,22 @@ function createApp (argv = {}) { app.use('/api/accounts', needsOverwrite) app.use('/', corsSettings, idp.get.bind(idp)) - app.get('/api/accounts/signin', (req, res, next) => { + app.post('/api/accounts/signin', bodyParser.urlencoded({ extended: false }), (req, res, next) => { + if (!validUrl.isUri(req.body.webid)) { + return res.status(406).send('This is not a valid URI') + } + + request.options(req.body.webid, function (err, req) { + if (err) { + res.status(409).send('Did not find a valid endpoint') + } + const linkHeaders = li.parse(req.headers.link) + if (linkHeaders['oidc']) { + res.redirect(linkHeaders['oidc']) + } else { + res.status(409).send('The URI requested is not a valid endpoint') + } + }) res.status(200).send() }) From 34b94b296e4d2cacbe57e770a965e02730ab07ae Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 10:54:01 -0400 Subject: [PATCH 13/24] implementing signin --- lib/create-app.js | 28 +-------- lib/handlers/authentication.js | 3 +- test/api-accounts.js | 105 ++++++++++++++++++++++----------- 3 files changed, 77 insertions(+), 59 deletions(-) diff --git a/lib/create-app.js b/lib/create-app.js index 8383b6c78..4507c37bb 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -13,10 +13,10 @@ var path = require('path') var EmailService = require('./email-service') const AccountRecovery = require('./account-recovery') const capabilityDiscovery = require('./capability-discovery') -const validUrl = require('valid-url') const request = require('request') const li = require('li') const bodyParser = require('body-parser') +const API = require('./api') var corsSettings = cors({ methods: [ @@ -126,30 +126,8 @@ function createApp (argv = {}) { app.use('/api/accounts', needsOverwrite) app.use('/', corsSettings, idp.get.bind(idp)) - app.post('/api/accounts/signin', bodyParser.urlencoded({ extended: false }), (req, res, next) => { - if (!validUrl.isUri(req.body.webid)) { - return res.status(406).send('This is not a valid URI') - } - - request.options(req.body.webid, function (err, req) { - if (err) { - res.status(409).send('Did not find a valid endpoint') - } - const linkHeaders = li.parse(req.headers.link) - if (linkHeaders['oidc']) { - res.redirect(linkHeaders['oidc']) - } else { - res.status(409).send('The URI requested is not a valid endpoint') - } - }) - res.status(200).send() - }) - - app.post('/api/accounts/signout', (req, res, next) => { - req.session.userId = '' - req.session.identified = false - res.status(200).send() - }) + app.post('/api/accounts/signin', corsSettings, bodyParser.urlencoded({ extended: false }), API.accounts.signin()) + app.post('/api/accounts/signout', corsSettings, API.accounts.signout()) } if (ldp.idp) { diff --git a/lib/handlers/authentication.js b/lib/handlers/authentication.js index df9f003c6..fef1df026 100644 --- a/lib/handlers/authentication.js +++ b/lib/handlers/authentication.js @@ -51,7 +51,8 @@ function handler (req, res, next) { return next() }) } else if (ldp.auth === 'oidc') { - return next(error(500, 'OIDC not implemented yet')) + setEmptySession(req) + return next() } else { return next(error(500, 'Authentication method not supported')) } diff --git a/test/api-accounts.js b/test/api-accounts.js index 24f848628..1b07449ff 100644 --- a/test/api-accounts.js +++ b/test/api-accounts.js @@ -4,27 +4,10 @@ const waterfall = require('run-waterfall') const path = require('path') const supertest = require('supertest') const expect = require('chai').expect +const nock = require('nock') // In this test we always assume that we are Alice -function getBobFoo (alice, bob, done) { - bob.get('/foo') - .expect(401) - .end((err, res) => { - if (err) return done(err) - expect(res).to.match(/META http-equiv="refresh"/) - done() - }) -} - -function postBobDiscoverSignIn (alice, bob, done) { - done() -} - -function entersPasswordAndConsent (alice, bob, done) { - done() -} - -describe('OIDC flow', () => { +describe('API', () => { let aliceServer let bobServer let alice @@ -49,6 +32,24 @@ describe('OIDC flow', () => { webid: true }) + function getBobFoo (alice, bob, done) { + bob.get('/foo') + .expect(401) + .end((err, res) => { + if (err) return done(err) + expect(res).to.match(/META http-equiv="refresh"/) + done() + }) + } + + function postBobDiscoverSignIn (alice, bob, done) { + done() + } + + function entersPasswordAndConsent (alice, bob, done) { + done() + } + before(function (done) { parallel([ (cb) => { @@ -67,23 +68,61 @@ describe('OIDC flow', () => { if (bobServer) bobServer.close() }) - it.skip('step1: User tries to get /foo and gets 401 and meta redirect', (done) => { - getBobFoo(alice, bob, done) + describe('APIs', () => { + describe('/api/accounts/signin', () => { + it('should complain if a URL is missing', (done) => { + alice.post('/api/accounts/signin') + .expect(406) + .end(done) + }) + it('should complain if a URL is invalid', (done) => { + alice.post('/api/accounts/signin') + .send('webid=HELLO') + .expect(406) + .end(done) + }) + it('should return a 409 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(409) + .end(done) + }) + it('should return a 409 if endpoint doesn\'t have oidc in the headers', (done) => { + nock('https://amazingwebsite.tld').intercept('/', 'OPTIONS').reply(200, '', { + 'Link': function (req, res, body) { + return '; rel="oidc"' + }}) + alice.post('/api/accounts/signin') + .send('webid=https://amazingwebsite.tld/') + .expect(302) + .end((err, res) => { + expect(res.header.location).to.eql('https://oidc.amazingwebsite.tld') + done(err) + }) + }) + }) }) - it.skip('step2: User enters webId to signin', (done) => { - postBobDiscoverSignIn(alice, bob, done) - }) + describe('Auth workflow', () => { + it.skip('step1: User tries to get /foo and gets 401 and meta redirect', (done) => { + getBobFoo(alice, bob, done) + }) - it.skip('step3: User enters password', (done) => { - entersPasswordAndConsent(alice, bob, done) - }) + it.skip('step2: User enters webId to signin', (done) => { + postBobDiscoverSignIn(alice, bob, done) + }) - it.skip('entire flow', (done) => { - waterfall([ - (cb) => getBobFoo(alice, bob, cb), - (cb) => postBobDiscoverSignIn(alice, bob, cb), - (cb) => entersPasswordAndConsent(alice, bob, cb) - ], done) + it.skip('step3: User enters password', (done) => { + entersPasswordAndConsent(alice, bob, done) + }) + + it.skip('entire flow', (done) => { + waterfall([ + (cb) => getBobFoo(alice, bob, cb), + (cb) => postBobDiscoverSignIn(alice, bob, cb), + (cb) => entersPasswordAndConsent(alice, bob, cb) + ], done) + }) }) }) From a607b8c199ec684a1d021711531b6aea7aec6ad0 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 10:55:08 -0400 Subject: [PATCH 14/24] adding lib/api --- lib/api/index.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lib/api/index.js diff --git a/lib/api/index.js b/lib/api/index.js new file mode 100644 index 000000000..1d7959e1e --- /dev/null +++ b/lib/api/index.js @@ -0,0 +1,3 @@ +module.exports = { + accounts: require('./accounts') +} From 9fa56d2c9eba01e1e2df7df7161f90ac327519c8 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 10:56:45 -0400 Subject: [PATCH 15/24] adding APIs and renaming folders in gitignore --- .gitignore | 12 ++++++------ .npmignore | 10 +++++----- lib/api/accounts/index.js | 4 ++++ lib/api/accounts/signin.js | 32 ++++++++++++++++++++++++++++++++ lib/api/accounts/signout.js | 9 +++++++++ 5 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 lib/api/accounts/index.js create mode 100644 lib/api/accounts/signin.js create mode 100644 lib/api/accounts/signout.js diff --git a/.gitignore b/.gitignore index dd275c6a7..dacde2b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,9 @@ node_modules/ *.swp .tern-port npm-debug.log -accounts -profile -inbox -.acl -config.json -settings +./accounts +./profile +./inbox +./.acl +./config.json +./settings diff --git a/.npmignore b/.npmignore index 6d88a7953..a348f69e9 100644 --- a/.npmignore +++ b/.npmignore @@ -1,8 +1,8 @@ config.json.bck config.json test -accounts -settings -profile -.acl -inbox +./accounts +./settings +./profile +./.acl +./inbox diff --git a/lib/api/accounts/index.js b/lib/api/accounts/index.js new file mode 100644 index 000000000..c100c78d6 --- /dev/null +++ b/lib/api/accounts/index.js @@ -0,0 +1,4 @@ +module.exports = { + signin: require('./signin'), + signout: require('./signout') +} diff --git a/lib/api/accounts/signin.js b/lib/api/accounts/signin.js new file mode 100644 index 000000000..00799dc1f --- /dev/null +++ b/lib/api/accounts/signin.js @@ -0,0 +1,32 @@ +module.exports = signin + +const validUrl = require('valid-url') +const request = require('request') +const li = require('li') + +function signin () { + return (req, res, next) => { + if (!validUrl.isUri(req.body.webid)) { + return res.status(406).send('This is not a valid URI') + } + + request({ method: 'OPTIONS', uri: req.body.webid }, function (err, req) { + if (err) { + res.status(406).send('Did not find a valid endpoint') + return + } + if (!req.headers.link) { + res.status(409).send('The URI requested is not a valid endpoint') + return + } + + const linkHeaders = li.parse(req.headers.link) + if (!linkHeaders['oidc']) { + res.status(409).send('The URI requested is not a valid endpoint') + return + } + + res.redirect(linkHeaders['oidc']) + }) + } +} diff --git a/lib/api/accounts/signout.js b/lib/api/accounts/signout.js new file mode 100644 index 000000000..16ab7372c --- /dev/null +++ b/lib/api/accounts/signout.js @@ -0,0 +1,9 @@ +module.exports = signout + +function signout () { + return (req, res, next) => { + req.session.userId = '' + req.session.identified = false + res.status(200).send() + } +} From c5f9e76044aff8a6424680b73ca736c5f2076b87 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 11:23:10 -0400 Subject: [PATCH 16/24] 406 to 400 --- lib/api/accounts/signin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/accounts/signin.js b/lib/api/accounts/signin.js index 00799dc1f..5f2162f5a 100644 --- a/lib/api/accounts/signin.js +++ b/lib/api/accounts/signin.js @@ -12,7 +12,7 @@ function signin () { request({ method: 'OPTIONS', uri: req.body.webid }, function (err, req) { if (err) { - res.status(406).send('Did not find a valid endpoint') + res.status(400).send('Did not find a valid endpoint') return } if (!req.headers.link) { From 634c9f0ba402f6e61e8ad64724ca0b0023a48ff0 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 11:30:35 -0400 Subject: [PATCH 17/24] turn all errors into 400 --- lib/api/accounts/signin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/accounts/signin.js b/lib/api/accounts/signin.js index 5f2162f5a..a04674066 100644 --- a/lib/api/accounts/signin.js +++ b/lib/api/accounts/signin.js @@ -16,13 +16,13 @@ function signin () { return } if (!req.headers.link) { - res.status(409).send('The URI requested is not a valid endpoint') + res.status(400).send('The URI requested is not a valid endpoint') return } const linkHeaders = li.parse(req.headers.link) if (!linkHeaders['oidc']) { - res.status(409).send('The URI requested is not a valid endpoint') + res.status(400).send('The URI requested is not a valid endpoint') return } From 50eaa803ccf1c1bba9ffffe3032dda5e159ba911 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 11:37:14 -0400 Subject: [PATCH 18/24] update tests to 400 --- test/api-accounts.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/api-accounts.js b/test/api-accounts.js index 1b07449ff..2385e4300 100644 --- a/test/api-accounts.js +++ b/test/api-accounts.js @@ -72,23 +72,23 @@ describe('API', () => { describe('/api/accounts/signin', () => { it('should complain if a URL is missing', (done) => { alice.post('/api/accounts/signin') - .expect(406) + .expect(400) .end(done) }) it('should complain if a URL is invalid', (done) => { alice.post('/api/accounts/signin') .send('webid=HELLO') - .expect(406) + .expect(400) .end(done) }) - it('should return a 409 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(409) + .expect(400) .end(done) }) - it('should return a 409 if endpoint doesn\'t have oidc in the headers', (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 '; rel="oidc"' From a7e01697e4ebfeef9df57b70173391cf3194a407 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 11:47:14 -0400 Subject: [PATCH 19/24] missing a 400 --- lib/api/accounts/signin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/accounts/signin.js b/lib/api/accounts/signin.js index a04674066..b91d70556 100644 --- a/lib/api/accounts/signin.js +++ b/lib/api/accounts/signin.js @@ -7,7 +7,7 @@ const li = require('li') function signin () { return (req, res, next) => { if (!validUrl.isUri(req.body.webid)) { - return res.status(406).send('This is not a valid URI') + return res.status(400).send('This is not a valid URI') } request({ method: 'OPTIONS', uri: req.body.webid }, function (err, req) { From 5bf254963fff958fab9dfbd8758380bd726baa7b Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 11:49:15 -0400 Subject: [PATCH 20/24] from oidc to oidc issuer --- lib/api/accounts/signin.js | 2 +- test/api-accounts.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/accounts/signin.js b/lib/api/accounts/signin.js index b91d70556..6cfb1816c 100644 --- a/lib/api/accounts/signin.js +++ b/lib/api/accounts/signin.js @@ -21,7 +21,7 @@ function signin () { } const linkHeaders = li.parse(req.headers.link) - if (!linkHeaders['oidc']) { + if (!linkHeaders['oidc.issuer']) { res.status(400).send('The URI requested is not a valid endpoint') return } diff --git a/test/api-accounts.js b/test/api-accounts.js index 2385e4300..79ee200fd 100644 --- a/test/api-accounts.js +++ b/test/api-accounts.js @@ -91,7 +91,7 @@ describe('API', () => { 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 '; rel="oidc"' + return '; rel="oidc.issuer"' }}) alice.post('/api/accounts/signin') .send('webid=https://amazingwebsite.tld/') From 7e80473e7245d11feff0aab960a12c431c068f03 Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 11:54:07 -0400 Subject: [PATCH 21/24] missed oidc.issuer --- lib/api/accounts/signin.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api/accounts/signin.js b/lib/api/accounts/signin.js index 6cfb1816c..01e88709f 100644 --- a/lib/api/accounts/signin.js +++ b/lib/api/accounts/signin.js @@ -21,12 +21,13 @@ function signin () { } const linkHeaders = li.parse(req.headers.link) + console.log(linkHeaders) if (!linkHeaders['oidc.issuer']) { res.status(400).send('The URI requested is not a valid endpoint') return } - res.redirect(linkHeaders['oidc']) + res.redirect(linkHeaders['oidc.issuer']) }) } } From 81778a9d001276fac546e285689113d1cec005dc Mon Sep 17 00:00:00 2001 From: Nicola Greco Date: Thu, 12 May 2016 12:50:05 -0400 Subject: [PATCH 22/24] implementing OIDC for new account creation (#349) * implementing OIDC for new account creation * adding create user call in new * oidc in new * client is under .client * adding password --- lib/identity-provider.js | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/identity-provider.js b/lib/identity-provider.js index 5f25fcfbb..31b95cd40 100644 --- a/lib/identity-provider.js +++ b/lib/identity-provider.js @@ -515,16 +515,32 @@ IdentityProvider.prototype.post = function (req, res, next) { debug('Create account with settings ', options) waterfall([ - function (callback) { - if (options.spkac && options.spkac.length > 0) { - spkac = new Buffer(stripLineEndings(options.spkac), 'utf-8') - webid('tls').generate({ - spkac: spkac, - agent: agent // TODO generate agent - }, callback) - } else { + (callback) => { + if (this.auth !== 'oidc') { + return callback() + } + + const oidc = req.app.locals.oidc + return oidc.client.users + .create({ + email: options.email, + profile: agent, + name: options.name, + password: options.password + }) + .then(() => callback()) + .catch(callback) + }, + (callback) => { + if (!(this.auth === 'tls' && options.spkac && options.spkac.length > 0)) { return callback(null, false) } + + spkac = new Buffer(stripLineEndings(options.spkac), 'utf-8') + webid('tls').generate({ + spkac: spkac, + agent: agent // TODO generate agent + }, callback) }, function (newCert, callback) { cert = newCert From 08ae5efca59dccb7d3b410f13b8db8705b077a6c Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Thu, 12 May 2016 13:39:56 -0400 Subject: [PATCH 23/24] Remove unused requires --- lib/create-app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/create-app.js b/lib/create-app.js index 4507c37bb..6b739d5df 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -13,8 +13,6 @@ var path = require('path') var EmailService = require('./email-service') const AccountRecovery = require('./account-recovery') const capabilityDiscovery = require('./capability-discovery') -const request = require('request') -const li = require('li') const bodyParser = require('body-parser') const API = require('./api') From dc4c61473bb571373bc375466de806b11c0124ec Mon Sep 17 00:00:00 2001 From: nicola Date: Thu, 12 May 2016 14:56:53 -0400 Subject: [PATCH 24/24] skipping oidc if no provider is found --- lib/identity-provider.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/identity-provider.js b/lib/identity-provider.js index 31b95cd40..517bc41cc 100644 --- a/lib/identity-provider.js +++ b/lib/identity-provider.js @@ -521,6 +521,12 @@ IdentityProvider.prototype.post = function (req, res, next) { } const oidc = req.app.locals.oidc + + if (!oidc) { + debug('there is no OidcService') + return callback() + } + return oidc.client.users .create({ email: options.email,