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.
- package/.acl +10 -0
- package/.github/workflows/ci.yml +47 -0
- package/.nvmrc +1 -0
- package/.snyk +35 -0
- package/.well-known/.acl +15 -0
- package/CHANGELOG.md +198 -0
- package/CONTRIBUTING.md +139 -0
- package/CONTRIBUTORS.md +36 -0
- package/Dockerfile +22 -0
- package/LICENSE.md +23 -0
- package/README.md +453 -0
- package/bin/lib/cli-utils.js +85 -0
- package/bin/lib/cli.js +39 -0
- package/bin/lib/init.js +94 -0
- package/bin/lib/invalidUsernames.js +148 -0
- package/bin/lib/migrateLegacyResources.js +69 -0
- package/bin/lib/options.js +399 -0
- package/bin/lib/start.js +148 -0
- package/bin/lib/updateIndex.js +56 -0
- package/bin/solid +3 -0
- package/bin/solid-test +12 -0
- package/bin/solid.js +3 -0
- package/common/css/solid.css +58 -0
- package/common/fonts/glyphicons-halflings-regular.eot +0 -0
- package/common/fonts/glyphicons-halflings-regular.svg +288 -0
- package/common/fonts/glyphicons-halflings-regular.ttf +0 -0
- package/common/fonts/glyphicons-halflings-regular.woff +0 -0
- package/common/fonts/glyphicons-halflings-regular.woff2 +0 -0
- package/common/img/.gitkeep +0 -0
- package/common/js/auth-buttons.js +65 -0
- package/common/js/solid.js +454 -0
- package/common/well-known/security.txt +2 -0
- package/config/defaults.js +25 -0
- package/config/usernames-blacklist.json +4 -0
- package/config.json-default +22 -0
- package/default-templates/emails/delete-account.js +49 -0
- package/default-templates/emails/invalid-username.js +30 -0
- package/default-templates/emails/reset-password.js +49 -0
- package/default-templates/emails/welcome.js +39 -0
- package/default-templates/new-account/.acl +26 -0
- package/default-templates/new-account/.meta +5 -0
- package/default-templates/new-account/.meta.acl +25 -0
- package/default-templates/new-account/.well-known/.acl +19 -0
- package/default-templates/new-account/favicon.ico +0 -0
- package/default-templates/new-account/favicon.ico.acl +26 -0
- package/default-templates/new-account/inbox/.acl +26 -0
- package/default-templates/new-account/private/.acl +10 -0
- package/default-templates/new-account/profile/.acl +19 -0
- package/default-templates/new-account/profile/card$.ttl +25 -0
- package/default-templates/new-account/public/.acl +19 -0
- package/default-templates/new-account/robots.txt +3 -0
- package/default-templates/new-account/robots.txt.acl +26 -0
- package/default-templates/new-account/settings/.acl +20 -0
- package/default-templates/new-account/settings/prefs.ttl +15 -0
- package/default-templates/new-account/settings/privateTypeIndex.ttl +4 -0
- package/default-templates/new-account/settings/publicTypeIndex.ttl +4 -0
- package/default-templates/new-account/settings/publicTypeIndex.ttl.acl +25 -0
- package/default-templates/new-account/settings/serverSide.ttl.acl +13 -0
- package/default-templates/new-account/settings/serverSide.ttl.inactive +12 -0
- package/default-templates/server/.acl +10 -0
- package/default-templates/server/.well-known/.acl +15 -0
- package/default-templates/server/favicon.ico +0 -0
- package/default-templates/server/favicon.ico.acl +15 -0
- package/default-templates/server/index.html +55 -0
- package/default-templates/server/robots.txt +3 -0
- package/default-templates/server/robots.txt.acl +15 -0
- package/default-views/account/account-deleted.hbs +17 -0
- package/default-views/account/delete-confirm.hbs +51 -0
- package/default-views/account/delete-link-sent.hbs +17 -0
- package/default-views/account/delete.hbs +51 -0
- package/default-views/account/invalid-username.hbs +22 -0
- package/default-views/account/register-disabled.hbs +6 -0
- package/default-views/account/register-form.hbs +132 -0
- package/default-views/account/register.hbs +24 -0
- package/default-views/auth/auth-hidden-fields.hbs +8 -0
- package/default-views/auth/change-password.hbs +58 -0
- package/default-views/auth/goodbye.hbs +23 -0
- package/default-views/auth/login-required.hbs +34 -0
- package/default-views/auth/login-tls.hbs +11 -0
- package/default-views/auth/login-username-password.hbs +28 -0
- package/default-views/auth/login.hbs +55 -0
- package/default-views/auth/no-permission.hbs +29 -0
- package/default-views/auth/password-changed.hbs +27 -0
- package/default-views/auth/reset-link-sent.hbs +21 -0
- package/default-views/auth/reset-password.hbs +52 -0
- package/default-views/auth/sharing.hbs +49 -0
- package/default-views/shared/create-account.hbs +8 -0
- package/default-views/shared/error.hbs +5 -0
- package/docs/how-to-delete-your-account.md +56 -0
- package/docs/login-and-grant-access-to-application.md +32 -0
- package/examples/custom-error-handling.js +31 -0
- package/examples/ldp-with-webid.js +12 -0
- package/examples/simple-express-app.js +20 -0
- package/examples/simple-ldp-server.js +8 -0
- package/favicon.ico +0 -0
- package/favicon.ico.acl +15 -0
- package/index.html +48 -0
- package/index.js +3 -0
- package/lib/acl-checker.js +274 -0
- package/lib/api/accounts/user-accounts.js +88 -0
- package/lib/api/authn/force-user.js +21 -0
- package/lib/api/authn/index.js +5 -0
- package/lib/api/authn/webid-oidc.js +202 -0
- package/lib/api/authn/webid-tls.js +69 -0
- package/lib/api/index.js +6 -0
- package/lib/capability-discovery.js +54 -0
- package/lib/common/fs-utils.js +43 -0
- package/lib/common/template-utils.js +50 -0
- package/lib/common/user-utils.js +28 -0
- package/lib/create-app.js +322 -0
- package/lib/create-server.js +107 -0
- package/lib/debug.js +17 -0
- package/lib/handlers/allow.js +82 -0
- package/lib/handlers/auth-proxy.js +63 -0
- package/lib/handlers/copy.js +39 -0
- package/lib/handlers/cors-proxy.js +95 -0
- package/lib/handlers/delete.js +23 -0
- package/lib/handlers/error-pages.js +212 -0
- package/lib/handlers/get.js +219 -0
- package/lib/handlers/index.js +42 -0
- package/lib/handlers/options.js +33 -0
- package/lib/handlers/patch/n3-patch-parser.js +49 -0
- package/lib/handlers/patch/sparql-update-parser.js +16 -0
- package/lib/handlers/patch.js +203 -0
- package/lib/handlers/post.js +99 -0
- package/lib/handlers/put.js +56 -0
- package/lib/handlers/restrict-to-top-domain.js +13 -0
- package/lib/header.js +136 -0
- package/lib/http-error.js +34 -0
- package/lib/ldp-container.js +161 -0
- package/lib/ldp-copy.js +73 -0
- package/lib/ldp-middleware.js +32 -0
- package/lib/ldp.js +620 -0
- package/lib/lock.js +10 -0
- package/lib/metadata.js +10 -0
- package/lib/models/account-manager.js +603 -0
- package/lib/models/account-template.js +152 -0
- package/lib/models/authenticator.js +333 -0
- package/lib/models/oidc-manager.js +53 -0
- package/lib/models/solid-host.js +131 -0
- package/lib/models/user-account.js +112 -0
- package/lib/models/webid-tls-certificate.js +184 -0
- package/lib/payment-pointer-discovery.js +83 -0
- package/lib/requests/add-cert-request.js +138 -0
- package/lib/requests/auth-request.js +234 -0
- package/lib/requests/create-account-request.js +468 -0
- package/lib/requests/delete-account-confirm-request.js +170 -0
- package/lib/requests/delete-account-request.js +144 -0
- package/lib/requests/login-request.js +205 -0
- package/lib/requests/password-change-request.js +201 -0
- package/lib/requests/password-reset-email-request.js +199 -0
- package/lib/requests/sharing-request.js +259 -0
- package/lib/resource-mapper.js +198 -0
- package/lib/server-config.js +167 -0
- package/lib/services/blacklist-service.js +33 -0
- package/lib/services/email-service.js +162 -0
- package/lib/services/token-service.js +47 -0
- package/lib/utils.js +254 -0
- package/lib/webid/index.js +13 -0
- package/lib/webid/lib/get.js +27 -0
- package/lib/webid/lib/parse.js +12 -0
- package/lib/webid/tls/index.js +185 -0
- package/package.json +172 -0
- package/renovate.json +5 -0
- package/robots.txt +3 -0
- package/robots.txt.acl +15 -0
- package/static/account-recovery.html +78 -0
- package/static/popup-redirect.html +1 -0
- package/static/signup.html +108 -0
- 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
|