Skip to content

Commit c0bbc99

Browse files
authored
Merge pull request #1073 from solid/feature/prevent-api-spoofing
Restricting access to some HTTP API endpoints to top domain
2 parents bafa5f6 + 927acf6 commit c0bbc99

File tree

5 files changed

+61
-43
lines changed

5 files changed

+61
-43
lines changed

lib/api/accounts/user-accounts.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const express = require('express')
44
const bodyParser = require('body-parser').urlencoded({ extended: false })
55
const debug = require('../../debug').accounts
66

7+
const restrictToTopDomain = require('../../handlers/restrict-to-top-domain')
8+
79
const CreateAccountRequest = require('../../requests/create-account-request')
810
const AddCertificateRequest = require('../../requests/add-cert-request')
911
const DeleteAccountRequest = require('../../requests/delete-account-request')
@@ -65,16 +67,16 @@ function middleware (accountManager) {
6567

6668
router.get('/', checkAccountExists(accountManager))
6769

68-
router.post('/api/accounts/new', bodyParser, CreateAccountRequest.post)
69-
router.get(['/register', '/api/accounts/new'], CreateAccountRequest.get)
70+
router.post('/api/accounts/new', restrictToTopDomain, bodyParser, CreateAccountRequest.post)
71+
router.get(['/register', '/api/accounts/new'], restrictToTopDomain, CreateAccountRequest.get)
7072

71-
router.post('/api/accounts/cert', bodyParser, newCertificate(accountManager))
73+
router.post('/api/accounts/cert', restrictToTopDomain, bodyParser, newCertificate(accountManager))
7274

73-
router.get('/account/delete', DeleteAccountRequest.get)
74-
router.post('/account/delete', bodyParser, DeleteAccountRequest.post)
75+
router.get('/account/delete', restrictToTopDomain, DeleteAccountRequest.get)
76+
router.post('/account/delete', restrictToTopDomain, bodyParser, DeleteAccountRequest.post)
7577

76-
router.get('/account/delete/confirm', DeleteAccountConfirmRequest.get)
77-
router.post('/account/delete/confirm', bodyParser, DeleteAccountConfirmRequest.post)
78+
router.get('/account/delete/confirm', restrictToTopDomain, DeleteAccountConfirmRequest.get)
79+
router.post('/account/delete/confirm', restrictToTopDomain, bodyParser, DeleteAccountConfirmRequest.post)
7880

7981
return router
8082
}

lib/api/authn/webid-oidc.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const bodyParser = require('body-parser').urlencoded({ extended: false })
99
const OidcManager = require('../../models/oidc-manager')
1010
const { LoginRequest } = require('../../requests/login-request')
1111

12+
const restrictToTopDomain = require('../../handlers/restrict-to-top-domain')
13+
1214
const PasswordResetEmailRequest = require('../../requests/password-reset-email-request')
1315
const PasswordChangeRequest = require('../../requests/password-change-request')
1416

@@ -71,11 +73,11 @@ function middleware (oidc) {
7173

7274
router.post('/login/tls', bodyParser, LoginRequest.loginTls)
7375

74-
router.get('/account/password/reset', PasswordResetEmailRequest.get)
75-
router.post('/account/password/reset', bodyParser, PasswordResetEmailRequest.post)
76+
router.get('/account/password/reset', restrictToTopDomain, PasswordResetEmailRequest.get)
77+
router.post('/account/password/reset', restrictToTopDomain, bodyParser, PasswordResetEmailRequest.post)
7678

77-
router.get('/account/password/change', PasswordChangeRequest.get)
78-
router.post('/account/password/change', bodyParser, PasswordChangeRequest.post)
79+
router.get('/account/password/change', restrictToTopDomain, PasswordChangeRequest.get)
80+
router.post('/account/password/change', restrictToTopDomain, bodyParser, PasswordChangeRequest.post)
7981

8082
router.get('/.well-known/solid/logout/', (req, res) => res.redirect('/logout'))
8183

lib/capability-discovery.js

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,7 @@
33
* @module capability-discovery
44
*/
55
const express = require('express')
6-
7-
const serviceConfigDefaults = {
8-
'api': {
9-
'accounts': {
10-
// 'changePassword': '/api/account/changePassword',
11-
// 'delete': '/api/accounts/delete',
12-
13-
// Create new user (see IdentityProvider.post() in identity-provider.js)
14-
'new': '/api/accounts/new',
15-
'recover': '/api/accounts/recover',
16-
'signin': '/login',
17-
'signout': '/logout',
18-
'validateToken': '/api/accounts/validateToken'
19-
}
20-
}
21-
}
6+
const { URL } = require('url')
227

238
module.exports = capabilityDiscovery
249

@@ -31,7 +16,7 @@ function capabilityDiscovery () {
3116
const router = express.Router('/')
3217

3318
// Advertise the server capability discover endpoint
34-
router.get('/.well-known/solid', serviceCapabilityDocument(serviceConfigDefaults))
19+
router.get('/.well-known/solid', serviceCapabilityDocument())
3520
return router
3621
}
3722

@@ -43,12 +28,27 @@ function capabilityDiscovery () {
4328
* @param res
4429
* @param next
4530
*/
46-
function serviceCapabilityDocument (serviceConfig) {
31+
function serviceCapabilityDocument () {
4732
return (req, res) => {
48-
// Add the server root url
49-
serviceConfig.root = req.app.locals.ldp.resourceMapper.resolveUrl(req.hostname, req.path)
50-
// Add the 'apps' urls section
51-
serviceConfig.apps = req.app.locals.appUrls
52-
res.json(serviceConfig)
33+
const ldp = req.app.locals.ldp
34+
res.json({
35+
// Add the server root url
36+
root: req.app.locals.ldp.resourceMapper.resolveUrl(req.hostname, req.path),
37+
// Add the 'apps' urls section
38+
apps: req.app.locals.appUrls,
39+
api: {
40+
accounts: {
41+
// 'changePassword': '/api/account/changePassword',
42+
// 'delete': '/api/accounts/delete',
43+
44+
// Create new user (see IdentityProvider.post() in identity-provider.js)
45+
new: new URL('/api/accounts/new', ldp.serverUri),
46+
recover: new URL('/api/accounts/recover', ldp.serverUri),
47+
signin: req.app.locals.ldp.resourceMapper.resolveUrl(req.hostname, '/login'),
48+
signout: req.app.locals.ldp.resourceMapper.resolveUrl(req.hostname, '/logout'),
49+
validateToken: new URL('/api/accounts/validateToken', ldp.serverUri)
50+
}
51+
}
52+
})
5353
}
5454
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const HTTPError = require('../http-error')
2+
3+
module.exports = function (req, res, next) {
4+
const locals = req.app.locals
5+
const ldp = locals.ldp
6+
const serverUri = locals.host.serverUri
7+
const hostname = ldp.resourceMapper.resolveUrl(req.hostname)
8+
if (hostname === serverUri) {
9+
return next()
10+
}
11+
const isLoggedIn = !!(req.session && req.session.userId)
12+
return next(new HTTPError(isLoggedIn ? 403 : 401, 'Not allowed to access top-level APIs on accounts'))
13+
}

test/integration/account-creation-oidc-test.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,21 @@ describe('AccountManager (OIDC account creation tests)', function () {
8080
})
8181

8282
it('should not create WebID if no username is given', (done) => {
83-
let subdomain = supertest('https://nicola.' + host)
83+
let subdomain = supertest('https://' + host)
8484
subdomain.post('/api/accounts/new')
8585
.send('username=&password=12345')
8686
.expect(400, done)
8787
})
8888

8989
it('should not create WebID if no password is given', (done) => {
90-
let subdomain = supertest('https://nicola.' + host)
90+
let subdomain = supertest('https://' + host)
9191
subdomain.post('/api/accounts/new')
9292
.send('username=nicola&password=')
9393
.expect(400, done)
9494
})
9595

9696
it('should not create a WebID if it already exists', function (done) {
97-
var subdomain = supertest('https://nicola.' + host)
97+
var subdomain = supertest('https://' + host)
9898
subdomain.post('/api/accounts/new')
9999
.send('username=nicola&password=12345&acceptToc=true')
100100
.expect(302)
@@ -112,14 +112,14 @@ describe('AccountManager (OIDC account creation tests)', function () {
112112
})
113113

114114
it('should not create WebID if T&C is not accepted', (done) => {
115-
let subdomain = supertest('https://nicola.' + host)
115+
let subdomain = supertest('https://' + host)
116116
subdomain.post('/api/accounts/new')
117117
.send('username=nicola&password=12345&acceptToc=')
118118
.expect(400, done)
119119
})
120120

121121
it('should create the default folders', function (done) {
122-
var subdomain = supertest('https://nicola.' + host)
122+
var subdomain = supertest('https://' + host)
123123
subdomain.post('/api/accounts/new')
124124
.send('username=nicola&password=12345&acceptToc=true')
125125
.expect(302)
@@ -150,14 +150,15 @@ describe('AccountManager (OIDC account creation tests)', function () {
150150
}).timeout(20000)
151151

152152
it('should link WebID to the root account', function (done) {
153-
var subdomain = supertest('https://nicola.' + host)
154-
subdomain.post('/api/accounts/new')
153+
const domain = supertest('https://' + host)
154+
domain.post('/api/accounts/new')
155155
.send('username=nicola&password=12345&acceptToc=true')
156156
.expect(302)
157157
.end(function (err) {
158158
if (err) {
159159
return done(err)
160160
}
161+
const subdomain = supertest('https://nicola.' + host)
161162
subdomain.get('/.meta')
162163
.expect(200)
163164
.end(function (err, data) {
@@ -185,7 +186,7 @@ describe('AccountManager (OIDC account creation tests)', function () {
185186

186187
describe('after setting up account', () => {
187188
beforeEach(done => {
188-
var subdomain = supertest('https://nicola.' + host)
189+
var subdomain = supertest('https://' + host)
189190
subdomain.post('/api/accounts/new')
190191
.send('username=nicola&password=12345&acceptToc=true')
191192
.end(done)
@@ -294,7 +295,7 @@ describe('Signup page where Terms & Conditions are not being enforced', () => {
294295
})
295296

296297
it('should not enforce T&C upon creating account', function (done) {
297-
var subdomain = supertest('https://nicola.' + host)
298+
var subdomain = supertest('https://' + host)
298299
subdomain.post('/api/accounts/new')
299300
.send('username=nicola&password=12345')
300301
.expect(302, done)

0 commit comments

Comments
 (0)