solid-server 5.6.9-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/.acl +10 -0
  2. package/.github/workflows/ci.yml +47 -0
  3. package/.nvmrc +1 -0
  4. package/.snyk +35 -0
  5. package/.well-known/.acl +15 -0
  6. package/CHANGELOG.md +198 -0
  7. package/CONTRIBUTING.md +139 -0
  8. package/CONTRIBUTORS.md +36 -0
  9. package/Dockerfile +22 -0
  10. package/LICENSE.md +23 -0
  11. package/README.md +453 -0
  12. package/bin/lib/cli-utils.js +85 -0
  13. package/bin/lib/cli.js +39 -0
  14. package/bin/lib/init.js +94 -0
  15. package/bin/lib/invalidUsernames.js +148 -0
  16. package/bin/lib/migrateLegacyResources.js +69 -0
  17. package/bin/lib/options.js +399 -0
  18. package/bin/lib/start.js +148 -0
  19. package/bin/lib/updateIndex.js +56 -0
  20. package/bin/solid +3 -0
  21. package/bin/solid-test +12 -0
  22. package/bin/solid.js +3 -0
  23. package/common/css/solid.css +58 -0
  24. package/common/fonts/glyphicons-halflings-regular.eot +0 -0
  25. package/common/fonts/glyphicons-halflings-regular.svg +288 -0
  26. package/common/fonts/glyphicons-halflings-regular.ttf +0 -0
  27. package/common/fonts/glyphicons-halflings-regular.woff +0 -0
  28. package/common/fonts/glyphicons-halflings-regular.woff2 +0 -0
  29. package/common/img/.gitkeep +0 -0
  30. package/common/js/auth-buttons.js +65 -0
  31. package/common/js/solid.js +454 -0
  32. package/common/well-known/security.txt +2 -0
  33. package/config/defaults.js +25 -0
  34. package/config/usernames-blacklist.json +4 -0
  35. package/config.json-default +22 -0
  36. package/default-templates/emails/delete-account.js +49 -0
  37. package/default-templates/emails/invalid-username.js +30 -0
  38. package/default-templates/emails/reset-password.js +49 -0
  39. package/default-templates/emails/welcome.js +39 -0
  40. package/default-templates/new-account/.acl +26 -0
  41. package/default-templates/new-account/.meta +5 -0
  42. package/default-templates/new-account/.meta.acl +25 -0
  43. package/default-templates/new-account/.well-known/.acl +19 -0
  44. package/default-templates/new-account/favicon.ico +0 -0
  45. package/default-templates/new-account/favicon.ico.acl +26 -0
  46. package/default-templates/new-account/inbox/.acl +26 -0
  47. package/default-templates/new-account/private/.acl +10 -0
  48. package/default-templates/new-account/profile/.acl +19 -0
  49. package/default-templates/new-account/profile/card$.ttl +25 -0
  50. package/default-templates/new-account/public/.acl +19 -0
  51. package/default-templates/new-account/robots.txt +3 -0
  52. package/default-templates/new-account/robots.txt.acl +26 -0
  53. package/default-templates/new-account/settings/.acl +20 -0
  54. package/default-templates/new-account/settings/prefs.ttl +15 -0
  55. package/default-templates/new-account/settings/privateTypeIndex.ttl +4 -0
  56. package/default-templates/new-account/settings/publicTypeIndex.ttl +4 -0
  57. package/default-templates/new-account/settings/publicTypeIndex.ttl.acl +25 -0
  58. package/default-templates/new-account/settings/serverSide.ttl.acl +13 -0
  59. package/default-templates/new-account/settings/serverSide.ttl.inactive +12 -0
  60. package/default-templates/server/.acl +10 -0
  61. package/default-templates/server/.well-known/.acl +15 -0
  62. package/default-templates/server/favicon.ico +0 -0
  63. package/default-templates/server/favicon.ico.acl +15 -0
  64. package/default-templates/server/index.html +55 -0
  65. package/default-templates/server/robots.txt +3 -0
  66. package/default-templates/server/robots.txt.acl +15 -0
  67. package/default-views/account/account-deleted.hbs +17 -0
  68. package/default-views/account/delete-confirm.hbs +51 -0
  69. package/default-views/account/delete-link-sent.hbs +17 -0
  70. package/default-views/account/delete.hbs +51 -0
  71. package/default-views/account/invalid-username.hbs +22 -0
  72. package/default-views/account/register-disabled.hbs +6 -0
  73. package/default-views/account/register-form.hbs +132 -0
  74. package/default-views/account/register.hbs +24 -0
  75. package/default-views/auth/auth-hidden-fields.hbs +8 -0
  76. package/default-views/auth/change-password.hbs +58 -0
  77. package/default-views/auth/goodbye.hbs +23 -0
  78. package/default-views/auth/login-required.hbs +34 -0
  79. package/default-views/auth/login-tls.hbs +11 -0
  80. package/default-views/auth/login-username-password.hbs +28 -0
  81. package/default-views/auth/login.hbs +55 -0
  82. package/default-views/auth/no-permission.hbs +29 -0
  83. package/default-views/auth/password-changed.hbs +27 -0
  84. package/default-views/auth/reset-link-sent.hbs +21 -0
  85. package/default-views/auth/reset-password.hbs +52 -0
  86. package/default-views/auth/sharing.hbs +49 -0
  87. package/default-views/shared/create-account.hbs +8 -0
  88. package/default-views/shared/error.hbs +5 -0
  89. package/docs/how-to-delete-your-account.md +56 -0
  90. package/docs/login-and-grant-access-to-application.md +32 -0
  91. package/examples/custom-error-handling.js +31 -0
  92. package/examples/ldp-with-webid.js +12 -0
  93. package/examples/simple-express-app.js +20 -0
  94. package/examples/simple-ldp-server.js +8 -0
  95. package/favicon.ico +0 -0
  96. package/favicon.ico.acl +15 -0
  97. package/index.html +48 -0
  98. package/index.js +3 -0
  99. package/lib/acl-checker.js +274 -0
  100. package/lib/api/accounts/user-accounts.js +88 -0
  101. package/lib/api/authn/force-user.js +21 -0
  102. package/lib/api/authn/index.js +5 -0
  103. package/lib/api/authn/webid-oidc.js +202 -0
  104. package/lib/api/authn/webid-tls.js +69 -0
  105. package/lib/api/index.js +6 -0
  106. package/lib/capability-discovery.js +54 -0
  107. package/lib/common/fs-utils.js +43 -0
  108. package/lib/common/template-utils.js +50 -0
  109. package/lib/common/user-utils.js +28 -0
  110. package/lib/create-app.js +322 -0
  111. package/lib/create-server.js +107 -0
  112. package/lib/debug.js +17 -0
  113. package/lib/handlers/allow.js +82 -0
  114. package/lib/handlers/auth-proxy.js +63 -0
  115. package/lib/handlers/copy.js +39 -0
  116. package/lib/handlers/cors-proxy.js +95 -0
  117. package/lib/handlers/delete.js +23 -0
  118. package/lib/handlers/error-pages.js +212 -0
  119. package/lib/handlers/get.js +219 -0
  120. package/lib/handlers/index.js +42 -0
  121. package/lib/handlers/options.js +33 -0
  122. package/lib/handlers/patch/n3-patch-parser.js +49 -0
  123. package/lib/handlers/patch/sparql-update-parser.js +16 -0
  124. package/lib/handlers/patch.js +203 -0
  125. package/lib/handlers/post.js +99 -0
  126. package/lib/handlers/put.js +56 -0
  127. package/lib/handlers/restrict-to-top-domain.js +13 -0
  128. package/lib/header.js +136 -0
  129. package/lib/http-error.js +34 -0
  130. package/lib/ldp-container.js +161 -0
  131. package/lib/ldp-copy.js +73 -0
  132. package/lib/ldp-middleware.js +32 -0
  133. package/lib/ldp.js +620 -0
  134. package/lib/lock.js +10 -0
  135. package/lib/metadata.js +10 -0
  136. package/lib/models/account-manager.js +603 -0
  137. package/lib/models/account-template.js +152 -0
  138. package/lib/models/authenticator.js +333 -0
  139. package/lib/models/oidc-manager.js +53 -0
  140. package/lib/models/solid-host.js +131 -0
  141. package/lib/models/user-account.js +112 -0
  142. package/lib/models/webid-tls-certificate.js +184 -0
  143. package/lib/payment-pointer-discovery.js +83 -0
  144. package/lib/requests/add-cert-request.js +138 -0
  145. package/lib/requests/auth-request.js +234 -0
  146. package/lib/requests/create-account-request.js +468 -0
  147. package/lib/requests/delete-account-confirm-request.js +170 -0
  148. package/lib/requests/delete-account-request.js +144 -0
  149. package/lib/requests/login-request.js +205 -0
  150. package/lib/requests/password-change-request.js +201 -0
  151. package/lib/requests/password-reset-email-request.js +199 -0
  152. package/lib/requests/sharing-request.js +259 -0
  153. package/lib/resource-mapper.js +198 -0
  154. package/lib/server-config.js +167 -0
  155. package/lib/services/blacklist-service.js +33 -0
  156. package/lib/services/email-service.js +162 -0
  157. package/lib/services/token-service.js +47 -0
  158. package/lib/utils.js +254 -0
  159. package/lib/webid/index.js +13 -0
  160. package/lib/webid/lib/get.js +27 -0
  161. package/lib/webid/lib/parse.js +12 -0
  162. package/lib/webid/tls/index.js +185 -0
  163. package/package.json +172 -0
  164. package/renovate.json +5 -0
  165. package/robots.txt +3 -0
  166. package/robots.txt.acl +15 -0
  167. package/static/account-recovery.html +78 -0
  168. package/static/popup-redirect.html +1 -0
  169. package/static/signup.html +108 -0
  170. package/static/signup.html.acl +14 -0
@@ -0,0 +1,32 @@
1
+ # Granting access to an application as part of authentication
2
+
3
+ **TL:DR: This is a temporary option that will be removed once we have better ways of granting access to applications. We recommend you grant read and write access by default, but it depends on the application you want to trust.**
4
+
5
+ Applications provide very useful ways of consuming and producing data. Solid provides functionality that allows you to grant access to applications that you trust. This trust might be misplaced sometimes though, which Solid tries to mitigate to lessen the harm that malicious applications can cause.
6
+
7
+ One of the strategies available in Solid is to check the origins of applications, and in solid-server in Node version 5 (NSS5) we set the configuration for this to be true by default. This strengthens the security of instances running on this codebase, but it also makes it more difficult for users to test applications without explicitly granting them access beforehand.
8
+
9
+ To facilitate a better user experience, we introduced the option of granting access to applications as part of the authentication process. We believe this is a [better flow then forcing users to navigate to their profile and use the functionality provided in the trusted applications pane](https://github.com/solid/node-solid-server/issues/1142), and offer this as a temporary solution.
10
+
11
+ ## Which modes should I grant the application?
12
+
13
+ That really depends on what the application needs to do. In general we suggest granting it Read and Write access.
14
+
15
+ This is what the various modes allows the application to do:
16
+
17
+ - **Read:** Allows the application to read resources - this includes navigating through your pod and potentially copy all of your data
18
+ - **Write:** Allows the application to change and delete resources
19
+ - **Append:** Allows the application to only append new content to resources, not remove existing content
20
+ - **Control:** Allows the application to set which access modes agents have (including themself) - by allowing this you essentially allow the application complete control of your pod
21
+
22
+ The last mode is a very powerful mode to grant an application. An application could use this to remove all of your control access, essentially locking you out of your pod. (This would also mean that the application couldn't get access to your pod though, as it is still relying on your authentication.)
23
+
24
+ ## Why is it temporary?
25
+
26
+ The way this solutions works "behind the scenes" is that you are granting the application access to all resources that you have access to and that is connected to your profile (in general this would be the pod that was created alongside your WebID). This is probably fine when you want to test an application that you or someone you trust are developing, but it's definitely not the granular access control we want to offer.
27
+
28
+ We do not have a solution ready yet, but [we are working on it](https://github.com/solid/webid-oidc-spec). When the solution is specified and implemented in NSS, we will remove the option in the login flow, as you would go through another process of granting applications access that would result in a more granular control.
29
+
30
+ ## Learn more
31
+
32
+ The way that we handle access control in Solid is described in [the Web Access Control specification (WAC)]([http://solid.github.io/web-access-control-spec/](http://solid.github.io/web-access-control-spec/)). If you want to understand the reasoning for why we chose to turn origin checking on by default, you can read about it in the [Meeting W3 Solid Community Group had March 7th 2019 (last point on the agenda)](https://www.w3.org/community/solid/wiki/Meetings#20190307_1400CET).
@@ -0,0 +1,31 @@
1
+ const solid = require('../')
2
+ const path = require('path')
3
+
4
+ solid
5
+ .createServer({
6
+ webid: true,
7
+ sslCert: path.resolve('../test/keys/cert.pem'),
8
+ sslKey: path.resolve('../test/keys/key.pem'),
9
+ errorHandler: function (err, req, res, next) {
10
+ if (err.status !== 200) {
11
+ console.log('Oh no! There is an error:' + err.message)
12
+ res.status(err.status)
13
+
14
+ // Now you can send the error how you want
15
+ // Maybe you want to render an error page
16
+ // res.render('errorPage.ejs', {
17
+ // title: err.status + ": This is an error!",
18
+ // message: err.message
19
+ // })
20
+ // Or you want to respond in JSON?
21
+
22
+ res.json({
23
+ title: err.status + ': This is an error!',
24
+ message: err.message
25
+ })
26
+ }
27
+ }
28
+ })
29
+ .listen(3456, function () {
30
+ console.log('started ldp with webid on port ' + 3456)
31
+ })
@@ -0,0 +1,12 @@
1
+ const solid = require('../') // or require('solid')
2
+ const path = require('path')
3
+
4
+ solid
5
+ .createServer({
6
+ webid: true,
7
+ sslCert: path.resolve('../test/keys/cert.pem'),
8
+ sslKey: path.resolve('../test/keys/key.pem')
9
+ })
10
+ .listen(3456, function () {
11
+ console.log('started ldp with webid on port ' + 3456)
12
+ })
@@ -0,0 +1,20 @@
1
+ const express = require('express')
2
+ const solid = require('../') // or require('solid')
3
+
4
+ // Starting our express app
5
+ const app = express()
6
+
7
+ // My routes
8
+ app.get('/', function (req, res) {
9
+ console.log(req)
10
+ res.send('Welcome to my server!')
11
+ })
12
+
13
+ // Mounting solid on /ldp
14
+ const ldp = solid()
15
+ app.use('/ldp', ldp)
16
+
17
+ // Starting server
18
+ app.listen(3000, function () {
19
+ console.log('Server started on port 3000!')
20
+ })
@@ -0,0 +1,8 @@
1
+ const solid = require('../') // or require('solid-server')
2
+
3
+ // Startin solid server
4
+ const ldp = solid.createServer()
5
+ ldp.listen(3456, function () {
6
+ console.log('Starting server on port ' + 3456)
7
+ console.log('LDP will run on /')
8
+ })
package/favicon.ico ADDED
Binary file
@@ -0,0 +1,15 @@
1
+ # ACL for the default favicon.ico resource
2
+ # Server operators will be able to override it as they wish
3
+ # Public-readable
4
+
5
+ @prefix acl: <http://www.w3.org/ns/auth/acl#>.
6
+ @prefix foaf: <http://xmlns.com/foaf/0.1/>.
7
+
8
+ <#public>
9
+ a acl:Authorization;
10
+
11
+ acl:agentClass foaf:Agent; # everyone
12
+
13
+ acl:accessTo </favicon.ico>;
14
+
15
+ acl:mode acl:Read.
package/index.html ADDED
@@ -0,0 +1,48 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Welcome to Solid</title>
7
+ <link rel="stylesheet" href="/common/css/bootstrap.min.css">
8
+ <link rel="stylesheet" href="/common/css/solid.css">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <div class="page-header">
13
+ <div class="pull-right">
14
+ <button id="register" type="button" class="btn btn-primary">Register</button>
15
+ <button id="login" type="button" class="hidden btn btn-default btn-success">Log in</button>
16
+ <button id="logout" type="button" class="hidden btn btn-danger">Log out</button>
17
+ </div>
18
+
19
+ <h1>Welcome to the Solid Prototype</h1>
20
+ </div>
21
+
22
+ <p class="lead">
23
+ This is a prototype implementation of a Solid server.
24
+
25
+ It is a fully functional server, but there are no security or stability guarantees.
26
+
27
+ If you have not already done so, please create an account.
28
+ </p>
29
+
30
+ <p class="lead hidden" id="loggedIn">
31
+ You are logged in as
32
+ <a href="#" id="profileLink"></a>.
33
+ </p>
34
+
35
+ <section>
36
+ <h2>Server info</h2>
37
+ <dl>
38
+ <dt>Name</dt>
39
+ <dd>localhost</dd>
40
+ <dt>Details</dt>
41
+ <dd>Running on <a href="https://github.com/solid/node-solid-server/releases/tag/v5.6.6">Solid 5.6.6</a></dd>
42
+ </dl>
43
+ </section>
44
+ </div>
45
+ <script src="/common/js/solid-auth-client.bundle.js"></script>
46
+ <script src="/common/js/auth-buttons.js"></script>
47
+ </body>
48
+ </html>
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = require('./lib/create-app')
2
+ module.exports.createServer = require('./lib/create-server')
3
+ module.exports.startCli = require('./bin/lib/cli')
@@ -0,0 +1,274 @@
1
+ 'use strict'
2
+ /* eslint-disable node/no-deprecated-api */
3
+
4
+ const { dirname } = require('path')
5
+ const rdf = require('rdflib')
6
+ const debug = require('./debug').ACL
7
+ const debugCache = require('./debug').cache
8
+ const HTTPError = require('./http-error')
9
+ const aclCheck = require('@solid/acl-check')
10
+ const { URL } = require('url')
11
+ const { promisify } = require('util')
12
+ const fs = require('fs')
13
+ const Url = require('url')
14
+ const httpFetch = require('node-fetch')
15
+
16
+ const DEFAULT_ACL_SUFFIX = '.acl'
17
+ const ACL = rdf.Namespace('http://www.w3.org/ns/auth/acl#')
18
+
19
+ // TODO: expunge-on-write so that we can increase the caching time
20
+ // For now this cache is a big performance gain but very simple
21
+ // FIXME: set this through the config system instead of directly
22
+ // through an env var:
23
+ const EXPIRY_MS = parseInt(process.env.ACL_CACHE_TIME) || 10000 // 10 seconds
24
+ let temporaryCache = {}
25
+
26
+ // An ACLChecker exposes the permissions on a specific resource
27
+ class ACLChecker {
28
+ constructor (resource, options = {}) {
29
+ this.resource = resource
30
+ this.resourceUrl = new URL(resource)
31
+ this.agentOrigin = options.strictOrigin && options.agentOrigin ? rdf.sym(options.agentOrigin) : null
32
+ this.fetch = options.fetch
33
+ this.fetchGraph = options.fetchGraph
34
+ this.trustedOrigins = options.strictOrigin && options.trustedOrigins ? options.trustedOrigins.map(trustedOrigin => rdf.sym(trustedOrigin)) : null
35
+ this.suffix = options.suffix || DEFAULT_ACL_SUFFIX
36
+ this.aclCached = {}
37
+ this.messagesCached = {}
38
+ this.requests = {}
39
+ this.slug = options.slug
40
+ }
41
+
42
+ // Returns a fulfilled promise when the user can access the resource
43
+ // in the given mode; otherwise, rejects with an HTTP error
44
+ async can (user, mode, method = 'GET', resourceExists = true) {
45
+ const cacheKey = `${mode}-${user}`
46
+ if (this.aclCached[cacheKey]) {
47
+ return this.aclCached[cacheKey]
48
+ }
49
+ this.messagesCached[cacheKey] = this.messagesCached[cacheKey] || []
50
+
51
+ const acl = await this.getNearestACL().catch(err => {
52
+ this.messagesCached[cacheKey].push(new HTTPError(err.status || 500, err.message || err))
53
+ })
54
+ if (!acl) {
55
+ this.aclCached[cacheKey] = Promise.resolve(false)
56
+ return this.aclCached[cacheKey]
57
+ }
58
+ let resource = rdf.sym(this.resource)
59
+ if (this.resource.endsWith('/' + this.suffix)) {
60
+ resource = rdf.sym(ACLChecker.getDirectory(this.resource))
61
+ }
62
+ // If this is an ACL, Control mode must be present for any operations
63
+ if (this.isAcl(this.resource)) {
64
+ mode = 'Control'
65
+ resource = rdf.sym(this.resource.substring(0, this.resource.length - this.suffix.length))
66
+ }
67
+ // If the slug is an acl, reject
68
+ /* if (this.isAcl(this.slug)) {
69
+ this.aclCached[cacheKey] = Promise.resolve(false)
70
+ return this.aclCached[cacheKey]
71
+ } */
72
+ const directory = acl.isContainer ? rdf.sym(ACLChecker.getDirectory(acl.acl)) : null
73
+ const aclFile = rdf.sym(acl.acl)
74
+ const agent = user ? rdf.sym(user) : null
75
+ const modes = [ACL(mode)]
76
+ const agentOrigin = this.agentOrigin
77
+ const trustedOrigins = this.trustedOrigins
78
+ let originTrustedModes = []
79
+ try {
80
+ this.fetch(aclFile.doc().value)
81
+ originTrustedModes = await aclCheck.getTrustedModesForOrigin(acl.graph, resource, directory, aclFile, agentOrigin, (uriNode) => {
82
+ return this.fetch(uriNode.doc().value, acl.graph)
83
+ })
84
+ } catch (e) {
85
+ // FIXME: https://github.com/solid/acl-check/issues/23
86
+ // console.error(e.message)
87
+ }
88
+ let accessDenied = aclCheck.accessDenied(acl.graph, resource, directory, aclFile, agent, modes, agentOrigin, trustedOrigins, originTrustedModes)
89
+
90
+ // For create and update HTTP methods
91
+ if ((method === 'PUT' || method === 'PATCH' || method === 'COPY') && directory) {
92
+ // if resource and acl have same parent container,
93
+ // and resource does not exist, then accessTo Append from parent is required
94
+ if (directory.value === dirname(aclFile.value) + '/' && !resourceExists) {
95
+ const accessDeniedAccessTo = aclCheck.accessDenied(acl.graph, directory, null, aclFile, agent, [ACL('Append')], agentOrigin, trustedOrigins, originTrustedModes)
96
+ const accessResult = !accessDenied && !accessDeniedAccessTo
97
+ accessDenied = accessResult ? false : accessDenied || accessDeniedAccessTo
98
+ // debugCache('accessDenied result ' + accessDenied)
99
+ }
100
+ }
101
+ if (accessDenied && user) {
102
+ this.messagesCached[cacheKey].push(HTTPError(403, accessDenied))
103
+ } else if (accessDenied) {
104
+ this.messagesCached[cacheKey].push(HTTPError(401, 'Unauthenticated'))
105
+ }
106
+ this.aclCached[cacheKey] = Promise.resolve(!accessDenied)
107
+ return this.aclCached[cacheKey]
108
+ }
109
+
110
+ async getError (user, mode) {
111
+ const cacheKey = `${mode}-${user}`
112
+ // TODO ?? add to can: req.method and resourceExists. Actually all tests pass
113
+ this.aclCached[cacheKey] = this.aclCached[cacheKey] || this.can(user, mode)
114
+ const isAllowed = await this.aclCached[cacheKey]
115
+ return isAllowed ? null : this.messagesCached[cacheKey].reduce((prevMsg, msg) => msg.status > prevMsg.status ? msg : prevMsg, { status: 0 })
116
+ }
117
+
118
+ static getDirectory (aclFile) {
119
+ const parts = aclFile.split('/')
120
+ parts.pop()
121
+ return `${parts.join('/')}/`
122
+ }
123
+
124
+ // Gets the ACL that applies to the resource
125
+ async getNearestACL () {
126
+ const { resource } = this
127
+ let isContainer = false
128
+ const possibleACLs = this.getPossibleACLs()
129
+ const acls = [...possibleACLs]
130
+ let returnAcl = null
131
+ while (possibleACLs.length > 0 && !returnAcl) {
132
+ const acl = possibleACLs.shift()
133
+ let graph
134
+ try {
135
+ this.requests[acl] = this.requests[acl] || this.fetch(acl)
136
+ graph = await this.requests[acl]
137
+ } catch (err) {
138
+ if (err && (err.code === 'ENOENT' || err.status === 404)) {
139
+ isContainer = true
140
+ continue
141
+ }
142
+ debug(err)
143
+ throw err
144
+ }
145
+ const relative = resource.replace(acl.replace(/[^/]+$/, ''), './')
146
+ debug(`Using ACL ${acl} for ${relative}`)
147
+ returnAcl = { acl, graph, isContainer }
148
+ }
149
+ if (!returnAcl) {
150
+ throw new HTTPError(500, `No ACL found for ${resource}, searched in \n- ${acls.join('\n- ')}`)
151
+ }
152
+ const groupNodes = returnAcl.graph.statementsMatching(null, ACL('agentGroup'), null)
153
+ const groupUrls = groupNodes.map(node => node.object.value.split('#')[0])
154
+ await Promise.all(groupUrls.map(async groupUrl => {
155
+ try {
156
+ const graph = await this.fetch(groupUrl, returnAcl.graph)
157
+ this.requests[groupUrl] = this.requests[groupUrl] || graph
158
+ } catch (e) {} // failed to fetch groupUrl
159
+ }))
160
+
161
+ return returnAcl
162
+ }
163
+
164
+ // Gets all possible ACL paths that apply to the resource
165
+ getPossibleACLs () {
166
+ // Obtain the resource URI and the length of its base
167
+ const { resource: uri, suffix } = this
168
+ const [{ length: base }] = uri.match(/^[^:]+:\/*[^/]+/)
169
+
170
+ // If the URI points to a file, append the file's ACL
171
+ const possibleAcls = []
172
+ if (!uri.endsWith('/')) {
173
+ possibleAcls.push(uri.endsWith(suffix) ? uri : uri + suffix)
174
+ }
175
+
176
+ // Append the ACLs of all parent directories
177
+ for (let i = lastSlash(uri); i >= base; i = lastSlash(uri, i - 1)) {
178
+ possibleAcls.push(uri.substr(0, i + 1) + suffix)
179
+ }
180
+ return possibleAcls
181
+ }
182
+
183
+ isAcl (resource) {
184
+ return resource.endsWith(this.suffix)
185
+ }
186
+
187
+ static createFromLDPAndRequest (resource, ldp, req) {
188
+ const trustedOrigins = ldp.getTrustedOrigins(req)
189
+ return new ACLChecker(resource, {
190
+ agentOrigin: req.get('origin'),
191
+ // host: req.get('host'),
192
+ fetch: fetchLocalOrRemote(ldp.resourceMapper, ldp.serverUri),
193
+ fetchGraph: (uri, options) => {
194
+ // first try loading from local fs
195
+ return ldp.getGraph(uri, options.contentType)
196
+ // failing that, fetch remote graph
197
+ .catch(() => ldp.fetchGraph(uri, options))
198
+ },
199
+ suffix: ldp.suffixAcl,
200
+ strictOrigin: ldp.strictOrigin,
201
+ trustedOrigins,
202
+ slug: decodeURIComponent(req.headers.slug)
203
+ })
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Returns a fetch document handler used by the ACLChecker to fetch .acl
209
+ * resources up the inheritance chain.
210
+ * The `fetch(uri, callback)` results in the callback, with either:
211
+ * - `callback(err, graph)` if any error is encountered, or
212
+ * - `callback(null, graph)` with the parsed RDF graph of the fetched resource
213
+ * @return {Function} Returns a `fetch(uri, callback)` handler
214
+ */
215
+ function fetchLocalOrRemote (mapper, serverUri) {
216
+ async function doFetch (url) {
217
+ // Convert the URL into a filename
218
+ let body, path, contentType
219
+
220
+ if (Url.parse(url).host.includes(Url.parse(serverUri).host)) {
221
+ // Fetch the acl from local
222
+ try {
223
+ ({ path, contentType } = await mapper.mapUrlToFile({ url }))
224
+ } catch (err) {
225
+ // delete from cache
226
+ delete temporaryCache[url]
227
+ throw new HTTPError(404, err)
228
+ }
229
+ // Read the file from disk
230
+ body = await promisify(fs.readFile)(path, { encoding: 'utf8' })
231
+ } else {
232
+ // Fetch the acl from the internet
233
+ const response = await httpFetch(url)
234
+ body = await response.text()
235
+ contentType = response.headers.get('content-type')
236
+ }
237
+ return { body, contentType }
238
+ }
239
+ return async function fetch (url, graph = rdf.graph()) {
240
+ graph.initPropertyActions(['sameAs']) // activate sameAs
241
+ if (!temporaryCache[url]) {
242
+ // debugCache('Populating cache', url)
243
+ temporaryCache[url] = {
244
+ timer: setTimeout(() => {
245
+ // debugCache('Expunging from cache', url)
246
+ delete temporaryCache[url]
247
+ if (Object.keys(temporaryCache).length === 0) {
248
+ debugCache('Cache is empty again')
249
+ }
250
+ }, EXPIRY_MS),
251
+ promise: doFetch(url)
252
+ }
253
+ }
254
+ // debugCache('Cache hit', url)
255
+ const { body, contentType } = await temporaryCache[url].promise
256
+ // Parse the file as Turtle
257
+ rdf.parse(body, graph, url, contentType)
258
+ return graph
259
+ }
260
+ }
261
+
262
+ // Returns the index of the last slash before the given position
263
+ function lastSlash (string, pos = string.length) {
264
+ return string.lastIndexOf('/', pos)
265
+ }
266
+
267
+ module.exports = ACLChecker
268
+ module.exports.DEFAULT_ACL_SUFFIX = DEFAULT_ACL_SUFFIX
269
+
270
+ // Used in ldp and the unit tests:
271
+ module.exports.clearAclCache = function (url) {
272
+ if (url) delete temporaryCache[url]
273
+ else temporaryCache = {}
274
+ }
@@ -0,0 +1,88 @@
1
+ 'use strict'
2
+
3
+ const express = require('express')
4
+ const bodyParser = require('body-parser').urlencoded({ extended: false })
5
+ const debug = require('../../debug').accounts
6
+
7
+ const restrictToTopDomain = require('../../handlers/restrict-to-top-domain')
8
+
9
+ const CreateAccountRequest = require('../../requests/create-account-request')
10
+ const AddCertificateRequest = require('../../requests/add-cert-request')
11
+ const DeleteAccountRequest = require('../../requests/delete-account-request')
12
+ const DeleteAccountConfirmRequest = require('../../requests/delete-account-confirm-request')
13
+
14
+ /**
15
+ * Returns an Express middleware handler for checking if a particular account
16
+ * exists (used by Signup apps).
17
+ *
18
+ * @param accountManager {AccountManager}
19
+ *
20
+ * @return {Function}
21
+ */
22
+ function checkAccountExists (accountManager) {
23
+ return (req, res, next) => {
24
+ const accountUri = req.hostname
25
+
26
+ accountManager.accountUriExists(accountUri)
27
+ .then(found => {
28
+ if (!found) {
29
+ debug(`Account ${accountUri} is available (for ${req.originalUrl})`)
30
+ return res.sendStatus(404)
31
+ }
32
+ debug(`Account ${accountUri} is not available (for ${req.originalUrl})`)
33
+ next()
34
+ })
35
+ .catch(next)
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Returns an Express middleware handler for adding a new certificate to an
41
+ * existing account (POST to /api/accounts/cert).
42
+ *
43
+ * @param accountManager
44
+ *
45
+ * @return {Function}
46
+ */
47
+ function newCertificate (accountManager) {
48
+ return (req, res, next) => {
49
+ return AddCertificateRequest.handle(req, res, accountManager)
50
+ .catch(err => {
51
+ err.status = err.status || 400
52
+ next(err)
53
+ })
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Returns an Express router for providing user account related middleware
59
+ * handlers.
60
+ *
61
+ * @param accountManager {AccountManager}
62
+ *
63
+ * @return {Router}
64
+ */
65
+ function middleware (accountManager) {
66
+ const router = express.Router('/')
67
+
68
+ router.get('/', checkAccountExists(accountManager))
69
+
70
+ router.post('/api/accounts/new', restrictToTopDomain, bodyParser, CreateAccountRequest.post)
71
+ router.get(['/register', '/api/accounts/new'], restrictToTopDomain, CreateAccountRequest.get)
72
+
73
+ router.post('/api/accounts/cert', restrictToTopDomain, bodyParser, newCertificate(accountManager))
74
+
75
+ router.get('/account/delete', restrictToTopDomain, DeleteAccountRequest.get)
76
+ router.post('/account/delete', restrictToTopDomain, bodyParser, DeleteAccountRequest.post)
77
+
78
+ router.get('/account/delete/confirm', restrictToTopDomain, DeleteAccountConfirmRequest.get)
79
+ router.post('/account/delete/confirm', restrictToTopDomain, bodyParser, DeleteAccountConfirmRequest.post)
80
+
81
+ return router
82
+ }
83
+
84
+ module.exports = {
85
+ middleware,
86
+ checkAccountExists,
87
+ newCertificate
88
+ }
@@ -0,0 +1,21 @@
1
+ const debug = require('../../debug').authentication
2
+
3
+ /**
4
+ * Enforces the `--force-user` server flag, hardcoding a webid for all requests,
5
+ * for testing purposes.
6
+ */
7
+ function initialize (app, argv) {
8
+ const forceUserId = argv.forceUser
9
+ app.use('/', (req, res, next) => {
10
+ debug(`Identified user (override): ${forceUserId}`)
11
+ req.session.userId = forceUserId
12
+ if (argv.auth === 'tls') {
13
+ res.set('User', forceUserId)
14
+ }
15
+ next()
16
+ })
17
+ }
18
+
19
+ module.exports = {
20
+ initialize
21
+ }
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ oidc: require('./webid-oidc'),
3
+ tls: require('./webid-tls'),
4
+ forceUser: require('./force-user.js')
5
+ }