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,234 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* eslint-disable node/no-deprecated-api */
|
|
3
|
+
|
|
4
|
+
const url = require('url')
|
|
5
|
+
const debug = require('./../debug').authentication
|
|
6
|
+
|
|
7
|
+
const IDToken = require('@solid/oidc-op/src/IDToken')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hidden form fields from the login page that must be passed through to the
|
|
11
|
+
* Authentication request.
|
|
12
|
+
*
|
|
13
|
+
* @type {Array<string>}
|
|
14
|
+
*/
|
|
15
|
+
const AUTH_QUERY_PARAMS = ['response_type', 'display', 'scope',
|
|
16
|
+
'client_id', 'redirect_uri', 'state', 'nonce', 'request']
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Base authentication request (used for login and password reset workflows).
|
|
20
|
+
*/
|
|
21
|
+
class AuthRequest {
|
|
22
|
+
/**
|
|
23
|
+
* @constructor
|
|
24
|
+
* @param [options.response] {ServerResponse} middleware `res` object
|
|
25
|
+
* @param [options.session] {Session} req.session
|
|
26
|
+
* @param [options.userStore] {UserStore}
|
|
27
|
+
* @param [options.accountManager] {AccountManager}
|
|
28
|
+
* @param [options.returnToUrl] {string}
|
|
29
|
+
* @param [options.authQueryParams] {Object} Key/value hashmap of parsed query
|
|
30
|
+
* parameters that will be passed through to the /authorize endpoint.
|
|
31
|
+
* @param [options.enforceToc] {boolean} Whether or not to enforce the service provider's T&C
|
|
32
|
+
* @param [options.tocUri] {string} URI to the service provider's T&C
|
|
33
|
+
*/
|
|
34
|
+
constructor (options) {
|
|
35
|
+
this.response = options.response
|
|
36
|
+
this.session = options.session || {}
|
|
37
|
+
this.userStore = options.userStore
|
|
38
|
+
this.accountManager = options.accountManager
|
|
39
|
+
this.returnToUrl = options.returnToUrl
|
|
40
|
+
this.authQueryParams = options.authQueryParams || {}
|
|
41
|
+
this.localAuth = options.localAuth
|
|
42
|
+
this.enforceToc = options.enforceToc
|
|
43
|
+
this.tocUri = options.tocUri
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extracts a given parameter from the request - either from a GET query param,
|
|
48
|
+
* a POST body param, or an express registered `/:param`.
|
|
49
|
+
* Usage:
|
|
50
|
+
*
|
|
51
|
+
* ```
|
|
52
|
+
* AuthRequest.parseParameter(req, 'client_id')
|
|
53
|
+
* // -> 'client123'
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @param req {IncomingRequest}
|
|
57
|
+
* @param parameter {string} Parameter key
|
|
58
|
+
*
|
|
59
|
+
* @return {string|null}
|
|
60
|
+
*/
|
|
61
|
+
static parseParameter (req, parameter) {
|
|
62
|
+
const query = req.query || {}
|
|
63
|
+
const body = req.body || {}
|
|
64
|
+
const params = req.params || {}
|
|
65
|
+
|
|
66
|
+
return query[parameter] || body[parameter] || params[parameter] || null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Extracts the options in common to most auth-related requests.
|
|
71
|
+
*
|
|
72
|
+
* @param req
|
|
73
|
+
* @param res
|
|
74
|
+
*
|
|
75
|
+
* @return {Object}
|
|
76
|
+
*/
|
|
77
|
+
static requestOptions (req, res) {
|
|
78
|
+
let userStore, accountManager, localAuth
|
|
79
|
+
|
|
80
|
+
if (req.app && req.app.locals) {
|
|
81
|
+
const locals = req.app.locals
|
|
82
|
+
|
|
83
|
+
if (locals.oidc) {
|
|
84
|
+
userStore = locals.oidc.users
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
accountManager = locals.accountManager
|
|
88
|
+
|
|
89
|
+
localAuth = locals.localAuth
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const authQueryParams = AuthRequest.extractAuthParams(req)
|
|
93
|
+
const returnToUrl = AuthRequest.parseParameter(req, 'returnToUrl')
|
|
94
|
+
const acceptToc = AuthRequest.parseParameter(req, 'acceptToc') === 'true'
|
|
95
|
+
|
|
96
|
+
const options = {
|
|
97
|
+
response: res,
|
|
98
|
+
session: req.session,
|
|
99
|
+
userStore,
|
|
100
|
+
accountManager,
|
|
101
|
+
returnToUrl,
|
|
102
|
+
authQueryParams,
|
|
103
|
+
localAuth,
|
|
104
|
+
acceptToc
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return options
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Initializes query params required by Oauth2/OIDC type work flow from the
|
|
112
|
+
* request body.
|
|
113
|
+
* Only authorized params are loaded, all others are discarded.
|
|
114
|
+
*
|
|
115
|
+
* @param req {IncomingRequest}
|
|
116
|
+
*
|
|
117
|
+
* @return {Object}
|
|
118
|
+
*/
|
|
119
|
+
static extractAuthParams (req) {
|
|
120
|
+
let params
|
|
121
|
+
if (req.method === 'POST') {
|
|
122
|
+
params = req.body
|
|
123
|
+
} else {
|
|
124
|
+
params = req.query
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!params) { return {} }
|
|
128
|
+
|
|
129
|
+
const extracted = {}
|
|
130
|
+
|
|
131
|
+
const paramKeys = AUTH_QUERY_PARAMS
|
|
132
|
+
let value
|
|
133
|
+
|
|
134
|
+
for (const p of paramKeys) {
|
|
135
|
+
value = params[p]
|
|
136
|
+
// value = value === 'undefined' ? undefined : value
|
|
137
|
+
extracted[p] = value
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Special case because solid-auth-client does not include redirect in params
|
|
141
|
+
if (!extracted.redirect_uri && params.request) {
|
|
142
|
+
extracted.redirect_uri = IDToken.decode(params.request).payload.redirect_uri
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return extracted
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Calls the appropriate form to display to the user.
|
|
150
|
+
* Serves as an error handler for this request workflow.
|
|
151
|
+
*
|
|
152
|
+
* @param error {Error}
|
|
153
|
+
*/
|
|
154
|
+
error (error, body) {
|
|
155
|
+
error.statusCode = error.statusCode || 400
|
|
156
|
+
|
|
157
|
+
this.renderForm(error, body)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Initializes a session (for subsequent authentication/authorization) with
|
|
162
|
+
* a given user's credentials.
|
|
163
|
+
*
|
|
164
|
+
* @param userAccount {UserAccount}
|
|
165
|
+
*/
|
|
166
|
+
initUserSession (userAccount) {
|
|
167
|
+
const session = this.session
|
|
168
|
+
|
|
169
|
+
debug('Initializing user session with webId: ', userAccount.webId)
|
|
170
|
+
|
|
171
|
+
session.userId = userAccount.webId
|
|
172
|
+
session.subject = {
|
|
173
|
+
_id: userAccount.webId
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return userAccount
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Returns this installation's /authorize url. Used for redirecting post-login
|
|
181
|
+
* and post-signup.
|
|
182
|
+
*
|
|
183
|
+
* @return {string}
|
|
184
|
+
*/
|
|
185
|
+
authorizeUrl () {
|
|
186
|
+
const host = this.accountManager.host
|
|
187
|
+
const authUrl = host.authEndpoint
|
|
188
|
+
|
|
189
|
+
authUrl.query = this.authQueryParams
|
|
190
|
+
|
|
191
|
+
return url.format(authUrl)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Returns this installation's /register url. Used for redirecting post-signup.
|
|
196
|
+
*
|
|
197
|
+
* @return {string}
|
|
198
|
+
*/
|
|
199
|
+
registerUrl () {
|
|
200
|
+
const host = this.accountManager.host
|
|
201
|
+
const signupUrl = url.parse(url.resolve(host.serverUri, '/register'))
|
|
202
|
+
|
|
203
|
+
signupUrl.query = this.authQueryParams
|
|
204
|
+
|
|
205
|
+
return url.format(signupUrl)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Returns this installation's /login url.
|
|
210
|
+
*
|
|
211
|
+
* @return {string}
|
|
212
|
+
*/
|
|
213
|
+
loginUrl () {
|
|
214
|
+
const host = this.accountManager.host
|
|
215
|
+
const signupUrl = url.parse(url.resolve(host.serverUri, '/login'))
|
|
216
|
+
|
|
217
|
+
signupUrl.query = this.authQueryParams
|
|
218
|
+
|
|
219
|
+
return url.format(signupUrl)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
sharingUrl () {
|
|
223
|
+
const host = this.accountManager.host
|
|
224
|
+
const sharingUrl = url.parse(url.resolve(host.serverUri, '/sharing'))
|
|
225
|
+
|
|
226
|
+
sharingUrl.query = this.authQueryParams
|
|
227
|
+
|
|
228
|
+
return url.format(sharingUrl)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
AuthRequest.AUTH_QUERY_PARAMS = AUTH_QUERY_PARAMS
|
|
233
|
+
|
|
234
|
+
module.exports = AuthRequest
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const AuthRequest = require('./auth-request')
|
|
4
|
+
const WebIdTlsCertificate = require('../models/webid-tls-certificate')
|
|
5
|
+
const debug = require('../debug').accounts
|
|
6
|
+
const blacklistService = require('../services/blacklist-service')
|
|
7
|
+
const { isValidUsername } = require('../common/user-utils')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a 'create new user account' http request (either a POST to the
|
|
11
|
+
* `/accounts/api/new` endpoint, or a GET to `/register`).
|
|
12
|
+
*
|
|
13
|
+
* Intended just for browser-based requests; to create new user accounts from
|
|
14
|
+
* a command line, use the `AccountManager` class directly.
|
|
15
|
+
*
|
|
16
|
+
* This is an abstract class, subclasses are created (for example
|
|
17
|
+
* `CreateOidcAccountRequest`) depending on which Authentication mode the server
|
|
18
|
+
* is running in.
|
|
19
|
+
*
|
|
20
|
+
* @class CreateAccountRequest
|
|
21
|
+
*/
|
|
22
|
+
class CreateAccountRequest extends AuthRequest {
|
|
23
|
+
/**
|
|
24
|
+
* @param [options={}] {Object}
|
|
25
|
+
* @param [options.accountManager] {AccountManager}
|
|
26
|
+
* @param [options.userAccount] {UserAccount}
|
|
27
|
+
* @param [options.session] {Session} e.g. req.session
|
|
28
|
+
* @param [options.response] {HttpResponse}
|
|
29
|
+
* @param [options.returnToUrl] {string} If present, redirect the agent to
|
|
30
|
+
* this url on successful account creation
|
|
31
|
+
* @param [options.enforceToc] {boolean} Whether or not to enforce the service provider's T&C
|
|
32
|
+
* @param [options.tocUri] {string} URI to the service provider's T&C
|
|
33
|
+
* @param [options.acceptToc] {boolean} Whether or not user has accepted T&C
|
|
34
|
+
*/
|
|
35
|
+
constructor (options) {
|
|
36
|
+
super(options)
|
|
37
|
+
|
|
38
|
+
this.username = options.username
|
|
39
|
+
this.userAccount = options.userAccount
|
|
40
|
+
this.acceptToc = options.acceptToc
|
|
41
|
+
this.disablePasswordChecks = options.disablePasswordChecks
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Factory method, creates an appropriate CreateAccountRequest subclass from
|
|
46
|
+
* an HTTP request (browser form submit), depending on the authn method.
|
|
47
|
+
*
|
|
48
|
+
* @param req
|
|
49
|
+
* @param res
|
|
50
|
+
*
|
|
51
|
+
* @throws {Error} If required parameters are missing (via
|
|
52
|
+
* `userAccountFrom()`), or it encounters an unsupported authentication
|
|
53
|
+
* scheme.
|
|
54
|
+
*
|
|
55
|
+
* @return {CreateOidcAccountRequest|CreateTlsAccountRequest}
|
|
56
|
+
*/
|
|
57
|
+
static fromParams (req, res) {
|
|
58
|
+
const options = AuthRequest.requestOptions(req, res)
|
|
59
|
+
|
|
60
|
+
const locals = req.app.locals
|
|
61
|
+
const authMethod = locals.authMethod
|
|
62
|
+
const accountManager = locals.accountManager
|
|
63
|
+
|
|
64
|
+
const body = req.body || {}
|
|
65
|
+
|
|
66
|
+
if (body.username) {
|
|
67
|
+
options.username = body.username.toLowerCase()
|
|
68
|
+
options.userAccount = accountManager.userAccountFrom(body)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
options.enforceToc = locals.enforceToc
|
|
72
|
+
options.tocUri = locals.tocUri
|
|
73
|
+
options.disablePasswordChecks = locals.disablePasswordChecks
|
|
74
|
+
|
|
75
|
+
switch (authMethod) {
|
|
76
|
+
case 'oidc':
|
|
77
|
+
options.password = body.password
|
|
78
|
+
return new CreateOidcAccountRequest(options)
|
|
79
|
+
case 'tls':
|
|
80
|
+
options.spkac = body.spkac
|
|
81
|
+
return new CreateTlsAccountRequest(options)
|
|
82
|
+
default:
|
|
83
|
+
throw new TypeError('Unsupported authentication scheme')
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static async post (req, res) {
|
|
88
|
+
const request = CreateAccountRequest.fromParams(req, res)
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
request.validate()
|
|
92
|
+
await request.createAccount()
|
|
93
|
+
} catch (error) {
|
|
94
|
+
request.error(error, req.body)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static get (req, res) {
|
|
99
|
+
const request = CreateAccountRequest.fromParams(req, res)
|
|
100
|
+
|
|
101
|
+
return Promise.resolve()
|
|
102
|
+
.then(() => request.renderForm())
|
|
103
|
+
.catch(error => request.error(error))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Renders the Register form
|
|
108
|
+
*/
|
|
109
|
+
renderForm (error, data = {}) {
|
|
110
|
+
const authMethod = this.accountManager.authMethod
|
|
111
|
+
|
|
112
|
+
const params = Object.assign({}, this.authQueryParams, {
|
|
113
|
+
enforceToc: this.enforceToc,
|
|
114
|
+
loginUrl: this.loginUrl(),
|
|
115
|
+
multiuser: this.accountManager.multiuser,
|
|
116
|
+
registerDisabled: authMethod === 'tls',
|
|
117
|
+
returnToUrl: this.returnToUrl,
|
|
118
|
+
tocUri: this.tocUri,
|
|
119
|
+
disablePasswordChecks: this.disablePasswordChecks,
|
|
120
|
+
username: data.username,
|
|
121
|
+
name: data.name,
|
|
122
|
+
email: data.email,
|
|
123
|
+
acceptToc: data.acceptToc
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (error) {
|
|
127
|
+
params.error = error.message
|
|
128
|
+
this.response.status(error.statusCode)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.response.render('account/register', params)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates an account for a given user (from a POST to `/api/accounts/new`)
|
|
136
|
+
*
|
|
137
|
+
* @throws {Error} If errors were encountering while validating the username.
|
|
138
|
+
*
|
|
139
|
+
* @return {Promise<UserAccount>} Resolves with newly created account instance
|
|
140
|
+
*/
|
|
141
|
+
async createAccount () {
|
|
142
|
+
const userAccount = this.userAccount
|
|
143
|
+
const accountManager = this.accountManager
|
|
144
|
+
|
|
145
|
+
if (userAccount.externalWebId) {
|
|
146
|
+
const error = new Error('Linked users not currently supported, sorry (external WebID without TLS?)')
|
|
147
|
+
error.statusCode = 400
|
|
148
|
+
throw error
|
|
149
|
+
}
|
|
150
|
+
this.cancelIfUsernameInvalid(userAccount)
|
|
151
|
+
this.cancelIfBlacklistedUsername(userAccount)
|
|
152
|
+
await this.cancelIfAccountExists(userAccount)
|
|
153
|
+
await this.createAccountStorage(userAccount)
|
|
154
|
+
await this.saveCredentialsFor(userAccount)
|
|
155
|
+
await this.sendResponse(userAccount)
|
|
156
|
+
|
|
157
|
+
// 'return' not used deliberately, no need to block and wait for email
|
|
158
|
+
if (userAccount && userAccount.email) {
|
|
159
|
+
debug('Sending Welcome email')
|
|
160
|
+
accountManager.sendWelcomeEmail(userAccount)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return userAccount
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Rejects with an error if an account already exists, otherwise simply
|
|
168
|
+
* resolves with the account.
|
|
169
|
+
*
|
|
170
|
+
* @param userAccount {UserAccount} Instance of the account to be created
|
|
171
|
+
*
|
|
172
|
+
* @return {Promise<UserAccount>} Chainable
|
|
173
|
+
*/
|
|
174
|
+
cancelIfAccountExists (userAccount) {
|
|
175
|
+
const accountManager = this.accountManager
|
|
176
|
+
|
|
177
|
+
return accountManager.accountExists(userAccount.username)
|
|
178
|
+
.then(exists => {
|
|
179
|
+
if (exists) {
|
|
180
|
+
debug(`Canceling account creation, ${userAccount.webId} already exists`)
|
|
181
|
+
const error = new Error('Account already exists')
|
|
182
|
+
error.status = 400
|
|
183
|
+
throw error
|
|
184
|
+
}
|
|
185
|
+
// Account does not exist, proceed
|
|
186
|
+
return userAccount
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Creates the root storage folder, initializes default containers and
|
|
192
|
+
* resources for the new account.
|
|
193
|
+
*
|
|
194
|
+
* @param userAccount {UserAccount} Instance of the account to be created
|
|
195
|
+
*
|
|
196
|
+
* @throws {Error} If errors were encountering while creating new account
|
|
197
|
+
* resources.
|
|
198
|
+
*
|
|
199
|
+
* @return {Promise<UserAccount>} Chainable
|
|
200
|
+
*/
|
|
201
|
+
createAccountStorage (userAccount) {
|
|
202
|
+
return this.accountManager.createAccountFor(userAccount)
|
|
203
|
+
.catch(error => {
|
|
204
|
+
error.message = 'Error creating account storage: ' + error.message
|
|
205
|
+
throw error
|
|
206
|
+
})
|
|
207
|
+
.then(() => {
|
|
208
|
+
debug('Account storage resources created')
|
|
209
|
+
return userAccount
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check if a username is a valid slug.
|
|
215
|
+
*
|
|
216
|
+
* @param userAccount {UserAccount} Instance of the account to be created
|
|
217
|
+
*
|
|
218
|
+
* @throws {Error} If errors were encountering while validating the
|
|
219
|
+
* username.
|
|
220
|
+
*
|
|
221
|
+
* @return {UserAccount} Chainable
|
|
222
|
+
*/
|
|
223
|
+
cancelIfUsernameInvalid (userAccount) {
|
|
224
|
+
if (!userAccount.username || !isValidUsername(userAccount.username)) {
|
|
225
|
+
debug('Invalid username ' + userAccount.username)
|
|
226
|
+
const error = new Error('Invalid username (contains invalid characters)')
|
|
227
|
+
error.status = 400
|
|
228
|
+
throw error
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return userAccount
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if a username is a valid slug.
|
|
236
|
+
*
|
|
237
|
+
* @param userAccount {UserAccount} Instance of the account to be created
|
|
238
|
+
*
|
|
239
|
+
* @throws {Error} If username is blacklisted
|
|
240
|
+
*
|
|
241
|
+
* @return {UserAccount} Chainable
|
|
242
|
+
*/
|
|
243
|
+
cancelIfBlacklistedUsername (userAccount) {
|
|
244
|
+
const validUsername = blacklistService.validate(userAccount.username)
|
|
245
|
+
if (!validUsername) {
|
|
246
|
+
debug('Invalid username ' + userAccount.username)
|
|
247
|
+
const error = new Error('Invalid username (username is blacklisted)')
|
|
248
|
+
error.status = 400
|
|
249
|
+
throw error
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return userAccount
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Models a Create Account request for a server using WebID-OIDC (OpenID Connect)
|
|
258
|
+
* as a primary authentication mode. Handles saving user credentials to the
|
|
259
|
+
* `UserStore`, etc.
|
|
260
|
+
*
|
|
261
|
+
* @class CreateOidcAccountRequest
|
|
262
|
+
* @extends CreateAccountRequest
|
|
263
|
+
*/
|
|
264
|
+
class CreateOidcAccountRequest extends CreateAccountRequest {
|
|
265
|
+
/**
|
|
266
|
+
* @constructor
|
|
267
|
+
*
|
|
268
|
+
* @param [options={}] {Object} See `CreateAccountRequest` constructor docstring
|
|
269
|
+
* @param [options.password] {string} Password, as entered by the user at signup
|
|
270
|
+
* @param [options.acceptToc] {boolean} Whether or not user has accepted T&C
|
|
271
|
+
*/
|
|
272
|
+
constructor (options) {
|
|
273
|
+
super(options)
|
|
274
|
+
|
|
275
|
+
this.password = options.password
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Validates the Login request (makes sure required parameters are present),
|
|
280
|
+
* and throws an error if not.
|
|
281
|
+
*
|
|
282
|
+
* @throws {Error} If missing required params
|
|
283
|
+
*/
|
|
284
|
+
validate () {
|
|
285
|
+
let error
|
|
286
|
+
|
|
287
|
+
if (!this.username) {
|
|
288
|
+
error = new Error('Username required')
|
|
289
|
+
error.statusCode = 400
|
|
290
|
+
throw error
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!this.password) {
|
|
294
|
+
error = new Error('Password required')
|
|
295
|
+
error.statusCode = 400
|
|
296
|
+
throw error
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (this.enforceToc && !this.acceptToc) {
|
|
300
|
+
error = new Error('Accepting Terms & Conditions is required for this service')
|
|
301
|
+
error.statusCode = 400
|
|
302
|
+
throw error
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Generate salted password hash, etc.
|
|
308
|
+
*
|
|
309
|
+
* @param userAccount {UserAccount}
|
|
310
|
+
*
|
|
311
|
+
* @return {Promise<null|Graph>}
|
|
312
|
+
*/
|
|
313
|
+
saveCredentialsFor (userAccount) {
|
|
314
|
+
return this.userStore.createUser(userAccount, this.password)
|
|
315
|
+
.then(() => {
|
|
316
|
+
debug('User credentials stored')
|
|
317
|
+
return userAccount
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Generate the response for the account creation
|
|
323
|
+
*
|
|
324
|
+
* @param userAccount {UserAccount}
|
|
325
|
+
*
|
|
326
|
+
* @return {UserAccount}
|
|
327
|
+
*/
|
|
328
|
+
sendResponse (userAccount) {
|
|
329
|
+
const redirectUrl = this.returnToUrl || userAccount.podUri
|
|
330
|
+
this.response.redirect(redirectUrl)
|
|
331
|
+
|
|
332
|
+
return userAccount
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Models a Create Account request for a server using WebID-TLS as primary
|
|
338
|
+
* authentication mode. Handles generating and saving a TLS certificate, etc.
|
|
339
|
+
*
|
|
340
|
+
* @class CreateTlsAccountRequest
|
|
341
|
+
* @extends CreateAccountRequest
|
|
342
|
+
*/
|
|
343
|
+
class CreateTlsAccountRequest extends CreateAccountRequest {
|
|
344
|
+
/**
|
|
345
|
+
* @constructor
|
|
346
|
+
*
|
|
347
|
+
* @param [options={}] {Object} See `CreateAccountRequest` constructor docstring
|
|
348
|
+
* @param [options.spkac] {string}
|
|
349
|
+
* @param [options.acceptToc] {boolean} Whether or not user has accepted T&C
|
|
350
|
+
*/
|
|
351
|
+
constructor (options) {
|
|
352
|
+
super(options)
|
|
353
|
+
|
|
354
|
+
this.spkac = options.spkac
|
|
355
|
+
this.certificate = null
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Validates the Signup request (makes sure required parameters are present),
|
|
360
|
+
* and throws an error if not.
|
|
361
|
+
*
|
|
362
|
+
* @throws {Error} If missing required params
|
|
363
|
+
*/
|
|
364
|
+
validate () {
|
|
365
|
+
let error
|
|
366
|
+
|
|
367
|
+
if (!this.username) {
|
|
368
|
+
error = new Error('Username required')
|
|
369
|
+
error.statusCode = 400
|
|
370
|
+
throw error
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (this.enforceToc && !this.acceptToc) {
|
|
374
|
+
error = new Error('Accepting Terms & Conditions is required for this service')
|
|
375
|
+
error.statusCode = 400
|
|
376
|
+
throw error
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Generates a new X.509v3 RSA certificate (if `spkac` was passed in) and
|
|
382
|
+
* adds it to the user account. Used for storage in an agent's WebID
|
|
383
|
+
* Profile, for WebID-TLS authentication.
|
|
384
|
+
*
|
|
385
|
+
* @param userAccount {UserAccount}
|
|
386
|
+
* @param userAccount.webId {string} An agent's WebID URI
|
|
387
|
+
*
|
|
388
|
+
* @throws {Error} HTTP 400 error if errors were encountering during
|
|
389
|
+
* certificate generation.
|
|
390
|
+
*
|
|
391
|
+
* @return {Promise<UserAccount>} Chainable
|
|
392
|
+
*/
|
|
393
|
+
generateTlsCertificate (userAccount) {
|
|
394
|
+
if (!this.spkac) {
|
|
395
|
+
debug('Missing spkac param, not generating cert during account creation')
|
|
396
|
+
return Promise.resolve(userAccount)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return Promise.resolve()
|
|
400
|
+
.then(() => {
|
|
401
|
+
const host = this.accountManager.host
|
|
402
|
+
return WebIdTlsCertificate.fromSpkacPost(this.spkac, userAccount, host)
|
|
403
|
+
.generateCertificate()
|
|
404
|
+
})
|
|
405
|
+
.catch(err => {
|
|
406
|
+
err.status = 400
|
|
407
|
+
err.message = 'Error generating a certificate: ' + err.message
|
|
408
|
+
throw err
|
|
409
|
+
})
|
|
410
|
+
.then(certificate => {
|
|
411
|
+
debug('Generated a WebID-TLS certificate as part of account creation')
|
|
412
|
+
this.certificate = certificate
|
|
413
|
+
return userAccount
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Generates a WebID-TLS certificate and saves it to the user's profile
|
|
419
|
+
* graph.
|
|
420
|
+
*
|
|
421
|
+
* @param userAccount {UserAccount}
|
|
422
|
+
*
|
|
423
|
+
* @return {Promise<UserAccount>} Chainable
|
|
424
|
+
*/
|
|
425
|
+
saveCredentialsFor (userAccount) {
|
|
426
|
+
return this.generateTlsCertificate(userAccount)
|
|
427
|
+
.then(userAccount => {
|
|
428
|
+
if (this.certificate) {
|
|
429
|
+
return this.accountManager
|
|
430
|
+
.addCertKeyToProfile(this.certificate, userAccount)
|
|
431
|
+
.then(() => {
|
|
432
|
+
debug('Saved generated WebID-TLS certificate to profile')
|
|
433
|
+
})
|
|
434
|
+
} else {
|
|
435
|
+
debug('No certificate generated, no need to save to profile')
|
|
436
|
+
}
|
|
437
|
+
})
|
|
438
|
+
.then(() => {
|
|
439
|
+
return userAccount
|
|
440
|
+
})
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Writes the generated TLS certificate to the http Response object.
|
|
445
|
+
*
|
|
446
|
+
* @param userAccount {UserAccount}
|
|
447
|
+
*
|
|
448
|
+
* @return {UserAccount} Chainable
|
|
449
|
+
*/
|
|
450
|
+
sendResponse (userAccount) {
|
|
451
|
+
const res = this.response
|
|
452
|
+
res.set('User', userAccount.webId)
|
|
453
|
+
res.status(200)
|
|
454
|
+
|
|
455
|
+
if (this.certificate) {
|
|
456
|
+
res.set('Content-Type', 'application/x-x509-user-cert')
|
|
457
|
+
res.send(this.certificate.toDER())
|
|
458
|
+
} else {
|
|
459
|
+
res.end()
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return userAccount
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
module.exports = CreateAccountRequest
|
|
467
|
+
module.exports.CreateAccountRequest = CreateAccountRequest
|
|
468
|
+
module.exports.CreateTlsAccountRequest = CreateTlsAccountRequest
|