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,170 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const AuthRequest = require('./auth-request')
|
|
4
|
+
const debug = require('./../debug').accounts
|
|
5
|
+
const fs = require('fs-extra')
|
|
6
|
+
|
|
7
|
+
class DeleteAccountConfirmRequest extends AuthRequest {
|
|
8
|
+
/**
|
|
9
|
+
* @constructor
|
|
10
|
+
* @param options {Object}
|
|
11
|
+
* @param options.accountManager {AccountManager}
|
|
12
|
+
* @param options.userStore {UserStore}
|
|
13
|
+
* @param options.response {ServerResponse} express response object
|
|
14
|
+
* @param [options.token] {string} One-time reset password token (from email)
|
|
15
|
+
*/
|
|
16
|
+
constructor (options) {
|
|
17
|
+
super(options)
|
|
18
|
+
|
|
19
|
+
this.token = options.token
|
|
20
|
+
this.validToken = false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Factory method, returns an initialized instance of DeleteAccountConfirmRequest
|
|
25
|
+
* from an incoming http request.
|
|
26
|
+
*
|
|
27
|
+
* @param req {IncomingRequest}
|
|
28
|
+
* @param res {ServerResponse}
|
|
29
|
+
*
|
|
30
|
+
* @return {DeleteAccountConfirmRequest}
|
|
31
|
+
*/
|
|
32
|
+
static fromParams (req, res) {
|
|
33
|
+
const locals = req.app.locals
|
|
34
|
+
const accountManager = locals.accountManager
|
|
35
|
+
const userStore = locals.oidc.users
|
|
36
|
+
|
|
37
|
+
const token = this.parseParameter(req, 'token')
|
|
38
|
+
|
|
39
|
+
const options = {
|
|
40
|
+
accountManager,
|
|
41
|
+
userStore,
|
|
42
|
+
token,
|
|
43
|
+
response: res
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return new DeleteAccountConfirmRequest(options)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Handles a Change Password GET request on behalf of a middleware handler.
|
|
51
|
+
*
|
|
52
|
+
* @param req {IncomingRequest}
|
|
53
|
+
* @param res {ServerResponse}
|
|
54
|
+
*
|
|
55
|
+
* @return {Promise}
|
|
56
|
+
*/
|
|
57
|
+
static async get (req, res) {
|
|
58
|
+
const request = DeleteAccountConfirmRequest.fromParams(req, res)
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await request.validateToken()
|
|
62
|
+
return request.renderForm()
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return request.error(error)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Handles a Change Password POST request on behalf of a middleware handler.
|
|
70
|
+
*
|
|
71
|
+
* @param req {IncomingRequest}
|
|
72
|
+
* @param res {ServerResponse}
|
|
73
|
+
*
|
|
74
|
+
* @return {Promise}
|
|
75
|
+
*/
|
|
76
|
+
static post (req, res) {
|
|
77
|
+
const request = DeleteAccountConfirmRequest.fromParams(req, res)
|
|
78
|
+
|
|
79
|
+
return DeleteAccountConfirmRequest.handlePost(request)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Performs the 'Change Password' operation, after the user submits the
|
|
84
|
+
* password change form. Validates the parameters (the one-time token,
|
|
85
|
+
* the new password), changes the password, and renders the success view.
|
|
86
|
+
*
|
|
87
|
+
* @param request {DeleteAccountConfirmRequest}
|
|
88
|
+
*
|
|
89
|
+
* @return {Promise}
|
|
90
|
+
*/
|
|
91
|
+
static async handlePost (request) {
|
|
92
|
+
try {
|
|
93
|
+
const tokenContents = await request.validateToken()
|
|
94
|
+
await request.deleteAccount(tokenContents)
|
|
95
|
+
return request.renderSuccess()
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return request.error(error)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Validates the one-time Password Reset token that was emailed to the user.
|
|
103
|
+
* If the token service has a valid token saved for the given key, it returns
|
|
104
|
+
* the token object value (which contains the user's WebID URI, etc).
|
|
105
|
+
* If no token is saved, returns `false`.
|
|
106
|
+
*
|
|
107
|
+
* @return {Promise<Object|false>}
|
|
108
|
+
*/
|
|
109
|
+
async validateToken () {
|
|
110
|
+
try {
|
|
111
|
+
if (!this.token) {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
const validToken = await this.accountManager.validateDeleteToken(this.token)
|
|
115
|
+
if (validToken) {
|
|
116
|
+
this.validToken = true
|
|
117
|
+
}
|
|
118
|
+
return validToken
|
|
119
|
+
} catch (error) {
|
|
120
|
+
this.token = null
|
|
121
|
+
throw error
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Removes the user's account and all their data from the store.
|
|
127
|
+
*
|
|
128
|
+
* @param tokenContents {Object}
|
|
129
|
+
*
|
|
130
|
+
* @return {Promise}
|
|
131
|
+
*/
|
|
132
|
+
async deleteAccount (tokenContents) {
|
|
133
|
+
const user = this.accountManager.userAccountFrom(tokenContents)
|
|
134
|
+
const accountDir = this.accountManager.accountDirFor(user.username)
|
|
135
|
+
|
|
136
|
+
debug('Preparing removal of account for user:', user)
|
|
137
|
+
|
|
138
|
+
await this.userStore.deleteUser(user)
|
|
139
|
+
await fs.remove(accountDir)
|
|
140
|
+
debug('Removed user' + user.username)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Renders the 'change password' form.
|
|
145
|
+
*
|
|
146
|
+
* @param [error] {Error} Optional error to display
|
|
147
|
+
*/
|
|
148
|
+
renderForm (error) {
|
|
149
|
+
const params = {
|
|
150
|
+
validToken: this.validToken,
|
|
151
|
+
token: this.token
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (error) {
|
|
155
|
+
params.error = error.message
|
|
156
|
+
this.response.status(error.statusCode)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.response.render('account/delete-confirm', params)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Displays the 'password has been changed' success view.
|
|
164
|
+
*/
|
|
165
|
+
renderSuccess () {
|
|
166
|
+
this.response.render('account/account-deleted')
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = DeleteAccountConfirmRequest
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const AuthRequest = require('./auth-request')
|
|
4
|
+
const debug = require('./../debug').accounts
|
|
5
|
+
|
|
6
|
+
// class DeleteAccountRequest {
|
|
7
|
+
class DeleteAccountRequest extends AuthRequest {
|
|
8
|
+
constructor (options) {
|
|
9
|
+
super(options)
|
|
10
|
+
|
|
11
|
+
this.username = options.username
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Calls the appropriate form to display to the user.
|
|
16
|
+
* Serves as an error handler for this request workflow.
|
|
17
|
+
*
|
|
18
|
+
* @param error {Error}
|
|
19
|
+
*/
|
|
20
|
+
error (error) {
|
|
21
|
+
error.statusCode = error.statusCode || 400
|
|
22
|
+
|
|
23
|
+
this.renderForm(error)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns a user account instance for the submitted username.
|
|
28
|
+
*
|
|
29
|
+
* @throws {Error} Rejects if user account does not exist for the username
|
|
30
|
+
*
|
|
31
|
+
* @returns {Promise<UserAccount>}
|
|
32
|
+
*/
|
|
33
|
+
async loadUser () {
|
|
34
|
+
const username = this.username
|
|
35
|
+
|
|
36
|
+
return this.accountManager.accountExists(username)
|
|
37
|
+
.then(exists => {
|
|
38
|
+
if (!exists) {
|
|
39
|
+
throw new Error('Account not found for that username')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const userData = { username }
|
|
43
|
+
|
|
44
|
+
return this.accountManager.userAccountFrom(userData)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Renders the Delete form
|
|
50
|
+
*/
|
|
51
|
+
renderForm (error) {
|
|
52
|
+
this.response.render('account/delete', {
|
|
53
|
+
error,
|
|
54
|
+
multiuser: this.accountManager.multiuser
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Displays the 'your reset link has been sent' success message view
|
|
60
|
+
*/
|
|
61
|
+
renderSuccess () {
|
|
62
|
+
this.response.render('account/delete-link-sent')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Loads the account recovery email for a given user and sends out a
|
|
67
|
+
* password request email.
|
|
68
|
+
*
|
|
69
|
+
* @param userAccount {UserAccount}
|
|
70
|
+
*
|
|
71
|
+
* @return {Promise}
|
|
72
|
+
*/
|
|
73
|
+
async sendDeleteLink (userAccount) {
|
|
74
|
+
const accountManager = this.accountManager
|
|
75
|
+
|
|
76
|
+
const recoveryEmail = await accountManager.loadAccountRecoveryEmail(userAccount)
|
|
77
|
+
userAccount.email = recoveryEmail
|
|
78
|
+
|
|
79
|
+
debug('Preparing delete account email to:', recoveryEmail)
|
|
80
|
+
|
|
81
|
+
return accountManager.sendDeleteAccountEmail(userAccount)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validates the request parameters, and throws an error if any
|
|
86
|
+
* validation fails.
|
|
87
|
+
*
|
|
88
|
+
* @throws {Error}
|
|
89
|
+
*/
|
|
90
|
+
validate () {
|
|
91
|
+
if (this.accountManager.multiuser && !this.username) {
|
|
92
|
+
throw new Error('Username required')
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static async post (req, res) {
|
|
97
|
+
const request = DeleteAccountRequest.fromParams(req, res)
|
|
98
|
+
|
|
99
|
+
debug(`User '${request.username}' requested to be sent a delete account email`)
|
|
100
|
+
|
|
101
|
+
return DeleteAccountRequest.handlePost(request)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Performs a 'send me a password reset email' request operation, after the
|
|
106
|
+
* user has entered an email into the reset form.
|
|
107
|
+
*
|
|
108
|
+
* @param request {DeleteAccountRequest}
|
|
109
|
+
*
|
|
110
|
+
* @return {Promise}
|
|
111
|
+
*/
|
|
112
|
+
static async handlePost (request) {
|
|
113
|
+
try {
|
|
114
|
+
request.validate()
|
|
115
|
+
const userAccount = await request.loadUser()
|
|
116
|
+
await request.sendDeleteLink(userAccount)
|
|
117
|
+
return request.renderSuccess()
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return request.error(error)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static get (req, res) {
|
|
124
|
+
const request = DeleteAccountRequest.fromParams(req, res)
|
|
125
|
+
|
|
126
|
+
request.renderForm()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static fromParams (req, res) {
|
|
130
|
+
const locals = req.app.locals
|
|
131
|
+
const accountManager = locals.accountManager
|
|
132
|
+
const username = this.parseParameter(req, 'username')
|
|
133
|
+
|
|
134
|
+
const options = {
|
|
135
|
+
accountManager,
|
|
136
|
+
response: res,
|
|
137
|
+
username
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new DeleteAccountRequest(options)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = DeleteAccountRequest
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* eslint-disable no-mixed-operators */
|
|
3
|
+
|
|
4
|
+
const debug = require('./../debug').authentication
|
|
5
|
+
|
|
6
|
+
const AuthRequest = require('./auth-request')
|
|
7
|
+
const { PasswordAuthenticator, TlsAuthenticator } = require('../models/authenticator')
|
|
8
|
+
|
|
9
|
+
const PASSWORD_AUTH = 'password'
|
|
10
|
+
const TLS_AUTH = 'tls'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Models a local Login request
|
|
14
|
+
*/
|
|
15
|
+
class LoginRequest extends AuthRequest {
|
|
16
|
+
/**
|
|
17
|
+
* @constructor
|
|
18
|
+
* @param options {Object}
|
|
19
|
+
*
|
|
20
|
+
* @param [options.response] {ServerResponse} middleware `res` object
|
|
21
|
+
* @param [options.session] {Session} req.session
|
|
22
|
+
* @param [options.userStore] {UserStore}
|
|
23
|
+
* @param [options.accountManager] {AccountManager}
|
|
24
|
+
* @param [options.returnToUrl] {string}
|
|
25
|
+
* @param [options.authQueryParams] {Object} Key/value hashmap of parsed query
|
|
26
|
+
* parameters that will be passed through to the /authorize endpoint.
|
|
27
|
+
* @param [options.authenticator] {Authenticator} Auth strategy by which to
|
|
28
|
+
* log in
|
|
29
|
+
*/
|
|
30
|
+
constructor (options) {
|
|
31
|
+
super(options)
|
|
32
|
+
|
|
33
|
+
this.authenticator = options.authenticator
|
|
34
|
+
this.authMethod = options.authMethod
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Factory method, returns an initialized instance of LoginRequest
|
|
39
|
+
* from an incoming http request.
|
|
40
|
+
*
|
|
41
|
+
* @param req {IncomingRequest}
|
|
42
|
+
* @param res {ServerResponse}
|
|
43
|
+
* @param authMethod {string}
|
|
44
|
+
*
|
|
45
|
+
* @return {LoginRequest}
|
|
46
|
+
*/
|
|
47
|
+
static fromParams (req, res, authMethod) {
|
|
48
|
+
const options = AuthRequest.requestOptions(req, res)
|
|
49
|
+
options.authMethod = authMethod
|
|
50
|
+
|
|
51
|
+
switch (authMethod) {
|
|
52
|
+
case PASSWORD_AUTH:
|
|
53
|
+
options.authenticator = PasswordAuthenticator.fromParams(req, options)
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
case TLS_AUTH:
|
|
57
|
+
options.authenticator = TlsAuthenticator.fromParams(req, options)
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
default:
|
|
61
|
+
options.authenticator = null
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return new LoginRequest(options)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Handles a Login GET request on behalf of a middleware handler, displays
|
|
70
|
+
* the Login page.
|
|
71
|
+
* Usage:
|
|
72
|
+
*
|
|
73
|
+
* ```
|
|
74
|
+
* app.get('/login', LoginRequest.get)
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @param req {IncomingRequest}
|
|
78
|
+
* @param res {ServerResponse}
|
|
79
|
+
*/
|
|
80
|
+
static get (req, res) {
|
|
81
|
+
const request = LoginRequest.fromParams(req, res)
|
|
82
|
+
|
|
83
|
+
request.renderForm(null, req)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handles a Login via Username+Password.
|
|
88
|
+
* Errors encountered are displayed on the Login form.
|
|
89
|
+
* Usage:
|
|
90
|
+
*
|
|
91
|
+
* ```
|
|
92
|
+
* app.post('/login/password', LoginRequest.loginPassword)
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @param req
|
|
96
|
+
* @param res
|
|
97
|
+
*
|
|
98
|
+
* @return {Promise}
|
|
99
|
+
*/
|
|
100
|
+
static loginPassword (req, res) {
|
|
101
|
+
debug('Logging in via username + password')
|
|
102
|
+
|
|
103
|
+
const request = LoginRequest.fromParams(req, res, PASSWORD_AUTH)
|
|
104
|
+
|
|
105
|
+
return LoginRequest.login(request)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Handles a Login via WebID-TLS.
|
|
110
|
+
* Errors encountered are displayed on the Login form.
|
|
111
|
+
* Usage:
|
|
112
|
+
*
|
|
113
|
+
* ```
|
|
114
|
+
* app.post('/login/tls', LoginRequest.loginTls)
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @param req
|
|
118
|
+
* @param res
|
|
119
|
+
*
|
|
120
|
+
* @return {Promise}
|
|
121
|
+
*/
|
|
122
|
+
static loginTls (req, res) {
|
|
123
|
+
debug('Logging in via WebID-TLS certificate')
|
|
124
|
+
|
|
125
|
+
const request = LoginRequest.fromParams(req, res, TLS_AUTH)
|
|
126
|
+
|
|
127
|
+
return LoginRequest.login(request)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Performs the login operation -- loads and validates the
|
|
132
|
+
* appropriate user, inits the session with credentials, and redirects the
|
|
133
|
+
* user to continue their auth flow.
|
|
134
|
+
*
|
|
135
|
+
* @param request {LoginRequest}
|
|
136
|
+
*
|
|
137
|
+
* @return {Promise}
|
|
138
|
+
*/
|
|
139
|
+
static login (request) {
|
|
140
|
+
return request.authenticator.findValidUser()
|
|
141
|
+
|
|
142
|
+
.then(validUser => {
|
|
143
|
+
request.initUserSession(validUser)
|
|
144
|
+
|
|
145
|
+
request.redirectPostLogin(validUser)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
.catch(error => request.error(error))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns a URL to redirect the user to after login.
|
|
153
|
+
* Either uses the provided `redirect_uri` auth query param, or simply
|
|
154
|
+
* returns the user profile URI if none was provided.
|
|
155
|
+
*
|
|
156
|
+
* @param validUser {UserAccount}
|
|
157
|
+
*
|
|
158
|
+
* @return {string}
|
|
159
|
+
*/
|
|
160
|
+
postLoginUrl (validUser) {
|
|
161
|
+
// Login request is part of an app's auth flow
|
|
162
|
+
if (/token|code/.test(this.authQueryParams.response_type)) {
|
|
163
|
+
return this.sharingUrl()
|
|
164
|
+
// Login request is a user going to /login in browser
|
|
165
|
+
} else if (validUser) {
|
|
166
|
+
return this.authQueryParams.redirect_uri || validUser.accountUri
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Redirects the Login request to continue on the OIDC auth workflow.
|
|
172
|
+
*/
|
|
173
|
+
redirectPostLogin (validUser) {
|
|
174
|
+
const uri = this.postLoginUrl(validUser)
|
|
175
|
+
debug('Login successful, redirecting to ', uri)
|
|
176
|
+
this.response.redirect(uri)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Renders the login form
|
|
181
|
+
*/
|
|
182
|
+
renderForm (error, req) {
|
|
183
|
+
const queryString = req && req.url && req.url.replace(/[^?]+\?/, '') || ''
|
|
184
|
+
const params = Object.assign({}, this.authQueryParams,
|
|
185
|
+
{
|
|
186
|
+
registerUrl: this.registerUrl(),
|
|
187
|
+
returnToUrl: this.returnToUrl,
|
|
188
|
+
enablePassword: this.localAuth.password,
|
|
189
|
+
enableTls: this.localAuth.tls,
|
|
190
|
+
tlsUrl: `/login/tls?${encodeURIComponent(queryString)}`
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
if (error) {
|
|
194
|
+
params.error = error.message
|
|
195
|
+
this.response.status(error.statusCode)
|
|
196
|
+
}
|
|
197
|
+
this.response.render('auth/login', params)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
LoginRequest,
|
|
203
|
+
PASSWORD_AUTH,
|
|
204
|
+
TLS_AUTH
|
|
205
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const AuthRequest = require('./auth-request')
|
|
4
|
+
const debug = require('./../debug').accounts
|
|
5
|
+
|
|
6
|
+
class PasswordChangeRequest extends AuthRequest {
|
|
7
|
+
/**
|
|
8
|
+
* @constructor
|
|
9
|
+
* @param options {Object}
|
|
10
|
+
* @param options.accountManager {AccountManager}
|
|
11
|
+
* @param options.userStore {UserStore}
|
|
12
|
+
* @param options.response {ServerResponse} express response object
|
|
13
|
+
* @param [options.token] {string} One-time reset password token (from email)
|
|
14
|
+
* @param [options.returnToUrl] {string}
|
|
15
|
+
* @param [options.newPassword] {string} New password to save
|
|
16
|
+
*/
|
|
17
|
+
constructor (options) {
|
|
18
|
+
super(options)
|
|
19
|
+
|
|
20
|
+
this.token = options.token
|
|
21
|
+
this.returnToUrl = options.returnToUrl
|
|
22
|
+
|
|
23
|
+
this.validToken = false
|
|
24
|
+
|
|
25
|
+
this.newPassword = options.newPassword
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Factory method, returns an initialized instance of PasswordChangeRequest
|
|
30
|
+
* from an incoming http request.
|
|
31
|
+
*
|
|
32
|
+
* @param req {IncomingRequest}
|
|
33
|
+
* @param res {ServerResponse}
|
|
34
|
+
*
|
|
35
|
+
* @return {PasswordChangeRequest}
|
|
36
|
+
*/
|
|
37
|
+
static fromParams (req, res) {
|
|
38
|
+
const locals = req.app.locals
|
|
39
|
+
const accountManager = locals.accountManager
|
|
40
|
+
const userStore = locals.oidc.users
|
|
41
|
+
|
|
42
|
+
const returnToUrl = this.parseParameter(req, 'returnToUrl')
|
|
43
|
+
const token = this.parseParameter(req, 'token')
|
|
44
|
+
const oldPassword = this.parseParameter(req, 'password')
|
|
45
|
+
const newPassword = this.parseParameter(req, 'newPassword')
|
|
46
|
+
|
|
47
|
+
const options = {
|
|
48
|
+
accountManager,
|
|
49
|
+
userStore,
|
|
50
|
+
returnToUrl,
|
|
51
|
+
token,
|
|
52
|
+
oldPassword,
|
|
53
|
+
newPassword,
|
|
54
|
+
response: res
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return new PasswordChangeRequest(options)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Handles a Change Password GET request on behalf of a middleware handler.
|
|
62
|
+
*
|
|
63
|
+
* @param req {IncomingRequest}
|
|
64
|
+
* @param res {ServerResponse}
|
|
65
|
+
*
|
|
66
|
+
* @return {Promise}
|
|
67
|
+
*/
|
|
68
|
+
static get (req, res) {
|
|
69
|
+
const request = PasswordChangeRequest.fromParams(req, res)
|
|
70
|
+
|
|
71
|
+
return Promise.resolve()
|
|
72
|
+
.then(() => request.validateToken())
|
|
73
|
+
.then(() => request.renderForm())
|
|
74
|
+
.catch(error => request.error(error))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Handles a Change Password POST request on behalf of a middleware handler.
|
|
79
|
+
*
|
|
80
|
+
* @param req {IncomingRequest}
|
|
81
|
+
* @param res {ServerResponse}
|
|
82
|
+
*
|
|
83
|
+
* @return {Promise}
|
|
84
|
+
*/
|
|
85
|
+
static post (req, res) {
|
|
86
|
+
const request = PasswordChangeRequest.fromParams(req, res)
|
|
87
|
+
|
|
88
|
+
return PasswordChangeRequest.handlePost(request)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Performs the 'Change Password' operation, after the user submits the
|
|
93
|
+
* password change form. Validates the parameters (the one-time token,
|
|
94
|
+
* the new password), changes the password, and renders the success view.
|
|
95
|
+
*
|
|
96
|
+
* @param request {PasswordChangeRequest}
|
|
97
|
+
*
|
|
98
|
+
* @return {Promise}
|
|
99
|
+
*/
|
|
100
|
+
static handlePost (request) {
|
|
101
|
+
return Promise.resolve()
|
|
102
|
+
.then(() => request.validatePost())
|
|
103
|
+
.then(() => request.validateToken())
|
|
104
|
+
.then(tokenContents => request.changePassword(tokenContents))
|
|
105
|
+
.then(() => request.renderSuccess())
|
|
106
|
+
.catch(error => request.error(error))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validates the 'Change Password' parameters, and throws an error if any
|
|
111
|
+
* validation fails.
|
|
112
|
+
*
|
|
113
|
+
* @throws {Error}
|
|
114
|
+
*/
|
|
115
|
+
validatePost () {
|
|
116
|
+
if (!this.newPassword) {
|
|
117
|
+
throw new Error('Please enter a new password')
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validates the one-time Password Reset token that was emailed to the user.
|
|
123
|
+
* If the token service has a valid token saved for the given key, it returns
|
|
124
|
+
* the token object value (which contains the user's WebID URI, etc).
|
|
125
|
+
* If no token is saved, returns `false`.
|
|
126
|
+
*
|
|
127
|
+
* @return {Promise<Object|false>}
|
|
128
|
+
*/
|
|
129
|
+
validateToken () {
|
|
130
|
+
return Promise.resolve()
|
|
131
|
+
.then(() => {
|
|
132
|
+
if (!this.token) { return false }
|
|
133
|
+
|
|
134
|
+
return this.accountManager.validateResetToken(this.token)
|
|
135
|
+
})
|
|
136
|
+
.then(validToken => {
|
|
137
|
+
if (validToken) {
|
|
138
|
+
this.validToken = true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return validToken
|
|
142
|
+
})
|
|
143
|
+
.catch(error => {
|
|
144
|
+
this.token = null
|
|
145
|
+
throw error
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Changes the password that's saved in the user store.
|
|
151
|
+
* If the user has no user store entry, it creates one.
|
|
152
|
+
*
|
|
153
|
+
* @param tokenContents {Object}
|
|
154
|
+
* @param tokenContents.webId {string}
|
|
155
|
+
*
|
|
156
|
+
* @return {Promise}
|
|
157
|
+
*/
|
|
158
|
+
changePassword (tokenContents) {
|
|
159
|
+
const user = this.accountManager.userAccountFrom(tokenContents)
|
|
160
|
+
|
|
161
|
+
debug('Changing password for user:', user.webId)
|
|
162
|
+
|
|
163
|
+
return this.userStore.findUser(user.id)
|
|
164
|
+
.then(userStoreEntry => {
|
|
165
|
+
if (userStoreEntry) {
|
|
166
|
+
return this.userStore.updatePassword(user, this.newPassword)
|
|
167
|
+
} else {
|
|
168
|
+
return this.userStore.createUser(user, this.newPassword)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Renders the 'change password' form.
|
|
175
|
+
*
|
|
176
|
+
* @param [error] {Error} Optional error to display
|
|
177
|
+
*/
|
|
178
|
+
renderForm (error) {
|
|
179
|
+
const params = {
|
|
180
|
+
validToken: this.validToken,
|
|
181
|
+
returnToUrl: this.returnToUrl,
|
|
182
|
+
token: this.token
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (error) {
|
|
186
|
+
params.error = error.message
|
|
187
|
+
this.response.status(error.statusCode)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.response.render('auth/change-password', params)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Displays the 'password has been changed' success view.
|
|
195
|
+
*/
|
|
196
|
+
renderSuccess () {
|
|
197
|
+
this.response.render('auth/password-changed', { returnToUrl: this.returnToUrl })
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = PasswordChangeRequest
|