-
Notifications
You must be signed in to change notification settings - Fork 306
Expand file tree
/
Copy pathcreate-server.mjs
More file actions
169 lines (145 loc) · 5.14 KB
/
create-server.mjs
File metadata and controls
169 lines (145 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import express from 'express'
import fs from 'fs'
import https from 'https'
import http from 'http'
import SolidWs from 'solid-ws'
import globalTunnel from 'global-tunnel-ng'
import debug from './debug.mjs'
import createApp from './create-app.mjs'
import ACLChecker from './acl-checker.mjs'
import url from 'url'
function createServer (argv, app) {
argv = argv || {}
app = app || express()
const ldpApp = createApp(argv)
const ldp = ldpApp.locals.ldp || {}
let mount = argv.mount || '/'
// Removing ending '/'
if (mount.length > 1 &&
mount[mount.length - 1] === '/') {
mount = mount.slice(0, -1)
}
app.use(mount, ldpApp)
debug.settings('Base URL (--mount): ' + mount)
if (argv.idp) {
console.warn('The idp configuration option has been renamed to multiuser.')
argv.multiuser = argv.idp
delete argv.idp
}
if (argv.httpProxy) {
globalTunnel.initialize(argv.httpProxy)
}
let server
const needsTLS = argv.sslKey || argv.sslCert
if (!needsTLS) {
server = http.createServer(app)
} else {
debug.settings('SSL Private Key path: ' + argv.sslKey)
debug.settings('SSL Certificate path: ' + argv.sslCert)
if (!argv.sslCert && !argv.sslKey) {
throw new Error('Missing SSL cert and SSL key to enable WebIDs')
}
if (!argv.sslKey && argv.sslCert) {
throw new Error('Missing path for SSL key')
}
if (!argv.sslCert && argv.sslKey) {
throw new Error('Missing path for SSL cert')
}
let key
try {
key = fs.readFileSync(argv.sslKey)
} catch (e) {
throw new Error('Can\'t find SSL key in ' + argv.sslKey)
}
let cert
try {
cert = fs.readFileSync(argv.sslCert)
} catch (e) {
throw new Error('Can\'t find SSL cert in ' + argv.sslCert)
}
const credentials = Object.assign({
key: key,
cert: cert
}, argv)
if (ldp.webid && ldp.auth === 'tls') {
credentials.requestCert = true
}
server = https.createServer(credentials, app)
}
// Look for port or list of ports to redirect to argv.port
if ('redirectHttpFrom' in argv) {
const redirectHttpFroms = argv.redirectHttpFrom.constructor === Array
? argv.redirectHttpFrom
: [argv.redirectHttpFrom]
const portStr = argv.port === 443 ? '' : ':' + argv.port
redirectHttpFroms.forEach(redirectHttpFrom => {
debug.settings('will redirect from port ' + redirectHttpFrom + ' to port ' + argv.port)
const redirectingServer = express()
redirectingServer.get('*', function (req, res) {
const host = req.headers.host.split(':') // ignore port
debug.server(host, '=> https://' + host + portStr + req.url)
res.redirect('https://' + host + portStr + req.url)
})
redirectingServer.listen(redirectHttpFrom)
})
}
// Setup Express app
if (ldp.live) {
// Authorization callback for WebSocket subscriptions
// Checks ACL read permission before allowing subscription
const authorizeSubscription = function (iri, req, callback) {
// TODO: Extract userId from session cookie or Authorization header
// For now, treat all WebSocket connections as anonymous
// This still prevents anonymous access to private resources
const userId = null
try {
const parsedUrl = url.parse(iri)
const resourcePath = decodeURIComponent(parsedUrl.pathname)
const hostname = parsedUrl.hostname || req.headers.host?.split(':')[0]
const rootUrl = ldp.resourceMapper.resolveUrl(hostname)
const resourceUrl = rootUrl + resourcePath
// Create a minimal request-like object for ACLChecker
const pseudoReq = {
hostname,
path: resourcePath,
get: (header) => req.headers[header.toLowerCase()]
}
const aclChecker = ACLChecker.createFromLDPAndRequest(resourceUrl, ldp, pseudoReq)
aclChecker.can(userId, 'Read')
.then(allowed => {
debug.ACL(`WebSocket subscription ${allowed ? 'allowed' : 'denied'} for ${iri} (user: ${userId || 'anonymous'})`)
callback(null, allowed)
})
.catch(err => {
debug.ACL(`WebSocket ACL check error for ${iri}: ${err.message}`)
callback(null, false)
})
} catch (err) {
debug.ACL(`WebSocket authorization error: ${err.message}`)
callback(null, false)
}
}
const solidWs = SolidWs(server, ldpApp, { authorize: authorizeSubscription })
ldpApp.locals.ldp.live = solidWs.publish.bind(solidWs)
}
// Wrap server.listen() to ensure async initialization completes after server starts
const originalListen = server.listen.bind(server)
server.listen = function (...args) {
// Start listening first
originalListen(...args)
// Then run async initialization (if needed)
if (ldpApp.locals.initFunction) {
const initFunction = ldpApp.locals.initFunction
delete ldpApp.locals.initFunction
// Run initialization after server is listening
initFunction()
.catch(err => {
console.error('Initialization error:', err)
server.emit('error', err)
})
}
return server
}
return server
}
export default createServer