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,322 @@
|
|
|
1
|
+
module.exports = createApp
|
|
2
|
+
|
|
3
|
+
const express = require('express')
|
|
4
|
+
const session = require('express-session')
|
|
5
|
+
const handlebars = require('express-handlebars')
|
|
6
|
+
const uuid = require('uuid')
|
|
7
|
+
const cors = require('cors')
|
|
8
|
+
const LDP = require('./ldp')
|
|
9
|
+
const LdpMiddleware = require('./ldp-middleware')
|
|
10
|
+
const corsProxy = require('./handlers/cors-proxy')
|
|
11
|
+
const authProxy = require('./handlers/auth-proxy')
|
|
12
|
+
const SolidHost = require('./models/solid-host')
|
|
13
|
+
const AccountManager = require('./models/account-manager')
|
|
14
|
+
const vhost = require('vhost')
|
|
15
|
+
const EmailService = require('./services/email-service')
|
|
16
|
+
const TokenService = require('./services/token-service')
|
|
17
|
+
const capabilityDiscovery = require('./capability-discovery')
|
|
18
|
+
const paymentPointerDiscovery = require('./payment-pointer-discovery')
|
|
19
|
+
const API = require('./api')
|
|
20
|
+
const errorPages = require('./handlers/error-pages')
|
|
21
|
+
const config = require('./server-config')
|
|
22
|
+
const defaults = require('../config/defaults')
|
|
23
|
+
const options = require('./handlers/options')
|
|
24
|
+
const debug = require('./debug')
|
|
25
|
+
const path = require('path')
|
|
26
|
+
const { routeResolvedFile } = require('./utils')
|
|
27
|
+
const ResourceMapper = require('./resource-mapper')
|
|
28
|
+
const aclCheck = require('@solid/acl-check')
|
|
29
|
+
const { version } = require('../package.json')
|
|
30
|
+
|
|
31
|
+
const corsSettings = cors({
|
|
32
|
+
methods: [
|
|
33
|
+
'OPTIONS', 'HEAD', 'GET', 'PATCH', 'POST', 'PUT', 'DELETE'
|
|
34
|
+
],
|
|
35
|
+
exposedHeaders: 'Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Accept-Post, Updates-Via, Allow, WAC-Allow, Content-Length, WWW-Authenticate, MS-Author-Via, X-Powered-By',
|
|
36
|
+
credentials: true,
|
|
37
|
+
maxAge: 1728000,
|
|
38
|
+
origin: true,
|
|
39
|
+
preflightContinue: true
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
function createApp (argv = {}) {
|
|
43
|
+
// Override default configs (defaults) with passed-in params (argv)
|
|
44
|
+
argv = Object.assign({}, defaults, argv)
|
|
45
|
+
|
|
46
|
+
argv.host = SolidHost.from(argv)
|
|
47
|
+
|
|
48
|
+
argv.resourceMapper = new ResourceMapper({
|
|
49
|
+
rootUrl: argv.serverUri,
|
|
50
|
+
rootPath: path.resolve(argv.root || process.cwd()),
|
|
51
|
+
includeHost: argv.multiuser,
|
|
52
|
+
defaultContentType: argv.defaultContentType
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const configPath = config.initConfigDir(argv)
|
|
56
|
+
argv.templates = config.initTemplateDirs(configPath)
|
|
57
|
+
|
|
58
|
+
config.printDebugInfo(argv)
|
|
59
|
+
|
|
60
|
+
const ldp = new LDP(argv)
|
|
61
|
+
|
|
62
|
+
const app = express()
|
|
63
|
+
|
|
64
|
+
initAppLocals(app, argv, ldp)
|
|
65
|
+
initHeaders(app)
|
|
66
|
+
initViews(app, configPath)
|
|
67
|
+
initLoggers()
|
|
68
|
+
|
|
69
|
+
// Serve the public 'common' directory (for shared CSS files, etc)
|
|
70
|
+
app.use('/common', express.static(path.join(__dirname, '../common')))
|
|
71
|
+
app.use('/', express.static(path.dirname(require.resolve('mashlib/dist/databrowser.html')), { index: false }))
|
|
72
|
+
routeResolvedFile(app, '/common/js/', 'solid-auth-client/dist-lib/solid-auth-client.bundle.js')
|
|
73
|
+
routeResolvedFile(app, '/common/js/', 'solid-auth-client/dist-lib/solid-auth-client.bundle.js.map')
|
|
74
|
+
app.use('/.well-known', express.static(path.join(__dirname, '../common/well-known')))
|
|
75
|
+
|
|
76
|
+
// Serve bootstrap from it's node_module directory
|
|
77
|
+
routeResolvedFile(app, '/common/css/', 'bootstrap/dist/css/bootstrap.min.css')
|
|
78
|
+
routeResolvedFile(app, '/common/css/', 'bootstrap/dist/css/bootstrap.min.css.map')
|
|
79
|
+
routeResolvedFile(app, '/common/fonts/', 'bootstrap/dist/fonts/glyphicons-halflings-regular.eot')
|
|
80
|
+
routeResolvedFile(app, '/common/fonts/', 'bootstrap/dist/fonts/glyphicons-halflings-regular.svg')
|
|
81
|
+
routeResolvedFile(app, '/common/fonts/', 'bootstrap/dist/fonts/glyphicons-halflings-regular.ttf')
|
|
82
|
+
routeResolvedFile(app, '/common/fonts/', 'bootstrap/dist/fonts/glyphicons-halflings-regular.woff')
|
|
83
|
+
routeResolvedFile(app, '/common/fonts/', 'bootstrap/dist/fonts/glyphicons-halflings-regular.woff2')
|
|
84
|
+
|
|
85
|
+
// Serve OWASP password checker from it's node_module directory
|
|
86
|
+
routeResolvedFile(app, '/common/js/', 'owasp-password-strength-test/owasp-password-strength-test.js')
|
|
87
|
+
// Serve the TextEncoder polyfill
|
|
88
|
+
routeResolvedFile(app, '/common/js/', 'text-encoder-lite/text-encoder-lite.min.js')
|
|
89
|
+
|
|
90
|
+
// Add CORS proxy
|
|
91
|
+
if (argv.proxy) {
|
|
92
|
+
console.warn('The proxy configuration option has been renamed to corsProxy.')
|
|
93
|
+
argv.corsProxy = argv.corsProxy || argv.proxy
|
|
94
|
+
delete argv.proxy
|
|
95
|
+
}
|
|
96
|
+
if (argv.corsProxy) {
|
|
97
|
+
corsProxy(app, argv.corsProxy)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Options handler
|
|
101
|
+
app.options('/*', options)
|
|
102
|
+
|
|
103
|
+
// Set up API
|
|
104
|
+
if (argv.apiApps) {
|
|
105
|
+
app.use('/api/apps', express.static(argv.apiApps))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Authenticate the user
|
|
109
|
+
if (argv.webid) {
|
|
110
|
+
initWebId(argv, app, ldp)
|
|
111
|
+
}
|
|
112
|
+
// Add Auth proxy (requires authentication)
|
|
113
|
+
if (argv.authProxy) {
|
|
114
|
+
authProxy(app, argv.authProxy)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Attach the LDP middleware
|
|
118
|
+
app.use('/', LdpMiddleware(corsSettings))
|
|
119
|
+
|
|
120
|
+
// Errors
|
|
121
|
+
app.use(errorPages.handler)
|
|
122
|
+
|
|
123
|
+
return app
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Initializes `app.locals` parameters for downstream use (typically by route
|
|
128
|
+
* handlers).
|
|
129
|
+
*
|
|
130
|
+
* @param app {Function} Express.js app instance
|
|
131
|
+
* @param argv {Object} Config options hashmap
|
|
132
|
+
* @param ldp {LDP}
|
|
133
|
+
*/
|
|
134
|
+
function initAppLocals (app, argv, ldp) {
|
|
135
|
+
app.locals.ldp = ldp
|
|
136
|
+
app.locals.appUrls = argv.apps // used for service capability discovery
|
|
137
|
+
app.locals.host = argv.host
|
|
138
|
+
app.locals.authMethod = argv.auth
|
|
139
|
+
app.locals.localAuth = argv.localAuth
|
|
140
|
+
app.locals.tokenService = new TokenService()
|
|
141
|
+
app.locals.enforceToc = argv.enforceToc
|
|
142
|
+
app.locals.tocUri = argv.tocUri
|
|
143
|
+
app.locals.disablePasswordChecks = argv.disablePasswordChecks
|
|
144
|
+
|
|
145
|
+
if (argv.email && argv.email.host) {
|
|
146
|
+
app.locals.emailService = new EmailService(argv.templates.email, argv.email)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sets up headers common to all Solid requests (CORS-related, Allow, etc).
|
|
152
|
+
*
|
|
153
|
+
* @param app {Function} Express.js app instance
|
|
154
|
+
*/
|
|
155
|
+
function initHeaders (app) {
|
|
156
|
+
app.use(corsSettings)
|
|
157
|
+
|
|
158
|
+
app.use((req, res, next) => {
|
|
159
|
+
res.set('X-Powered-By', 'solid-server/' + version)
|
|
160
|
+
|
|
161
|
+
// Cors lib adds Vary: Origin automatically, but inreliably
|
|
162
|
+
res.set('Vary', 'Accept, Authorization, Origin')
|
|
163
|
+
|
|
164
|
+
// Set default Allow methods
|
|
165
|
+
res.set('Allow', 'OPTIONS, HEAD, GET, PATCH, POST, PUT, DELETE')
|
|
166
|
+
next()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
app.use('/', capabilityDiscovery())
|
|
170
|
+
app.use('/', paymentPointerDiscovery())
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Sets up the express rendering engine and views directory.
|
|
175
|
+
*
|
|
176
|
+
* @param app {Function} Express.js app
|
|
177
|
+
* @param configPath {string}
|
|
178
|
+
*/
|
|
179
|
+
function initViews (app, configPath) {
|
|
180
|
+
const viewsPath = config.initDefaultViews(configPath)
|
|
181
|
+
|
|
182
|
+
app.set('views', viewsPath)
|
|
183
|
+
app.engine('.hbs', handlebars({
|
|
184
|
+
extname: '.hbs',
|
|
185
|
+
partialsDir: viewsPath,
|
|
186
|
+
defaultLayout: null
|
|
187
|
+
}))
|
|
188
|
+
app.set('view engine', '.hbs')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Sets up WebID-related functionality (account creation and authentication)
|
|
193
|
+
*
|
|
194
|
+
* @param argv {Object}
|
|
195
|
+
* @param app {Function}
|
|
196
|
+
* @param ldp {LDP}
|
|
197
|
+
*/
|
|
198
|
+
function initWebId (argv, app, ldp) {
|
|
199
|
+
config.ensureWelcomePage(argv)
|
|
200
|
+
|
|
201
|
+
// Store the user's session key in a cookie
|
|
202
|
+
// (for same-domain browsing by people only)
|
|
203
|
+
const useSecureCookies = !!argv.sslKey // use secure cookies when over HTTPS
|
|
204
|
+
const sessionHandler = session(sessionSettings(useSecureCookies, argv.host))
|
|
205
|
+
app.use(sessionHandler)
|
|
206
|
+
// Reject cookies from third-party applications.
|
|
207
|
+
// Otherwise, when a user is logged in to their Solid server,
|
|
208
|
+
// any third-party application could perform authenticated requests
|
|
209
|
+
// without permission by including the credentials set by the Solid server.
|
|
210
|
+
app.use((req, res, next) => {
|
|
211
|
+
const origin = req.get('origin')
|
|
212
|
+
const trustedOrigins = ldp.getTrustedOrigins(req)
|
|
213
|
+
const userId = req.session.userId
|
|
214
|
+
// Exception: allow logout requests from all third-party apps
|
|
215
|
+
// such that OIDC client can log out via cookie auth
|
|
216
|
+
// TODO: remove this exception when OIDC clients
|
|
217
|
+
// use Bearer token to authenticate instead of cookie
|
|
218
|
+
// (https://github.com/solid/node-solid-server/pull/835#issuecomment-426429003)
|
|
219
|
+
//
|
|
220
|
+
// Authentication cookies are an optimization:
|
|
221
|
+
// instead of going through the process of
|
|
222
|
+
// fully validating authentication on every request,
|
|
223
|
+
// we go through this process once,
|
|
224
|
+
// and store its successful result in a cookie
|
|
225
|
+
// that will be reused upon the next request.
|
|
226
|
+
// However, that cookie can then be sent by any server,
|
|
227
|
+
// even servers that have not gone through the proper authentication mechanism.
|
|
228
|
+
// However, if trusted origins are enabled,
|
|
229
|
+
// then any origin is allowed to take the shortcut route,
|
|
230
|
+
// since malicious origins will be banned at the ACL checking phase.
|
|
231
|
+
// https://github.com/solid/node-solid-server/issues/1117
|
|
232
|
+
if (!argv.strictOrigin && !argv.host.allowsSessionFor(userId, origin, trustedOrigins) && !isLogoutRequest(req)) {
|
|
233
|
+
debug.authentication(`Rejecting session for ${userId} from ${origin}`)
|
|
234
|
+
// Destroy session data
|
|
235
|
+
delete req.session.userId
|
|
236
|
+
// Ensure this modified session is not saved
|
|
237
|
+
req.session.save = (done) => done()
|
|
238
|
+
}
|
|
239
|
+
if (isLogoutRequest(req)) {
|
|
240
|
+
delete req.session.userId
|
|
241
|
+
}
|
|
242
|
+
next()
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
const accountManager = AccountManager.from({
|
|
246
|
+
authMethod: argv.auth,
|
|
247
|
+
emailService: app.locals.emailService,
|
|
248
|
+
tokenService: app.locals.tokenService,
|
|
249
|
+
host: argv.host,
|
|
250
|
+
accountTemplatePath: argv.templates.account,
|
|
251
|
+
store: ldp,
|
|
252
|
+
multiuser: argv.multiuser
|
|
253
|
+
})
|
|
254
|
+
app.locals.accountManager = accountManager
|
|
255
|
+
|
|
256
|
+
// Account Management API (create account, new cert)
|
|
257
|
+
app.use('/', API.accounts.middleware(accountManager))
|
|
258
|
+
|
|
259
|
+
// Set up authentication-related API endpoints and app.locals
|
|
260
|
+
initAuthentication(app, argv)
|
|
261
|
+
|
|
262
|
+
if (argv.multiuser) {
|
|
263
|
+
app.use(vhost('*', LdpMiddleware(corsSettings)))
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function initLoggers () {
|
|
268
|
+
aclCheck.configureLogger(debug.ACL)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Determines whether the given request is a logout request
|
|
273
|
+
*/
|
|
274
|
+
function isLogoutRequest (req) {
|
|
275
|
+
// TODO: this is a hack that hard-codes OIDC paths,
|
|
276
|
+
// this code should live in the OIDC module
|
|
277
|
+
return req.path === '/logout' || req.path === '/goodbye'
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Sets up authentication-related routes and handlers for the app.
|
|
282
|
+
*
|
|
283
|
+
* @param app {Object} Express.js app instance
|
|
284
|
+
* @param argv {Object} Config options hashmap
|
|
285
|
+
*/
|
|
286
|
+
function initAuthentication (app, argv) {
|
|
287
|
+
const auth = argv.forceUser ? 'forceUser' : argv.auth
|
|
288
|
+
if (!(auth in API.authn)) {
|
|
289
|
+
throw new Error(`Unsupported authentication scheme: ${auth}`)
|
|
290
|
+
}
|
|
291
|
+
API.authn[auth].initialize(app, argv)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Returns a settings object for Express.js sessions.
|
|
296
|
+
*
|
|
297
|
+
* @param secureCookies {boolean}
|
|
298
|
+
* @param host {SolidHost}
|
|
299
|
+
*
|
|
300
|
+
* @return {Object} `express-session` settings object
|
|
301
|
+
*/
|
|
302
|
+
function sessionSettings (secureCookies, host) {
|
|
303
|
+
const sessionSettings = {
|
|
304
|
+
name: 'nssidp.sid',
|
|
305
|
+
secret: uuid.v1(),
|
|
306
|
+
saveUninitialized: false,
|
|
307
|
+
resave: false,
|
|
308
|
+
rolling: true,
|
|
309
|
+
cookie: {
|
|
310
|
+
maxAge: 24 * 60 * 60 * 1000
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Cookies should set to be secure if https is on
|
|
314
|
+
if (secureCookies) {
|
|
315
|
+
sessionSettings.cookie.secure = true
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Determine the cookie domain
|
|
319
|
+
sessionSettings.cookie.domain = host.cookieDomain
|
|
320
|
+
|
|
321
|
+
return sessionSettings
|
|
322
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module.exports = createServer
|
|
2
|
+
|
|
3
|
+
const express = require('express')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const https = require('https')
|
|
6
|
+
const http = require('http')
|
|
7
|
+
const SolidWs = require('solid-ws')
|
|
8
|
+
const debug = require('./debug')
|
|
9
|
+
const createApp = require('./create-app')
|
|
10
|
+
const globalTunnel = require('global-tunnel-ng')
|
|
11
|
+
|
|
12
|
+
function createServer (argv, app) {
|
|
13
|
+
argv = argv || {}
|
|
14
|
+
app = app || express()
|
|
15
|
+
const ldpApp = createApp(argv)
|
|
16
|
+
const ldp = ldpApp.locals.ldp || {}
|
|
17
|
+
let mount = argv.mount || '/'
|
|
18
|
+
// Removing ending '/'
|
|
19
|
+
if (mount.length > 1 &&
|
|
20
|
+
mount[mount.length - 1] === '/') {
|
|
21
|
+
mount = mount.slice(0, -1)
|
|
22
|
+
}
|
|
23
|
+
app.use(mount, ldpApp)
|
|
24
|
+
debug.settings('Base URL (--mount): ' + mount)
|
|
25
|
+
|
|
26
|
+
if (argv.idp) {
|
|
27
|
+
console.warn('The idp configuration option has been renamed to multiuser.')
|
|
28
|
+
argv.multiuser = argv.idp
|
|
29
|
+
delete argv.idp
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (argv.httpProxy) {
|
|
33
|
+
globalTunnel.initialize(argv.httpProxy)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let server
|
|
37
|
+
const needsTLS = argv.sslKey || argv.sslCert
|
|
38
|
+
if (!needsTLS) {
|
|
39
|
+
server = http.createServer(app)
|
|
40
|
+
} else {
|
|
41
|
+
debug.settings('SSL Private Key path: ' + argv.sslKey)
|
|
42
|
+
debug.settings('SSL Certificate path: ' + argv.sslCert)
|
|
43
|
+
|
|
44
|
+
if (!argv.sslCert && !argv.sslKey) {
|
|
45
|
+
throw new Error('Missing SSL cert and SSL key to enable WebIDs')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!argv.sslKey && argv.sslCert) {
|
|
49
|
+
throw new Error('Missing path for SSL key')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!argv.sslCert && argv.sslKey) {
|
|
53
|
+
throw new Error('Missing path for SSL cert')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let key
|
|
57
|
+
try {
|
|
58
|
+
key = fs.readFileSync(argv.sslKey)
|
|
59
|
+
} catch (e) {
|
|
60
|
+
throw new Error('Can\'t find SSL key in ' + argv.sslKey)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let cert
|
|
64
|
+
try {
|
|
65
|
+
cert = fs.readFileSync(argv.sslCert)
|
|
66
|
+
} catch (e) {
|
|
67
|
+
throw new Error('Can\'t find SSL cert in ' + argv.sslCert)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const credentials = Object.assign({
|
|
71
|
+
key: key,
|
|
72
|
+
cert: cert
|
|
73
|
+
}, argv)
|
|
74
|
+
|
|
75
|
+
if (ldp.webid && ldp.auth === 'tls') {
|
|
76
|
+
credentials.requestCert = true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
server = https.createServer(credentials, app)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Look for port or list of ports to redirect to argv.port
|
|
83
|
+
if ('redirectHttpFrom' in argv) {
|
|
84
|
+
const redirectHttpFroms = argv.redirectHttpFrom.constructor === Array
|
|
85
|
+
? argv.redirectHttpFrom
|
|
86
|
+
: [argv.redirectHttpFrom]
|
|
87
|
+
const portStr = argv.port === 443 ? '' : ':' + argv.port
|
|
88
|
+
redirectHttpFroms.forEach(redirectHttpFrom => {
|
|
89
|
+
debug.settings('will redirect from port ' + redirectHttpFrom + ' to port ' + argv.port)
|
|
90
|
+
const redirectingServer = express()
|
|
91
|
+
redirectingServer.get('*', function (req, res) {
|
|
92
|
+
const host = req.headers.host.split(':') // ignore port
|
|
93
|
+
debug.server(host, '=> https://' + host + portStr + req.url)
|
|
94
|
+
res.redirect('https://' + host + portStr + req.url)
|
|
95
|
+
})
|
|
96
|
+
redirectingServer.listen(redirectHttpFrom)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Setup Express app
|
|
101
|
+
if (ldp.live) {
|
|
102
|
+
const solidWs = SolidWs(server, ldpApp)
|
|
103
|
+
ldpApp.locals.ldp.live = solidWs.publish.bind(solidWs)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return server
|
|
107
|
+
}
|
package/lib/debug.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const debug = require('debug')
|
|
2
|
+
|
|
3
|
+
exports.handlers = debug('solid:handlers')
|
|
4
|
+
exports.errors = debug('solid:errors')
|
|
5
|
+
exports.ACL = debug('solid:ACL')
|
|
6
|
+
exports.cache = debug('solid:cache')
|
|
7
|
+
exports.parse = debug('solid:parse')
|
|
8
|
+
exports.metadata = debug('solid:metadata')
|
|
9
|
+
exports.authentication = debug('solid:authentication')
|
|
10
|
+
exports.settings = debug('solid:settings')
|
|
11
|
+
exports.server = debug('solid:server')
|
|
12
|
+
exports.subscription = debug('solid:subscription')
|
|
13
|
+
exports.container = debug('solid:container')
|
|
14
|
+
exports.accounts = debug('solid:accounts')
|
|
15
|
+
exports.email = debug('solid:email')
|
|
16
|
+
exports.ldp = debug('solid:ldp')
|
|
17
|
+
exports.fs = debug('solid:fs')
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module.exports = allow
|
|
2
|
+
|
|
3
|
+
// const path = require('path')
|
|
4
|
+
const ACL = require('../acl-checker')
|
|
5
|
+
const debug = require('../debug.js').ACL
|
|
6
|
+
// const error = require('../http-error')
|
|
7
|
+
|
|
8
|
+
function allow (mode) {
|
|
9
|
+
return async function allowHandler (req, res, next) {
|
|
10
|
+
const ldp = req.app.locals.ldp || {}
|
|
11
|
+
if (!ldp.webid) {
|
|
12
|
+
return next()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Set up URL to filesystem mapping
|
|
16
|
+
const rootUrl = ldp.resourceMapper.resolveUrl(req.hostname)
|
|
17
|
+
|
|
18
|
+
// Determine the actual path of the request
|
|
19
|
+
// (This is used as an ugly hack to check the ACL status of other resources.)
|
|
20
|
+
let resourcePath = res && res.locals && res.locals.path
|
|
21
|
+
? res.locals.path
|
|
22
|
+
: req.path
|
|
23
|
+
|
|
24
|
+
// Check whether the resource exists
|
|
25
|
+
let stat
|
|
26
|
+
try {
|
|
27
|
+
const ret = await ldp.exists(req.hostname, resourcePath)
|
|
28
|
+
stat = ret.stream
|
|
29
|
+
} catch (err) {
|
|
30
|
+
stat = null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Ensure directories always end in a slash
|
|
34
|
+
if (!resourcePath.endsWith('/') && stat && stat.isDirectory()) {
|
|
35
|
+
resourcePath += '/'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const trustedOrigins = [ldp.resourceMapper.resolveUrl(req.hostname)].concat(ldp.trustedOrigins)
|
|
39
|
+
if (ldp.multiuser) {
|
|
40
|
+
trustedOrigins.push(ldp.serverUri)
|
|
41
|
+
}
|
|
42
|
+
// Obtain and store the ACL of the requested resource
|
|
43
|
+
const resourceUrl = rootUrl + resourcePath
|
|
44
|
+
// Ensure the user has the required permission
|
|
45
|
+
const userId = req.session.userId
|
|
46
|
+
try {
|
|
47
|
+
req.acl = ACL.createFromLDPAndRequest(resourceUrl, ldp, req)
|
|
48
|
+
|
|
49
|
+
// if (resourceUrl.endsWith('.acl')) mode = 'Control'
|
|
50
|
+
const isAllowed = await req.acl.can(userId, mode, req.method, stat)
|
|
51
|
+
if (isAllowed) {
|
|
52
|
+
return next()
|
|
53
|
+
}
|
|
54
|
+
} catch (error) { next(error) }
|
|
55
|
+
if (mode === 'Read' && (resourcePath === '' || resourcePath === '/')) {
|
|
56
|
+
// This is a hack to make NSS check the ACL for representation that is served for root (if any)
|
|
57
|
+
// See https://github.com/solid/node-solid-server/issues/1063 for more info
|
|
58
|
+
const representationUrl = `${rootUrl}/index.html`
|
|
59
|
+
let representationPath
|
|
60
|
+
try {
|
|
61
|
+
representationPath = await ldp.resourceMapper.mapUrlToFile({ url: representationUrl })
|
|
62
|
+
} catch (err) {
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// We ONLY want to do this when the HTML representation exists
|
|
66
|
+
if (representationPath) {
|
|
67
|
+
req.acl = ACL.createFromLDPAndRequest(representationUrl, ldp, req)
|
|
68
|
+
const representationIsAllowed = await req.acl.can(userId, mode)
|
|
69
|
+
if (representationIsAllowed) {
|
|
70
|
+
return next()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// check user is owner. Find owner from /.meta
|
|
76
|
+
if (resourceUrl.endsWith('.acl') && userId === await ldp.getOwner(req.hostname)) return next()
|
|
77
|
+
|
|
78
|
+
const error = req.authError || await req.acl.getError(userId, mode)
|
|
79
|
+
debug(`${mode} access denied to ${userId || '(none)'}: ${error.status} - ${error.message}`)
|
|
80
|
+
next(error)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// An authentication proxy is a reverse proxy
|
|
2
|
+
// that sends a logged-in Solid user's details to a backend
|
|
3
|
+
module.exports = addAuthProxyHandlers
|
|
4
|
+
|
|
5
|
+
const { createProxyMiddleware } = require('http-proxy-middleware')
|
|
6
|
+
const debug = require('../debug')
|
|
7
|
+
const allow = require('./allow')
|
|
8
|
+
|
|
9
|
+
const PROXY_SETTINGS = {
|
|
10
|
+
logLevel: 'silent',
|
|
11
|
+
changeOrigin: true
|
|
12
|
+
}
|
|
13
|
+
const REQUIRED_PERMISSIONS = {
|
|
14
|
+
get: ['Read'],
|
|
15
|
+
options: ['Read'],
|
|
16
|
+
use: ['Read', 'Write']
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Registers Auth Proxy handlers for each target
|
|
20
|
+
function addAuthProxyHandlers (app, targets) {
|
|
21
|
+
for (const sourcePath in targets) {
|
|
22
|
+
addAuthProxyHandler(app, sourcePath, targets[sourcePath])
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Registers an Auth Proxy handler for the given target
|
|
27
|
+
function addAuthProxyHandler (app, sourcePath, target) {
|
|
28
|
+
debug.settings(`Add auth proxy from ${sourcePath} to ${target}`)
|
|
29
|
+
|
|
30
|
+
// Proxy to the target, removing the source path
|
|
31
|
+
// (e.g., /my/proxy/path resolves to http://my.proxy/path)
|
|
32
|
+
const sourcePathLength = sourcePath.length
|
|
33
|
+
const settings = Object.assign({
|
|
34
|
+
target,
|
|
35
|
+
onProxyReq: addAuthHeaders,
|
|
36
|
+
onProxyReqWs: addAuthHeaders,
|
|
37
|
+
pathRewrite: path => path.substr(sourcePathLength)
|
|
38
|
+
}, PROXY_SETTINGS)
|
|
39
|
+
|
|
40
|
+
// Activate the proxy
|
|
41
|
+
const proxy = createProxyMiddleware(settings)
|
|
42
|
+
for (const action in REQUIRED_PERMISSIONS) {
|
|
43
|
+
const permissions = REQUIRED_PERMISSIONS[action]
|
|
44
|
+
app[action](`${sourcePath}*`, setOriginalUrl, ...permissions.map(allow), proxy)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Adds a headers with authentication information
|
|
49
|
+
function addAuthHeaders (proxyReq, req) {
|
|
50
|
+
const { session = {}, headers = {} } = req
|
|
51
|
+
if (session.userId) {
|
|
52
|
+
proxyReq.setHeader('User', session.userId)
|
|
53
|
+
}
|
|
54
|
+
if (headers.host) {
|
|
55
|
+
proxyReq.setHeader('Forwarded', `host=${headers.host}`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Sets the original URL on the request (for the ACL handler)
|
|
60
|
+
function setOriginalUrl (req, res, next) {
|
|
61
|
+
res.locals.path = req.originalUrl
|
|
62
|
+
next()
|
|
63
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* eslint-disable node/no-deprecated-api */
|
|
2
|
+
|
|
3
|
+
module.exports = handler
|
|
4
|
+
|
|
5
|
+
const debug = require('../debug')
|
|
6
|
+
const error = require('../http-error')
|
|
7
|
+
const ldpCopy = require('../ldp-copy')
|
|
8
|
+
const url = require('url')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Handles HTTP COPY requests to import a given resource (specified in the
|
|
12
|
+
* `Source:` header) to a destination (specified in request path).
|
|
13
|
+
* For the moment, you can copy from public resources only (no auth delegation
|
|
14
|
+
* is implemented), and is mainly intended for use with
|
|
15
|
+
* "Save an external resource to Solid" type apps.
|
|
16
|
+
* @method handler
|
|
17
|
+
*/
|
|
18
|
+
async function handler (req, res, next) {
|
|
19
|
+
const copyFrom = req.header('Source')
|
|
20
|
+
if (!copyFrom) {
|
|
21
|
+
return next(error(400, 'Source header required'))
|
|
22
|
+
}
|
|
23
|
+
const fromExternal = !!url.parse(copyFrom).hostname
|
|
24
|
+
const ldp = req.app.locals.ldp
|
|
25
|
+
const serverRoot = ldp.resourceMapper.resolveUrl(req.hostname)
|
|
26
|
+
const copyFromUrl = fromExternal ? copyFrom : serverRoot + copyFrom
|
|
27
|
+
const copyToUrl = res.locals.path || req.path
|
|
28
|
+
try {
|
|
29
|
+
await ldpCopy(ldp.resourceMapper, copyToUrl, copyFromUrl)
|
|
30
|
+
} catch (err) {
|
|
31
|
+
const statusCode = err.statusCode || 500
|
|
32
|
+
const errorMessage = err.statusMessage || err.message
|
|
33
|
+
debug.handlers('Error with COPY request:' + errorMessage)
|
|
34
|
+
return next(error(statusCode, errorMessage))
|
|
35
|
+
}
|
|
36
|
+
res.set('Location', copyToUrl)
|
|
37
|
+
res.sendStatus(201)
|
|
38
|
+
next()
|
|
39
|
+
}
|