Skip to content

Commit 03b5ff4

Browse files
security: add ACL check for WebSocket subscriptions
Check WAC read permission before allowing WebSocket subscriptions. This prevents information leakage via notifications to unauthorized users. - Add authorizeSubscription callback for solid-ws - Check ACL read access before allowing subscription - Deny subscription returns 'err <url> forbidden' - Currently treats all WS connections as anonymous (TODO: auth integration) Depends on: nodeSolidServer/node-solid-ws#29 Fixes #1334
1 parent 6f51539 commit 03b5ff4

File tree

1 file changed

+42
-1
lines changed

1 file changed

+42
-1
lines changed

lib/create-server.mjs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import SolidWs from 'solid-ws'
66
import globalTunnel from 'global-tunnel-ng'
77
import debug from './debug.mjs'
88
import createApp from './create-app.mjs'
9+
import ACLChecker from './acl-checker.mjs'
10+
import url from 'url'
911

1012
function createServer (argv, app) {
1113
argv = argv || {}
@@ -96,7 +98,46 @@ function createServer (argv, app) {
9698

9799
// Setup Express app
98100
if (ldp.live) {
99-
const solidWs = SolidWs(server, ldpApp)
101+
// Authorization callback for WebSocket subscriptions
102+
// Checks ACL read permission before allowing subscription
103+
const authorizeSubscription = function (iri, req, callback) {
104+
// TODO: Extract userId from session cookie or Authorization header
105+
// For now, treat all WebSocket connections as anonymous
106+
// This still prevents anonymous access to private resources
107+
const userId = null
108+
109+
try {
110+
const parsedUrl = url.parse(iri)
111+
const resourcePath = decodeURIComponent(parsedUrl.pathname)
112+
const hostname = parsedUrl.hostname || req.headers.host?.split(':')[0]
113+
const rootUrl = ldp.resourceMapper.resolveUrl(hostname)
114+
const resourceUrl = rootUrl + resourcePath
115+
116+
// Create a minimal request-like object for ACLChecker
117+
const pseudoReq = {
118+
hostname,
119+
path: resourcePath,
120+
get: (header) => req.headers[header.toLowerCase()]
121+
}
122+
123+
const aclChecker = ACLChecker.createFromLDPAndRequest(resourceUrl, ldp, pseudoReq)
124+
125+
aclChecker.can(userId, 'Read')
126+
.then(allowed => {
127+
debug.ACL(`WebSocket subscription ${allowed ? 'allowed' : 'denied'} for ${iri} (user: ${userId || 'anonymous'})`)
128+
callback(null, allowed)
129+
})
130+
.catch(err => {
131+
debug.ACL(`WebSocket ACL check error for ${iri}: ${err.message}`)
132+
callback(null, false)
133+
})
134+
} catch (err) {
135+
debug.ACL(`WebSocket authorization error: ${err.message}`)
136+
callback(null, false)
137+
}
138+
}
139+
140+
const solidWs = SolidWs(server, ldpApp, { authorize: authorizeSubscription })
100141
ldpApp.locals.ldp.live = solidWs.publish.bind(solidWs)
101142
}
102143

0 commit comments

Comments
 (0)