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,167 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Server config initialization utilities
5
+ */
6
+
7
+ const fs = require('fs-extra')
8
+ const path = require('path')
9
+ const templateUtils = require('./common/template-utils')
10
+ const fsUtils = require('./common/fs-utils')
11
+
12
+ const debug = require('./debug')
13
+
14
+ function printDebugInfo (options) {
15
+ debug.settings('Server URI: ' + options.serverUri)
16
+ debug.settings('Auth method: ' + options.auth)
17
+ debug.settings('Strict origins: ' + options.strictOrigin)
18
+ debug.settings('Allowed origins: ' + options.trustedOrigins)
19
+ debug.settings('Db path: ' + options.dbPath)
20
+ debug.settings('Config path: ' + options.configPath)
21
+ debug.settings('Suffix Acl: ' + options.suffixAcl)
22
+ debug.settings('Suffix Meta: ' + options.suffixMeta)
23
+ debug.settings('Allow WebID authentication: ' + !!options.webid)
24
+ debug.settings('Live-updates: ' + !!options.live)
25
+ debug.settings('Multi-user: ' + !!options.multiuser)
26
+ debug.settings('Suppress default data browser app: ' + options.suppressDataBrowser)
27
+ debug.settings('Default data browser app file path: ' + options.dataBrowserPath)
28
+ }
29
+
30
+ /**
31
+ * Ensures that a directory has been copied / initialized. Used to ensure that
32
+ * account templates, email templates and default apps have been copied from
33
+ * their defaults to the customizable config directory, at server startup.
34
+ *
35
+ * @param fromDir {string} Path to copy from (defaults)
36
+ *
37
+ * @param toDir {string} Path to copy to (customizable config)
38
+ *
39
+ * @return {string} Returns the absolute path for `toDir`
40
+ */
41
+ function ensureDirCopyExists (fromDir, toDir) {
42
+ fromDir = path.resolve(fromDir)
43
+ toDir = path.resolve(toDir)
44
+
45
+ if (!fs.existsSync(toDir)) {
46
+ fs.copySync(fromDir, toDir)
47
+ }
48
+
49
+ return toDir
50
+ }
51
+
52
+ /**
53
+ * Creates (copies from the server templates dir) a Welcome index page for the
54
+ * server root web directory, if one does not already exist. This page
55
+ * typically has links to account signup and login, and can be overridden by
56
+ * the server operator.
57
+ *
58
+ * @param argv {Object} Express.js app object
59
+ */
60
+ async function ensureWelcomePage (argv) {
61
+ const { resourceMapper, templates, server, host } = argv
62
+ const serverRootDir = resourceMapper.resolveFilePath(host.hostname)
63
+ const existingIndexPage = path.join(serverRootDir, 'index.html')
64
+ const packageData = require('../package.json')
65
+
66
+ if (!fs.existsSync(existingIndexPage)) {
67
+ fs.mkdirp(serverRootDir)
68
+ await fsUtils.copyTemplateDir(templates.server, serverRootDir)
69
+ await templateUtils.processHandlebarFile(existingIndexPage, {
70
+ serverName: server ? server.name : host.hostname,
71
+ serverDescription: server ? server.description : '',
72
+ serverLogo: server ? server.logo : '',
73
+ serverVersion: packageData.version
74
+ })
75
+ }
76
+
77
+ // Ensure that the root .acl file exists,
78
+ // because this was not mandatory in before 5.0.0
79
+ const existingRootAcl = path.join(serverRootDir, '.acl')
80
+ if (!fs.existsSync(existingRootAcl)) {
81
+ await fsUtils.copyTemplateDir(path.join(templates.server, '.acl'), existingRootAcl)
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Ensures that the server config directory (something like '/etc/solid-server'
87
+ * or './config', taken from the `configPath` config.json file) exists, and
88
+ * creates it if not.
89
+ *
90
+ * @param argv
91
+ *
92
+ * @return {string} Path to the server config dir
93
+ */
94
+ function initConfigDir (argv) {
95
+ const configPath = path.resolve(argv.configPath)
96
+ fs.mkdirp(configPath)
97
+
98
+ return configPath
99
+ }
100
+
101
+ /**
102
+ * Ensures that the customizable 'views' folder exists for this installation
103
+ * (copies it from default views if not).
104
+ *
105
+ * @param configPath {string} Location of configuration directory (from the
106
+ * local config.json file or passed in as cli parameter)
107
+ *
108
+ * @return {string} Path to the views dir
109
+ */
110
+ function initDefaultViews (configPath) {
111
+ const defaultViewsPath = path.join(__dirname, '../default-views')
112
+ const viewsPath = path.join(configPath, 'views')
113
+
114
+ ensureDirCopyExists(defaultViewsPath, viewsPath)
115
+
116
+ return viewsPath
117
+ }
118
+
119
+ /**
120
+ * Makes sure that the various template directories (email templates, new
121
+ * account templates, etc) have been copied from the default directories to
122
+ * this server's own config directory.
123
+ *
124
+ * @param configPath {string} Location of configuration directory (from the
125
+ * local config.json file or passed in as cli parameter)
126
+ *
127
+ * @return {Object} Returns a hashmap of template directories by type
128
+ * (new account, email, server)
129
+ */
130
+ function initTemplateDirs (configPath) {
131
+ const accountTemplatePath = ensureDirCopyExists(
132
+ path.join(__dirname, '../default-templates/new-account'),
133
+ path.join(configPath, 'templates', 'new-account')
134
+ )
135
+
136
+ const emailTemplatesPath = ensureDirCopyExists(
137
+ path.join(__dirname, '../default-templates/emails'),
138
+ path.join(configPath, 'templates', 'emails')
139
+ )
140
+
141
+ const serverTemplatePath = ensureDirCopyExists(
142
+ path.join(__dirname, '../default-templates/server'),
143
+ path.join(configPath, 'templates', 'server')
144
+ )
145
+
146
+ // Ensure that the root .acl file exists,
147
+ // because this was not mandatory in before 5.0.0
148
+ ensureDirCopyExists(
149
+ path.join(__dirname, '../default-templates/server/.acl'),
150
+ path.join(configPath, 'templates', 'server', '.acl')
151
+ )
152
+
153
+ return {
154
+ account: accountTemplatePath,
155
+ email: emailTemplatesPath,
156
+ server: serverTemplatePath
157
+ }
158
+ }
159
+
160
+ module.exports = {
161
+ ensureDirCopyExists,
162
+ ensureWelcomePage,
163
+ initConfigDir,
164
+ initDefaultViews,
165
+ initTemplateDirs,
166
+ printDebugInfo
167
+ }
@@ -0,0 +1,33 @@
1
+ const blacklistConfig = require('../../config/usernames-blacklist.json')
2
+ const blacklist = require('the-big-username-blacklist').list
3
+
4
+ class BlacklistService {
5
+ constructor () {
6
+ this.reset()
7
+ }
8
+
9
+ addWord (word) {
10
+ this.list.push(BlacklistService._prepareWord(word))
11
+ }
12
+
13
+ reset (config) {
14
+ this.list = BlacklistService._initList(config)
15
+ }
16
+
17
+ validate (word) {
18
+ return this.list.indexOf(BlacklistService._prepareWord(word)) === -1
19
+ }
20
+
21
+ static _initList (config = blacklistConfig) {
22
+ return [
23
+ ...(config.useTheBigUsernameBlacklist ? blacklist : []),
24
+ ...config.customBlacklistedUsernames
25
+ ]
26
+ }
27
+
28
+ static _prepareWord (word) {
29
+ return word.trim().toLocaleLowerCase()
30
+ }
31
+ }
32
+
33
+ module.exports = new BlacklistService()
@@ -0,0 +1,162 @@
1
+ 'use strict'
2
+
3
+ const nodemailer = require('nodemailer')
4
+ const path = require('path')
5
+ const debug = require('../debug').email
6
+
7
+ /**
8
+ * Models a Nodemailer-based email sending service.
9
+ *
10
+ * @see https://nodemailer.com/about/
11
+ */
12
+ class EmailService {
13
+ /**
14
+ * @constructor
15
+ *
16
+ * @param templatePath {string} Path to the email templates directory
17
+ *
18
+ * @param config {Object} Nodemailer configuration object
19
+ * @see https://nodemailer.com/smtp/
20
+ *
21
+ * Transport SMTP config options:
22
+ * @param config.host {string} e.g. 'smtp.gmail.com'
23
+ * @param config.port {string} e.g. '465'
24
+ * @param config.secure {boolean} Whether to use TLS when connecting to server
25
+ *
26
+ * Transport authentication config options:
27
+ * @param config.auth {Object}
28
+ * @param config.auth.user {string} Smtp username (e.g. 'alice@gmail.com')
29
+ * @param config.auth.pass {string} Smtp password
30
+ *
31
+ * Optional default Sender / `from:` address:
32
+ * @param [config.sender] {string} e.g. 'Solid Server <no-reply@databox.me>'
33
+ */
34
+ constructor (templatePath, config) {
35
+ this.mailer = nodemailer.createTransport(config)
36
+
37
+ this.sender = this.initSender(config)
38
+
39
+ this.templatePath = templatePath
40
+ }
41
+
42
+ /**
43
+ * Returns the default Sender address based on config.
44
+ *
45
+ * Note that if using Gmail for SMTP transport, Gmail ignores the sender
46
+ * `from:` address and uses the SMTP username instead (`auth.user`).
47
+ *
48
+ * @param config {Object}
49
+ *
50
+ * The sender is derived from either:
51
+ * @param [config.sender] {string} e.g. 'Solid Server <no-reply@databox.me>'
52
+ *
53
+ * or, if explicit sender is not passed in, uses:
54
+ * @param [config.host] {string} SMTP host from transport config
55
+ *
56
+ * @return {string} Sender `from:` address
57
+ */
58
+ initSender (config) {
59
+ let sender
60
+
61
+ if (config.sender) {
62
+ sender = config.sender
63
+ } else {
64
+ sender = `no-reply@${config.host}`
65
+ }
66
+
67
+ return sender
68
+ }
69
+
70
+ /**
71
+ * Sends an email (passes it through to nodemailer).
72
+ *
73
+ * @param email {Object}
74
+ *
75
+ * @return {Promise<EmailResponse>}
76
+ */
77
+ sendMail (email) {
78
+ email.from = email.from || this.sender
79
+
80
+ debug('Sending email to ' + email.to)
81
+ return this.mailer.sendMail(email)
82
+ }
83
+
84
+ /**
85
+ * Sends an email using a saved email template.
86
+ * Usage:
87
+ *
88
+ * ```
89
+ * let data = { webid: 'https://example.com/alice#me', ... }
90
+ *
91
+ * emailService.sendWithTemplate('welcome', data)
92
+ * .then(response => {
93
+ * // email sent using the 'welcome' template
94
+ * })
95
+ * ```
96
+ *
97
+ * @param templateName {string} Name of a template file in the email-templates
98
+ * dir, no extension necessary.
99
+ *
100
+ * @param data {Object} Key/value hashmap of data for an email template.
101
+ *
102
+ * @return {Promise<EmailResponse>}
103
+ */
104
+ sendWithTemplate (templateName, data) {
105
+ return Promise.resolve()
106
+ .then(() => {
107
+ const renderedEmail = this.emailFromTemplate(templateName, data)
108
+
109
+ return this.sendMail(renderedEmail)
110
+ })
111
+ }
112
+
113
+ /**
114
+ * Returns an email from a rendered template.
115
+ *
116
+ * @param templateName {string}
117
+ * @param data {Object} Key/value hashmap of data for an email template.
118
+ *
119
+ * @return {Object} Rendered email object from template
120
+ */
121
+ emailFromTemplate (templateName, data) {
122
+ const template = this.readTemplate(templateName)
123
+
124
+ return Object.assign({}, template.render(data), data)
125
+ }
126
+
127
+ /**
128
+ * Reads (requires) and returns the contents of an email template file, for
129
+ * a given template name.
130
+ *
131
+ * @param templateName {string}
132
+ *
133
+ * @throws {Error} If the template could not be found
134
+ *
135
+ * @return {Object}
136
+ */
137
+ readTemplate (templateName) {
138
+ const templateFile = this.templatePathFor(templateName)
139
+ let template
140
+
141
+ try {
142
+ template = require(templateFile)
143
+ } catch (error) {
144
+ throw new Error('Cannot find email template: ' + templateFile)
145
+ }
146
+
147
+ return template
148
+ }
149
+
150
+ /**
151
+ * Returns a template file path for a given template name.
152
+ *
153
+ * @param templateName {string}
154
+ *
155
+ * @return {string}
156
+ */
157
+ templatePathFor (templateName) {
158
+ return path.join(this.templatePath, templateName)
159
+ }
160
+ }
161
+
162
+ module.exports = EmailService
@@ -0,0 +1,47 @@
1
+ 'use strict'
2
+
3
+ const { ulid } = require('ulid')
4
+
5
+ class TokenService {
6
+ constructor () {
7
+ this.tokens = {}
8
+ }
9
+
10
+ generate (domain, data = {}) {
11
+ const token = ulid()
12
+ this.tokens[domain] = this.tokens[domain] || {}
13
+
14
+ const value = {
15
+ exp: new Date(Date.now() + 20 * 60 * 1000)
16
+ }
17
+ this.tokens[domain][token] = Object.assign({}, value, data)
18
+
19
+ return token
20
+ }
21
+
22
+ verify (domain, token) {
23
+ const now = new Date()
24
+
25
+ if (!this.tokens[domain]) {
26
+ throw new Error(`Invalid domain for tokens: ${domain}`)
27
+ }
28
+
29
+ const tokenValue = this.tokens[domain][token]
30
+
31
+ if (tokenValue && now < tokenValue.exp) {
32
+ return tokenValue
33
+ } else {
34
+ return false
35
+ }
36
+ }
37
+
38
+ remove (domain, token) {
39
+ if (!this.tokens[domain]) {
40
+ throw new Error(`Invalid domain for tokens: ${domain}`)
41
+ }
42
+
43
+ delete this.tokens[domain][token]
44
+ }
45
+ }
46
+
47
+ module.exports = TokenService
package/lib/utils.js ADDED
@@ -0,0 +1,254 @@
1
+ /* eslint-disable node/no-deprecated-api */
2
+
3
+ module.exports.pathBasename = pathBasename
4
+ module.exports.hasSuffix = hasSuffix
5
+ module.exports.serialize = serialize
6
+ module.exports.translate = translate
7
+ module.exports.stringToStream = stringToStream
8
+ module.exports.debrack = debrack
9
+ module.exports.stripLineEndings = stripLineEndings
10
+ module.exports.fullUrlForReq = fullUrlForReq
11
+ module.exports.routeResolvedFile = routeResolvedFile
12
+ module.exports.getQuota = getQuota
13
+ module.exports.overQuota = overQuota
14
+ module.exports.getContentType = getContentType
15
+ module.exports.parse = parse
16
+
17
+ const fs = require('fs')
18
+ const path = require('path')
19
+ const util = require('util')
20
+ const $rdf = require('rdflib')
21
+ const from = require('from2')
22
+ const url = require('url')
23
+ const debug = require('./debug').fs
24
+ const getSize = require('get-folder-size')
25
+ const ns = require('solid-namespace')($rdf)
26
+
27
+ /**
28
+ * Returns a fully qualified URL from an Express.js Request object.
29
+ * (It's insane that Express does not provide this natively.)
30
+ *
31
+ * Usage:
32
+ *
33
+ * ```
34
+ * console.log(util.fullUrlForReq(req))
35
+ * // -> https://example.com/path/to/resource?q1=v1
36
+ * ```
37
+ *
38
+ * @param req {IncomingRequest}
39
+ *
40
+ * @return {string}
41
+ */
42
+ function fullUrlForReq (req) {
43
+ const fullUrl = url.format({
44
+ protocol: req.protocol,
45
+ host: req.get('host'),
46
+ pathname: url.resolve(req.baseUrl, req.path),
47
+ query: req.query
48
+ })
49
+
50
+ return fullUrl
51
+ }
52
+
53
+ /**
54
+ * Removes the `<` and `>` brackets around a string and returns it.
55
+ * Used by the `allow` handler in `verifyDelegator()` logic.
56
+ * @method debrack
57
+ *
58
+ * @param s {string}
59
+ *
60
+ * @return {string}
61
+ */
62
+ function debrack (s) {
63
+ if (!s || s.length < 2) {
64
+ return s
65
+ }
66
+ if (s[0] !== '<') {
67
+ return s
68
+ }
69
+ if (s[s.length - 1] !== '>') {
70
+ return s
71
+ }
72
+ return s.substring(1, s.length - 1)
73
+ }
74
+
75
+ async function parse (data, baseUri, contentType) {
76
+ const graph = $rdf.graph()
77
+ return new Promise((resolve, reject) => {
78
+ try {
79
+ return $rdf.parse(data, graph, baseUri, contentType, (err, str) => {
80
+ if (err) {
81
+ return reject(err)
82
+ }
83
+ resolve(str)
84
+ })
85
+ } catch (err) {
86
+ return reject(err)
87
+ }
88
+ })
89
+ }
90
+
91
+ function pathBasename (fullpath) {
92
+ let bname = ''
93
+ if (fullpath) {
94
+ bname = (fullpath.lastIndexOf('/') === fullpath.length - 1)
95
+ ? ''
96
+ : path.basename(fullpath)
97
+ }
98
+ return bname
99
+ }
100
+
101
+ function hasSuffix (path, suffixes) {
102
+ for (const i in suffixes) {
103
+ if (path.indexOf(suffixes[i], path.length - suffixes[i].length) !== -1) {
104
+ return true
105
+ }
106
+ }
107
+ return false
108
+ }
109
+
110
+ function serialize (graph, baseUri, contentType) {
111
+ return new Promise((resolve, reject) => {
112
+ try {
113
+ // target, kb, base, contentType, callback
114
+ $rdf.serialize(null, graph, baseUri, contentType, function (err, result) {
115
+ if (err) {
116
+ return reject(err)
117
+ }
118
+ if (result === undefined) {
119
+ return reject(new Error('Error serializing the graph to ' +
120
+ contentType))
121
+ }
122
+
123
+ resolve(result)
124
+ })
125
+ } catch (err) {
126
+ reject(err)
127
+ }
128
+ })
129
+ }
130
+
131
+ function translate (stream, baseUri, from, to) {
132
+ return new Promise((resolve, reject) => {
133
+ let data = ''
134
+ stream
135
+ .on('data', function (chunk) {
136
+ data += chunk
137
+ })
138
+ .on('end', function () {
139
+ const graph = $rdf.graph()
140
+ $rdf.parse(data, graph, baseUri, from, function (err) {
141
+ if (err) return reject(err)
142
+ resolve(serialize(graph, baseUri, to))
143
+ })
144
+ })
145
+ })
146
+ }
147
+
148
+ function stringToStream (string) {
149
+ return from(function (size, next) {
150
+ // if there's no more content
151
+ // left in the string, close the stream.
152
+ if (!string || string.length <= 0) {
153
+ return next(null, null)
154
+ }
155
+
156
+ // Pull in a new chunk of text,
157
+ // removing it from the string.
158
+ const chunk = string.slice(0, size)
159
+ string = string.slice(size)
160
+
161
+ // Emit "chunk" from the stream.
162
+ next(null, chunk)
163
+ })
164
+ }
165
+
166
+ /**
167
+ * Removes line endings from a given string. Used for WebID TLS Certificate
168
+ * generation.
169
+ *
170
+ * @param obj {string}
171
+ *
172
+ * @return {string}
173
+ */
174
+ function stripLineEndings (obj) {
175
+ if (!obj) { return obj }
176
+
177
+ return obj.replace(/(\r\n|\n|\r)/gm, '')
178
+ }
179
+
180
+ /**
181
+ * Adds a route that serves a static file from another Node module
182
+ */
183
+ function routeResolvedFile (router, path, file, appendFileName = true) {
184
+ const fullPath = appendFileName ? path + file.match(/[^/]+$/) : path
185
+ const fullFile = require.resolve(file)
186
+ router.get(fullPath, (req, res) => res.sendFile(fullFile))
187
+ }
188
+
189
+ /**
190
+ * Returns the number of bytes that the user owning the requested POD
191
+ * may store or Infinity if no limit
192
+ */
193
+
194
+ async function getQuota (root, serverUri) {
195
+ const filename = path.join(root, 'settings/serverSide.ttl')
196
+ let prefs
197
+ try {
198
+ prefs = await _asyncReadfile(filename)
199
+ } catch (error) {
200
+ debug('Setting no quota. While reading serverSide.ttl, got ' + error)
201
+ return Infinity
202
+ }
203
+ const graph = $rdf.graph()
204
+ const storageUri = serverUri + '/'
205
+ try {
206
+ $rdf.parse(prefs, graph, storageUri, 'text/turtle')
207
+ } catch (error) {
208
+ throw new Error('Failed to parse serverSide.ttl, got ' + error)
209
+ }
210
+ return Number(graph.anyValue($rdf.sym(storageUri), ns.solid('storageQuota'))) || Infinity
211
+ }
212
+
213
+ /**
214
+ * Returns true of the user has already exceeded their quota, i.e. it
215
+ * will check if new requests should be rejected, which means they
216
+ * could PUT a large file and get away with it.
217
+ */
218
+
219
+ async function overQuota (root, serverUri) {
220
+ const quota = await getQuota(root, serverUri)
221
+ if (quota === Infinity) {
222
+ return false
223
+ }
224
+ // TODO: cache this value?
225
+ const size = await actualSize(root)
226
+ return (size > quota)
227
+ }
228
+
229
+ /**
230
+ * Returns the number of bytes that is occupied by the actual files in
231
+ * the file system. IMPORTANT NOTE: Since it traverses the directory
232
+ * to find the actual file sizes, this does a costly operation, but
233
+ * neglible for the small quotas we currently allow. If the quotas
234
+ * grow bigger, this will significantly reduce write performance, and
235
+ * so it needs to be rewritten.
236
+ */
237
+
238
+ function actualSize (root) {
239
+ return util.promisify(getSize)(root)
240
+ }
241
+
242
+ function _asyncReadfile (filename) {
243
+ return util.promisify(fs.readFile)(filename, 'utf-8')
244
+ }
245
+
246
+ /**
247
+ * Get the content type from a headers object
248
+ * @param headers An Express or Fetch API headers object
249
+ * @return {string} A content type string
250
+ */
251
+ function getContentType (headers) {
252
+ const value = headers.get ? headers.get('content-type') : headers['content-type']
253
+ return value ? value.replace(/;.*/, '') : ''
254
+ }
@@ -0,0 +1,13 @@
1
+ module.exports = webid
2
+
3
+ const tls = require('./tls')
4
+
5
+ function webid (type) {
6
+ type = type || 'tls'
7
+
8
+ if (type === 'tls') {
9
+ return tls
10
+ }
11
+
12
+ throw new Error('No other WebID supported')
13
+ }
@@ -0,0 +1,27 @@
1
+ module.exports = get
2
+
3
+ const request = require('request')
4
+ const url = require('url')
5
+
6
+ function get (webid, callback) {
7
+ const uri = url.URL(webid)
8
+ const options = {
9
+ url: uri,
10
+ method: 'GET',
11
+ headers: {
12
+ Accept: 'text/turtle, application/ld+json'
13
+ }
14
+ }
15
+
16
+ request(options, function (err, res, body) {
17
+ if (err) {
18
+ return callback(new Error('Failed to fetch profile from ' + uri.href + ': ' + err))
19
+ }
20
+
21
+ if (res.statusCode !== 200) {
22
+ return callback(new Error('Failed to retrieve WebID from ' + uri.href + ': HTTP ' + res.statusCode))
23
+ }
24
+
25
+ callback(null, body, res.headers['content-type'])
26
+ })
27
+ }