solid-server 5.6.9-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/.acl +10 -0
  2. package/.github/workflows/ci.yml +47 -0
  3. package/.nvmrc +1 -0
  4. package/.snyk +35 -0
  5. package/.well-known/.acl +15 -0
  6. package/CHANGELOG.md +198 -0
  7. package/CONTRIBUTING.md +139 -0
  8. package/CONTRIBUTORS.md +36 -0
  9. package/Dockerfile +22 -0
  10. package/LICENSE.md +23 -0
  11. package/README.md +453 -0
  12. package/bin/lib/cli-utils.js +85 -0
  13. package/bin/lib/cli.js +39 -0
  14. package/bin/lib/init.js +94 -0
  15. package/bin/lib/invalidUsernames.js +148 -0
  16. package/bin/lib/migrateLegacyResources.js +69 -0
  17. package/bin/lib/options.js +399 -0
  18. package/bin/lib/start.js +148 -0
  19. package/bin/lib/updateIndex.js +56 -0
  20. package/bin/solid +3 -0
  21. package/bin/solid-test +12 -0
  22. package/bin/solid.js +3 -0
  23. package/common/css/solid.css +58 -0
  24. package/common/fonts/glyphicons-halflings-regular.eot +0 -0
  25. package/common/fonts/glyphicons-halflings-regular.svg +288 -0
  26. package/common/fonts/glyphicons-halflings-regular.ttf +0 -0
  27. package/common/fonts/glyphicons-halflings-regular.woff +0 -0
  28. package/common/fonts/glyphicons-halflings-regular.woff2 +0 -0
  29. package/common/img/.gitkeep +0 -0
  30. package/common/js/auth-buttons.js +65 -0
  31. package/common/js/solid.js +454 -0
  32. package/common/well-known/security.txt +2 -0
  33. package/config/defaults.js +25 -0
  34. package/config/usernames-blacklist.json +4 -0
  35. package/config.json-default +22 -0
  36. package/default-templates/emails/delete-account.js +49 -0
  37. package/default-templates/emails/invalid-username.js +30 -0
  38. package/default-templates/emails/reset-password.js +49 -0
  39. package/default-templates/emails/welcome.js +39 -0
  40. package/default-templates/new-account/.acl +26 -0
  41. package/default-templates/new-account/.meta +5 -0
  42. package/default-templates/new-account/.meta.acl +25 -0
  43. package/default-templates/new-account/.well-known/.acl +19 -0
  44. package/default-templates/new-account/favicon.ico +0 -0
  45. package/default-templates/new-account/favicon.ico.acl +26 -0
  46. package/default-templates/new-account/inbox/.acl +26 -0
  47. package/default-templates/new-account/private/.acl +10 -0
  48. package/default-templates/new-account/profile/.acl +19 -0
  49. package/default-templates/new-account/profile/card$.ttl +25 -0
  50. package/default-templates/new-account/public/.acl +19 -0
  51. package/default-templates/new-account/robots.txt +3 -0
  52. package/default-templates/new-account/robots.txt.acl +26 -0
  53. package/default-templates/new-account/settings/.acl +20 -0
  54. package/default-templates/new-account/settings/prefs.ttl +15 -0
  55. package/default-templates/new-account/settings/privateTypeIndex.ttl +4 -0
  56. package/default-templates/new-account/settings/publicTypeIndex.ttl +4 -0
  57. package/default-templates/new-account/settings/publicTypeIndex.ttl.acl +25 -0
  58. package/default-templates/new-account/settings/serverSide.ttl.acl +13 -0
  59. package/default-templates/new-account/settings/serverSide.ttl.inactive +12 -0
  60. package/default-templates/server/.acl +10 -0
  61. package/default-templates/server/.well-known/.acl +15 -0
  62. package/default-templates/server/favicon.ico +0 -0
  63. package/default-templates/server/favicon.ico.acl +15 -0
  64. package/default-templates/server/index.html +55 -0
  65. package/default-templates/server/robots.txt +3 -0
  66. package/default-templates/server/robots.txt.acl +15 -0
  67. package/default-views/account/account-deleted.hbs +17 -0
  68. package/default-views/account/delete-confirm.hbs +51 -0
  69. package/default-views/account/delete-link-sent.hbs +17 -0
  70. package/default-views/account/delete.hbs +51 -0
  71. package/default-views/account/invalid-username.hbs +22 -0
  72. package/default-views/account/register-disabled.hbs +6 -0
  73. package/default-views/account/register-form.hbs +132 -0
  74. package/default-views/account/register.hbs +24 -0
  75. package/default-views/auth/auth-hidden-fields.hbs +8 -0
  76. package/default-views/auth/change-password.hbs +58 -0
  77. package/default-views/auth/goodbye.hbs +23 -0
  78. package/default-views/auth/login-required.hbs +34 -0
  79. package/default-views/auth/login-tls.hbs +11 -0
  80. package/default-views/auth/login-username-password.hbs +28 -0
  81. package/default-views/auth/login.hbs +55 -0
  82. package/default-views/auth/no-permission.hbs +29 -0
  83. package/default-views/auth/password-changed.hbs +27 -0
  84. package/default-views/auth/reset-link-sent.hbs +21 -0
  85. package/default-views/auth/reset-password.hbs +52 -0
  86. package/default-views/auth/sharing.hbs +49 -0
  87. package/default-views/shared/create-account.hbs +8 -0
  88. package/default-views/shared/error.hbs +5 -0
  89. package/docs/how-to-delete-your-account.md +56 -0
  90. package/docs/login-and-grant-access-to-application.md +32 -0
  91. package/examples/custom-error-handling.js +31 -0
  92. package/examples/ldp-with-webid.js +12 -0
  93. package/examples/simple-express-app.js +20 -0
  94. package/examples/simple-ldp-server.js +8 -0
  95. package/favicon.ico +0 -0
  96. package/favicon.ico.acl +15 -0
  97. package/index.html +48 -0
  98. package/index.js +3 -0
  99. package/lib/acl-checker.js +274 -0
  100. package/lib/api/accounts/user-accounts.js +88 -0
  101. package/lib/api/authn/force-user.js +21 -0
  102. package/lib/api/authn/index.js +5 -0
  103. package/lib/api/authn/webid-oidc.js +202 -0
  104. package/lib/api/authn/webid-tls.js +69 -0
  105. package/lib/api/index.js +6 -0
  106. package/lib/capability-discovery.js +54 -0
  107. package/lib/common/fs-utils.js +43 -0
  108. package/lib/common/template-utils.js +50 -0
  109. package/lib/common/user-utils.js +28 -0
  110. package/lib/create-app.js +322 -0
  111. package/lib/create-server.js +107 -0
  112. package/lib/debug.js +17 -0
  113. package/lib/handlers/allow.js +82 -0
  114. package/lib/handlers/auth-proxy.js +63 -0
  115. package/lib/handlers/copy.js +39 -0
  116. package/lib/handlers/cors-proxy.js +95 -0
  117. package/lib/handlers/delete.js +23 -0
  118. package/lib/handlers/error-pages.js +212 -0
  119. package/lib/handlers/get.js +219 -0
  120. package/lib/handlers/index.js +42 -0
  121. package/lib/handlers/options.js +33 -0
  122. package/lib/handlers/patch/n3-patch-parser.js +49 -0
  123. package/lib/handlers/patch/sparql-update-parser.js +16 -0
  124. package/lib/handlers/patch.js +203 -0
  125. package/lib/handlers/post.js +99 -0
  126. package/lib/handlers/put.js +56 -0
  127. package/lib/handlers/restrict-to-top-domain.js +13 -0
  128. package/lib/header.js +136 -0
  129. package/lib/http-error.js +34 -0
  130. package/lib/ldp-container.js +161 -0
  131. package/lib/ldp-copy.js +73 -0
  132. package/lib/ldp-middleware.js +32 -0
  133. package/lib/ldp.js +620 -0
  134. package/lib/lock.js +10 -0
  135. package/lib/metadata.js +10 -0
  136. package/lib/models/account-manager.js +603 -0
  137. package/lib/models/account-template.js +152 -0
  138. package/lib/models/authenticator.js +333 -0
  139. package/lib/models/oidc-manager.js +53 -0
  140. package/lib/models/solid-host.js +131 -0
  141. package/lib/models/user-account.js +112 -0
  142. package/lib/models/webid-tls-certificate.js +184 -0
  143. package/lib/payment-pointer-discovery.js +83 -0
  144. package/lib/requests/add-cert-request.js +138 -0
  145. package/lib/requests/auth-request.js +234 -0
  146. package/lib/requests/create-account-request.js +468 -0
  147. package/lib/requests/delete-account-confirm-request.js +170 -0
  148. package/lib/requests/delete-account-request.js +144 -0
  149. package/lib/requests/login-request.js +205 -0
  150. package/lib/requests/password-change-request.js +201 -0
  151. package/lib/requests/password-reset-email-request.js +199 -0
  152. package/lib/requests/sharing-request.js +259 -0
  153. package/lib/resource-mapper.js +198 -0
  154. package/lib/server-config.js +167 -0
  155. package/lib/services/blacklist-service.js +33 -0
  156. package/lib/services/email-service.js +162 -0
  157. package/lib/services/token-service.js +47 -0
  158. package/lib/utils.js +254 -0
  159. package/lib/webid/index.js +13 -0
  160. package/lib/webid/lib/get.js +27 -0
  161. package/lib/webid/lib/parse.js +12 -0
  162. package/lib/webid/tls/index.js +185 -0
  163. package/package.json +172 -0
  164. package/renovate.json +5 -0
  165. package/robots.txt +3 -0
  166. package/robots.txt.acl +15 -0
  167. package/static/account-recovery.html +78 -0
  168. package/static/popup-redirect.html +1 -0
  169. package/static/signup.html +108 -0
  170. package/static/signup.html.acl +14 -0
@@ -0,0 +1,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