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,603 @@
1
+ 'use strict'
2
+ /* eslint-disable node/no-deprecated-api */
3
+
4
+ const url = require('url')
5
+ const rdf = require('rdflib')
6
+ const ns = require('solid-namespace')(rdf)
7
+
8
+ const defaults = require('../../config/defaults')
9
+ const UserAccount = require('./user-account')
10
+ const AccountTemplate = require('./account-template')
11
+ const debug = require('./../debug').accounts
12
+
13
+ const DEFAULT_PROFILE_CONTENT_TYPE = 'text/turtle'
14
+ const DEFAULT_ADMIN_USERNAME = 'admin'
15
+
16
+ /**
17
+ * Manages account creation (determining whether accounts exist, creating
18
+ * directory structures for new accounts, saving credentials).
19
+ *
20
+ * @class AccountManager
21
+ */
22
+ class AccountManager {
23
+ /**
24
+ * @constructor
25
+ * @param [options={}] {Object}
26
+ * @param [options.authMethod] {string} Primary authentication method (e.g. 'oidc')
27
+ * @param [options.emailService] {EmailService}
28
+ * @param [options.tokenService] {TokenService}
29
+ * @param [options.host] {SolidHost}
30
+ * @param [options.multiuser=false] {boolean} (argv.multiuser) Is the server running
31
+ * in multiuser mode (users can sign up for accounts) or single user
32
+ * (such as a personal website).
33
+ * @param [options.store] {LDP}
34
+ * @param [options.pathCard] {string}
35
+ * @param [options.suffixURI] {string}
36
+ * @param [options.accountTemplatePath] {string} Path to the account template
37
+ * directory (will be used as a template for default containers, etc, when
38
+ * creating new accounts).
39
+ */
40
+ constructor (options = {}) {
41
+ if (!options.host) {
42
+ throw Error('AccountManager requires a host instance')
43
+ }
44
+ this.host = options.host
45
+ this.emailService = options.emailService
46
+ this.tokenService = options.tokenService
47
+ this.authMethod = options.authMethod || defaults.auth
48
+ this.multiuser = options.multiuser || false
49
+ this.store = options.store
50
+ this.pathCard = options.pathCard || 'profile/card'
51
+ this.suffixURI = options.suffixURI || '#me'
52
+ this.accountTemplatePath = options.accountTemplatePath || './default-templates/new-account/'
53
+ }
54
+
55
+ /**
56
+ * Factory method for new account manager creation. Usage:
57
+ *
58
+ * ```
59
+ * let options = { host, multiuser, store }
60
+ * let accontManager = AccountManager.from(options)
61
+ * ```
62
+ *
63
+ * @param [options={}] {Object} See the `constructor()` docstring.
64
+ *
65
+ * @return {AccountManager}
66
+ */
67
+ static from (options) {
68
+ return new AccountManager(options)
69
+ }
70
+
71
+ /**
72
+ * Tests whether an account already exists for a given username.
73
+ * Usage:
74
+ *
75
+ * ```
76
+ * accountManager.accountExists('alice')
77
+ * .then(exists => {
78
+ * console.log('answer: ', exists)
79
+ * })
80
+ * ```
81
+ * @param accountName {string} Account username, e.g. 'alice'
82
+ *
83
+ * @return {Promise<boolean>}
84
+ */
85
+ accountExists (accountName) {
86
+ let accountUri
87
+ let cardPath
88
+
89
+ try {
90
+ accountUri = this.accountUriFor(accountName)
91
+ accountUri = url.parse(accountUri).hostname
92
+
93
+ cardPath = url.resolve('/', this.pathCard)
94
+ } catch (err) {
95
+ return Promise.reject(err)
96
+ }
97
+
98
+ return this.accountUriExists(accountUri, cardPath)
99
+ }
100
+
101
+ /**
102
+ * Tests whether a given account URI (e.g. 'https://alice.example.com/')
103
+ * already exists on the server.
104
+ *
105
+ * @param accountUri {string}
106
+ * @param accountResource {string}
107
+ *
108
+ * @return {Promise<boolean>}
109
+ */
110
+ async accountUriExists (accountUri, accountResource = '/') {
111
+ try {
112
+ return await this.store.exists(accountUri, accountResource)
113
+ } catch (err) {
114
+ return false
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Constructs a directory path for a given account (used for account creation).
120
+ * Usage:
121
+ *
122
+ * ```
123
+ * // If solid-server was launched with '/accounts/' as the root directory
124
+ * // and serverUri: 'https://example.com'
125
+ *
126
+ * accountManager.accountDirFor('alice') // -> '/accounts/alice.example.com'
127
+ * ```
128
+ *
129
+ * @param accountName {string}
130
+ *
131
+ * @return {string}
132
+ */
133
+ accountDirFor (accountName) {
134
+ const { hostname } = url.parse(this.accountUriFor(accountName))
135
+ return this.store.resourceMapper.resolveFilePath(hostname)
136
+ }
137
+
138
+ /**
139
+ * Composes an account URI for a given account name.
140
+ * Usage (given a host with serverUri of 'https://example.com'):
141
+ *
142
+ * ```
143
+ * // in multi user mode:
144
+ * acctMgr.accountUriFor('alice')
145
+ * // -> 'https://alice.example.com'
146
+ *
147
+ * // in single user mode:
148
+ * acctMgr.accountUriFor()
149
+ * // -> 'https://example.com'
150
+ * ```
151
+ *
152
+ * @param [accountName] {string}
153
+ *
154
+ * @throws {Error} If `this.host` has not been initialized with serverUri,
155
+ * or if in multiuser mode and accountName is not provided.
156
+ * @return {string}
157
+ */
158
+ accountUriFor (accountName) {
159
+ const accountUri = this.multiuser
160
+ ? this.host.accountUriFor(accountName)
161
+ : this.host.serverUri // single user mode
162
+
163
+ return accountUri
164
+ }
165
+
166
+ /**
167
+ * Composes a WebID (uri with hash fragment) for a given account name.
168
+ * Usage:
169
+ *
170
+ * ```
171
+ * // in multi user mode:
172
+ * acctMgr.accountWebIdFor('alice')
173
+ * // -> 'https://alice.example.com/profile/card#me'
174
+ *
175
+ * // in single user mode:
176
+ * acctMgr.accountWebIdFor()
177
+ * // -> 'https://example.com/profile/card#me'
178
+ * ```
179
+ *
180
+ * @param [accountName] {string}
181
+ *
182
+ * @throws {Error} via accountUriFor()
183
+ *
184
+ * @return {string|null}
185
+ */
186
+ accountWebIdFor (accountName) {
187
+ const accountUri = this.accountUriFor(accountName)
188
+
189
+ const webIdUri = url.parse(url.resolve(accountUri, this.pathCard))
190
+ webIdUri.hash = this.suffixURI
191
+ return webIdUri.format()
192
+ }
193
+
194
+ /**
195
+ * Returns the root .acl URI for a given user account (the account recovery
196
+ * email is stored there).
197
+ *
198
+ * @param userAccount {UserAccount}
199
+ *
200
+ * @throws {Error} via accountUriFor()
201
+ *
202
+ * @return {string} Root .acl URI
203
+ */
204
+ rootAclFor (userAccount) {
205
+ const accountUri = this.accountUriFor(userAccount.username)
206
+
207
+ return url.resolve(accountUri, this.store.suffixAcl)
208
+ }
209
+
210
+ /**
211
+ * Adds a newly generated WebID-TLS certificate to the user's profile graph.
212
+ *
213
+ * @param certificate {WebIdTlsCertificate}
214
+ * @param userAccount {UserAccount}
215
+ *
216
+ * @return {Promise<Graph>}
217
+ */
218
+ addCertKeyToProfile (certificate, userAccount) {
219
+ if (!certificate) {
220
+ throw new TypeError('Cannot add empty certificate to user profile')
221
+ }
222
+
223
+ return this.getProfileGraphFor(userAccount)
224
+ .then(profileGraph => {
225
+ return this.addCertKeyToGraph(certificate, profileGraph)
226
+ })
227
+ .then(profileGraph => {
228
+ return this.saveProfileGraph(profileGraph, userAccount)
229
+ })
230
+ }
231
+
232
+ /**
233
+ * Returns a parsed WebID Profile graph for a given user account.
234
+ *
235
+ * @param userAccount {UserAccount}
236
+ * @param [contentType] {string} Content type of the profile to parse
237
+ *
238
+ * @throws {Error} If the user account's WebID is missing
239
+ * @throws {Error} HTTP 404 error (via `getGraph()`) if the profile resource
240
+ * is not found
241
+ *
242
+ * @return {Promise<Graph>}
243
+ */
244
+ getProfileGraphFor (userAccount, contentType = DEFAULT_PROFILE_CONTENT_TYPE) {
245
+ const webId = userAccount.webId
246
+ if (!webId) {
247
+ const error = new Error('Cannot fetch profile graph, missing WebId URI')
248
+ error.status = 400
249
+ return Promise.reject(error)
250
+ }
251
+
252
+ const uri = userAccount.profileUri
253
+
254
+ return this.store.getGraph(uri, contentType)
255
+ .catch(error => {
256
+ error.message = `Error retrieving profile graph ${uri}: ` + error.message
257
+ throw error
258
+ })
259
+ }
260
+
261
+ /**
262
+ * Serializes and saves a given graph to the user's WebID Profile (and returns
263
+ * the original graph object, as it was before serialization).
264
+ *
265
+ * @param profileGraph {Graph}
266
+ * @param userAccount {UserAccount}
267
+ * @param [contentType] {string}
268
+ *
269
+ * @return {Promise<Graph>}
270
+ */
271
+ saveProfileGraph (profileGraph, userAccount, contentType = DEFAULT_PROFILE_CONTENT_TYPE) {
272
+ const webId = userAccount.webId
273
+ if (!webId) {
274
+ const error = new Error('Cannot save profile graph, missing WebId URI')
275
+ error.status = 400
276
+ return Promise.reject(error)
277
+ }
278
+
279
+ const uri = userAccount.profileUri
280
+
281
+ return this.store.putGraph(profileGraph, uri, contentType)
282
+ }
283
+
284
+ /**
285
+ * Adds the certificate's Public Key related triples to a user's profile graph.
286
+ *
287
+ * @param certificate {WebIdTlsCertificate}
288
+ * @param graph {Graph} Parsed WebID Profile graph
289
+ *
290
+ * @return {Graph}
291
+ */
292
+ addCertKeyToGraph (certificate, graph) {
293
+ const webId = rdf.namedNode(certificate.webId)
294
+ const key = rdf.namedNode(certificate.keyUri)
295
+ const timeCreated = rdf.literal(certificate.date.toISOString(), ns.xsd('dateTime'))
296
+ const modulus = rdf.literal(certificate.modulus, ns.xsd('hexBinary'))
297
+ const exponent = rdf.literal(certificate.exponent, ns.xsd('int'))
298
+ const title = rdf.literal('Created by solid-server')
299
+ const label = rdf.literal(certificate.commonName)
300
+
301
+ graph.add(webId, ns.cert('key'), key)
302
+ graph.add(key, ns.rdf('type'), ns.cert('RSAPublicKey'))
303
+ graph.add(key, ns.dct('title'), title)
304
+ graph.add(key, ns.rdfs('label'), label)
305
+ graph.add(key, ns.dct('created'), timeCreated)
306
+ graph.add(key, ns.cert('modulus'), modulus)
307
+ graph.add(key, ns.cert('exponent'), exponent)
308
+
309
+ return graph
310
+ }
311
+
312
+ /**
313
+ * Creates and returns a `UserAccount` instance from submitted user data
314
+ * (typically something like `req.body`, from a signup form).
315
+ *
316
+ * @param userData {Object} Options hashmap, like `req.body`.
317
+ * Either a `username` or a `webid` property is required.
318
+ *
319
+ * @param [userData.username] {string}
320
+ * @param [uesrData.webid] {string}
321
+ *
322
+ * @param [userData.email] {string}
323
+ * @param [userData.name] {string}
324
+ *
325
+ * @throws {Error} (via `accountWebIdFor()`) If in multiuser mode and no
326
+ * username passed
327
+ *
328
+ * @return {UserAccount}
329
+ */
330
+ userAccountFrom (userData) {
331
+ const userConfig = {
332
+ username: userData.username,
333
+ email: userData.email,
334
+ name: userData.name,
335
+ externalWebId: userData.externalWebId,
336
+ localAccountId: userData.localAccountId,
337
+ webId: userData.webid || userData.webId || userData.externalWebId
338
+ }
339
+
340
+ if (userConfig.username) {
341
+ userConfig.username = userConfig.username.toLowerCase()
342
+ }
343
+
344
+ try {
345
+ userConfig.webId = userConfig.webId || this.accountWebIdFor(userConfig.username)
346
+ } catch (err) {
347
+ if (err.message === 'Cannot construct uri for blank account name') {
348
+ throw new Error('Username or web id is required')
349
+ } else {
350
+ throw err
351
+ }
352
+ }
353
+
354
+ if (userConfig.username) {
355
+ if (userConfig.externalWebId && !userConfig.localAccountId) {
356
+ // External Web ID exists, derive the local account id from username
357
+ userConfig.localAccountId = this.accountWebIdFor(userConfig.username)
358
+ .split('//')[1] // drop the https://
359
+ }
360
+ } else { // no username - derive it from web id
361
+ if (userConfig.externalWebId) {
362
+ userConfig.username = userConfig.externalWebId
363
+ } else {
364
+ userConfig.username = this.usernameFromWebId(userConfig.webId)
365
+ }
366
+ }
367
+
368
+ return UserAccount.from(userConfig)
369
+ }
370
+
371
+ usernameFromWebId (webId) {
372
+ if (!this.multiuser) {
373
+ return DEFAULT_ADMIN_USERNAME
374
+ }
375
+
376
+ const profileUrl = url.parse(webId)
377
+ const hostname = profileUrl.hostname
378
+
379
+ return hostname.split('.')[0]
380
+ }
381
+
382
+ /**
383
+ * Creates a user account storage folder (from a default account template).
384
+ *
385
+ * @param userAccount {UserAccount}
386
+ *
387
+ * @return {Promise}
388
+ */
389
+ createAccountFor (userAccount) {
390
+ const template = AccountTemplate.for(userAccount)
391
+
392
+ const templatePath = this.accountTemplatePath
393
+ const accountDir = this.accountDirFor(userAccount.username)
394
+
395
+ debug(`Creating account folder for ${userAccount.webId} at ${accountDir}`)
396
+
397
+ return AccountTemplate.copyTemplateDir(templatePath, accountDir)
398
+ .then(() => {
399
+ return template.processAccount(accountDir)
400
+ })
401
+ }
402
+
403
+ /**
404
+ * Generates an expiring one-time-use token for password reset purposes
405
+ * (the user's Web ID is saved in the token service).
406
+ *
407
+ * @param userAccount {UserAccount}
408
+ *
409
+ * @return {string} Generated token
410
+ */
411
+ generateResetToken (userAccount) {
412
+ return this.tokenService.generate('reset-password', { webId: userAccount.webId })
413
+ }
414
+
415
+ /**
416
+ * Generates an expiring one-time-use token for password reset purposes
417
+ * (the user's Web ID is saved in the token service).
418
+ *
419
+ * @param userAccount {UserAccount}
420
+ *
421
+ * @return {string} Generated token
422
+ */
423
+ generateDeleteToken (userAccount) {
424
+ return this.tokenService.generate('delete-account', {
425
+ webId: userAccount.webId,
426
+ email: userAccount.email
427
+ })
428
+ }
429
+
430
+ /**
431
+ * Validates that a token exists and is not expired, and returns the saved
432
+ * token contents, or throws an error if invalid.
433
+ * Does not consume / clear the token.
434
+ *
435
+ * @param token {string}
436
+ *
437
+ * @throws {Error} If missing or invalid token
438
+ *
439
+ * @return {Object|false} Saved token data object if verified, false otherwise
440
+ */
441
+ validateDeleteToken (token) {
442
+ const tokenValue = this.tokenService.verify('delete-account', token)
443
+
444
+ if (!tokenValue) {
445
+ throw new Error('Invalid or expired delete account token')
446
+ }
447
+
448
+ return tokenValue
449
+ }
450
+
451
+ /**
452
+ * Validates that a token exists and is not expired, and returns the saved
453
+ * token contents, or throws an error if invalid.
454
+ * Does not consume / clear the token.
455
+ *
456
+ * @param token {string}
457
+ *
458
+ * @throws {Error} If missing or invalid token
459
+ *
460
+ * @return {Object|false} Saved token data object if verified, false otherwise
461
+ */
462
+ validateResetToken (token) {
463
+ const tokenValue = this.tokenService.verify('reset-password', token)
464
+
465
+ if (!tokenValue) {
466
+ throw new Error('Invalid or expired reset token')
467
+ }
468
+
469
+ return tokenValue
470
+ }
471
+
472
+ /**
473
+ * Returns a password reset URL (to be emailed to the user upon request)
474
+ *
475
+ * @param token {string} One-time-use expiring token, via the TokenService
476
+ * @param returnToUrl {string}
477
+ *
478
+ * @return {string}
479
+ */
480
+ passwordResetUrl (token, returnToUrl) {
481
+ let resetUrl = url.resolve(this.host.serverUri,
482
+ `/account/password/change?token=${token}`)
483
+
484
+ if (returnToUrl) {
485
+ resetUrl += `&returnToUrl=${returnToUrl}`
486
+ }
487
+
488
+ return resetUrl
489
+ }
490
+
491
+ /**
492
+ * Returns a password reset URL (to be emailed to the user upon request)
493
+ *
494
+ * @param token {string} One-time-use expiring token, via the TokenService
495
+ * @param returnToUrl {string}
496
+ *
497
+ * @return {string}
498
+ */
499
+ getAccountDeleteUrl (token) {
500
+ return url.resolve(this.host.serverUri, `/account/delete/confirm?token=${token}`)
501
+ }
502
+
503
+ /**
504
+ * Parses and returns an account recovery email stored in a user's root .acl
505
+ *
506
+ * @param userAccount {UserAccount}
507
+ *
508
+ * @return {Promise<string|undefined>}
509
+ */
510
+ loadAccountRecoveryEmail (userAccount) {
511
+ return Promise.resolve()
512
+ .then(() => {
513
+ const rootAclUri = this.rootAclFor(userAccount)
514
+
515
+ return this.store.getGraph(rootAclUri)
516
+ })
517
+ .then(rootAclGraph => {
518
+ const matches = rootAclGraph.match(null, ns.acl('agent'))
519
+
520
+ let recoveryMailto = matches.find(agent => {
521
+ return agent.object.value.startsWith('mailto:')
522
+ })
523
+
524
+ if (recoveryMailto) {
525
+ recoveryMailto = recoveryMailto.object.value.replace('mailto:', '')
526
+ }
527
+
528
+ return recoveryMailto
529
+ })
530
+ }
531
+
532
+ verifyEmailDependencies (userAccount) {
533
+ if (!this.emailService) {
534
+ throw new Error('Email service is not set up')
535
+ }
536
+
537
+ if (!userAccount.email) {
538
+ throw new Error('Account recovery email has not been provided')
539
+ }
540
+ }
541
+
542
+ sendDeleteAccountEmail (userAccount) {
543
+ return Promise.resolve()
544
+ .then(() => this.verifyEmailDependencies(userAccount))
545
+ .then(() => this.generateDeleteToken(userAccount))
546
+ .then(resetToken => {
547
+ const deleteUrl = this.getAccountDeleteUrl(resetToken)
548
+
549
+ const emailData = {
550
+ to: userAccount.email,
551
+ webId: userAccount.webId,
552
+ deleteUrl: deleteUrl
553
+ }
554
+
555
+ return this.emailService.sendWithTemplate('delete-account', emailData)
556
+ })
557
+ }
558
+
559
+ sendPasswordResetEmail (userAccount, returnToUrl) {
560
+ return Promise.resolve()
561
+ .then(() => this.verifyEmailDependencies(userAccount))
562
+ .then(() => this.generateResetToken(userAccount))
563
+ .then(resetToken => {
564
+ const resetUrl = this.passwordResetUrl(resetToken, returnToUrl)
565
+
566
+ const emailData = {
567
+ to: userAccount.email,
568
+ webId: userAccount.webId,
569
+ resetUrl
570
+ }
571
+
572
+ return this.emailService.sendWithTemplate('reset-password', emailData)
573
+ })
574
+ }
575
+
576
+ /**
577
+ * Sends a Welcome email (on new user signup).
578
+ *
579
+ * @param newUser {UserAccount}
580
+ * @param newUser.email {string}
581
+ * @param newUser.webId {string}
582
+ * @param newUser.name {string}
583
+ *
584
+ * @return {Promise}
585
+ */
586
+ sendWelcomeEmail (newUser) {
587
+ const emailService = this.emailService
588
+
589
+ if (!emailService || !newUser.email) {
590
+ return Promise.resolve(null)
591
+ }
592
+
593
+ const emailData = {
594
+ to: newUser.email,
595
+ webid: newUser.webId,
596
+ name: newUser.displayName
597
+ }
598
+
599
+ return emailService.sendWithTemplate('welcome', emailData)
600
+ }
601
+ }
602
+
603
+ module.exports = AccountManager