Skip to content

Commit 73b2626

Browse files
author
CI Fix
committed
ESM Migration: Complete lib/api/ and lib/handlers/ conversion
lib/api/ conversion completed: - authn/webid-tls.mjs (70 lines - TLS authentication) - authn/webid-oidc.mjs (203 lines - OIDC authentication) lib/handlers/ conversion completed (7 new .mjs files): - restrict-to-top-domain.mjs (simple middleware) - options.mjs (HTTP OPTIONS handler) - index.mjs (directory index handler) - auth-proxy.mjs (authentication proxy) - cors-proxy.mjs (96 lines - CORS proxy with IP filtering) - notify.mjs (149 lines - notification system) - error-pages.mjs (213 lines - error handling & databrowser) Progress: - lib/ main files: 9/9 converted - lib/api/: 6/6 converted - lib/handlers/: 14/14 converted - All new modules error-free and maintain dual compatibility
1 parent ac4eec7 commit 73b2626

File tree

9 files changed

+887
-0
lines changed

9 files changed

+887
-0
lines changed

lib/api/authn/webid-oidc.mjs

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/**
2+
* OIDC Relying Party API handler module.
3+
*/
4+
5+
import express from 'express'
6+
import { routeResolvedFile } from '../../utils.mjs'
7+
import { urlencoded } from 'body-parser'
8+
const bodyParser = urlencoded({ extended: false })
9+
import OidcManager from '../../models/oidc-manager.mjs'
10+
import { LoginRequest } from '../../requests/login-request.mjs'
11+
import { SharingRequest } from '../../requests/sharing-request.mjs'
12+
13+
import restrictToTopDomain from '../../handlers/restrict-to-top-domain.mjs'
14+
15+
import PasswordResetEmailRequest from '../../requests/password-reset-email-request.mjs'
16+
import PasswordChangeRequest from '../../requests/password-change-request.mjs'
17+
18+
import { createRequire } from 'module'
19+
const require = createRequire(import.meta.url)
20+
const { AuthCallbackRequest } = require('@solid/oidc-auth-manager').handlers
21+
22+
/**
23+
* Sets up OIDC authentication for the given app.
24+
*
25+
* @param app {Object} Express.js app instance
26+
* @param argv {Object} Config options hashmap
27+
*/
28+
export function initialize (app, argv) {
29+
const oidc = OidcManager.fromServerConfig(argv)
30+
app.locals.oidc = oidc
31+
oidc.initialize()
32+
33+
// Attach the OIDC API
34+
app.use('/', middleware(oidc))
35+
36+
// Perform the actual authentication
37+
app.use('/', async (req, res, next) => {
38+
oidc.rs.authenticate({ tokenTypesSupported: argv.tokenTypesSupported })(req, res, (err) => {
39+
// Error handling should be deferred to the ldp in case a user with a bad token is trying
40+
// to access a public resource
41+
if (err) {
42+
req.authError = err
43+
res.status(200)
44+
}
45+
next()
46+
})
47+
})
48+
49+
// Expose session.userId
50+
app.use('/', (req, res, next) => {
51+
oidc.webIdFromClaims(req.claims)
52+
.then(webId => {
53+
if (webId) {
54+
req.session.userId = webId
55+
}
56+
57+
next()
58+
})
59+
.catch(err => {
60+
const error = new Error('Could not verify Web ID from token claims')
61+
error.statusCode = 401
62+
error.statusText = 'Invalid login'
63+
error.cause = err
64+
65+
console.error(err)
66+
67+
next(error)
68+
})
69+
})
70+
}
71+
72+
/**
73+
* Returns a router with OIDC Relying Party and Identity Provider middleware:
74+
*
75+
* @method middleware
76+
*
77+
* @param oidc {OidcManager}
78+
*
79+
* @return {Router} Express router
80+
*/
81+
export function middleware (oidc) {
82+
const router = express.Router('/')
83+
84+
// User-facing Authentication API
85+
router.get(['/login', '/signin'], LoginRequest.get)
86+
87+
router.post('/login/password', bodyParser, LoginRequest.loginPassword)
88+
89+
router.post('/login/tls', bodyParser, LoginRequest.loginTls)
90+
91+
router.get('/sharing', SharingRequest.get)
92+
router.post('/sharing', bodyParser, SharingRequest.share)
93+
94+
router.get('/account/password/reset', restrictToTopDomain, PasswordResetEmailRequest.get)
95+
router.post('/account/password/reset', restrictToTopDomain, bodyParser, PasswordResetEmailRequest.post)
96+
97+
router.get('/account/password/change', restrictToTopDomain, PasswordChangeRequest.get)
98+
router.post('/account/password/change', restrictToTopDomain, bodyParser, PasswordChangeRequest.post)
99+
100+
router.get('/.well-known/solid/logout/', (req, res) => res.redirect('/logout'))
101+
102+
router.get('/goodbye', (req, res) => { res.render('auth/goodbye') })
103+
104+
// The relying party callback is called at the end of the OIDC signin process
105+
router.get('/api/oidc/rp/:issuer_id', AuthCallbackRequest.get)
106+
107+
// Static assets related to authentication
108+
const authAssets = [
109+
['/.well-known/solid/login/', '../static/popup-redirect.html', false],
110+
['/common/', 'solid-auth-client/dist-popup/popup.html']
111+
]
112+
authAssets.map(args => routeResolvedFile(router, ...args))
113+
114+
// Initialize the OIDC Identity Provider routes/api
115+
// router.get('/.well-known/openid-configuration', discover.bind(provider))
116+
// router.get('/jwks', jwks.bind(provider))
117+
// router.post('/register', register.bind(provider))
118+
// router.get('/authorize', authorize.bind(provider))
119+
// router.post('/authorize', authorize.bind(provider))
120+
// router.post('/token', token.bind(provider))
121+
// router.get('/userinfo', userinfo.bind(provider))
122+
// router.get('/logout', logout.bind(provider))
123+
const oidcProviderApi = require('oidc-op-express')(oidc.provider)
124+
router.use('/', oidcProviderApi)
125+
126+
return router
127+
}
128+
129+
/**
130+
* Sets the `WWW-Authenticate` response header for 401 error responses.
131+
* Used by error-pages handler.
132+
*
133+
* @param req {IncomingRequest}
134+
* @param res {ServerResponse}
135+
* @param err {Error}
136+
*/
137+
export function setAuthenticateHeader (req, res, err) {
138+
const locals = req.app.locals
139+
140+
const errorParams = {
141+
realm: locals.host.serverUri,
142+
scope: 'openid webid',
143+
error: err.error,
144+
error_description: err.error_description,
145+
error_uri: err.error_uri
146+
}
147+
148+
const challengeParams = Object.keys(errorParams)
149+
.filter(key => !!errorParams[key])
150+
.map(key => `${key}="${errorParams[key]}"`)
151+
.join(', ')
152+
153+
res.set('WWW-Authenticate', 'Bearer ' + challengeParams)
154+
}
155+
156+
/**
157+
* Provides custom logic for error status code overrides.
158+
*
159+
* @param statusCode {number}
160+
* @param req {IncomingRequest}
161+
*
162+
* @returns {number}
163+
*/
164+
export function statusCodeOverride (statusCode, req) {
165+
if (isEmptyToken(req)) {
166+
return 400
167+
} else {
168+
return statusCode
169+
}
170+
}
171+
172+
/**
173+
* Tests whether the `Authorization:` header includes an empty or missing Bearer
174+
* token.
175+
*
176+
* @param req {IncomingRequest}
177+
*
178+
* @returns {boolean}
179+
*/
180+
export function isEmptyToken (req) {
181+
const header = req.get('Authorization')
182+
183+
if (!header) { return false }
184+
185+
if (header.startsWith('Bearer')) {
186+
const fragments = header.split(' ')
187+
188+
if (fragments.length === 1) {
189+
return true
190+
} else if (!fragments[1]) {
191+
return true
192+
}
193+
}
194+
195+
return false
196+
}
197+
198+
export default {
199+
initialize,
200+
isEmptyToken,
201+
middleware,
202+
setAuthenticateHeader,
203+
statusCodeOverride
204+
}

lib/api/authn/webid-tls.mjs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import webid from '../../webid/tls.mjs'
2+
import debug from '../../debug.mjs'
3+
const debugAuth = debug.authentication
4+
5+
export function initialize (app, argv) {
6+
app.use('/', handler)
7+
}
8+
9+
export function handler (req, res, next) {
10+
// User already logged in? skip
11+
if (req.session.userId) {
12+
debugAuth('User: ' + req.session.userId)
13+
res.set('User', req.session.userId)
14+
return next()
15+
}
16+
17+
// No certificate? skip
18+
const certificate = getCertificateViaTLS(req)
19+
if (!certificate) {
20+
setEmptySession(req)
21+
return next()
22+
}
23+
24+
// Verify webid
25+
webid.verify(certificate, function (err, result) {
26+
if (err) {
27+
debugAuth('Error processing certificate: ' + err.message)
28+
setEmptySession(req)
29+
return next()
30+
}
31+
req.session.userId = result
32+
debugAuth('Identified user: ' + req.session.userId)
33+
res.set('User', req.session.userId)
34+
return next()
35+
})
36+
}
37+
38+
// Tries to obtain a client certificate retrieved through the TLS handshake
39+
function getCertificateViaTLS (req) {
40+
const certificate = req.connection.getPeerCertificate &&
41+
req.connection.getPeerCertificate()
42+
if (certificate && Object.keys(certificate).length > 0) {
43+
return certificate
44+
}
45+
debugAuth('No peer certificate received during TLS handshake.')
46+
}
47+
48+
export function setEmptySession (req) {
49+
req.session.userId = ''
50+
}
51+
52+
/**
53+
* Sets the `WWW-Authenticate` response header for 401 error responses.
54+
* Used by error-pages handler.
55+
*
56+
* @param req {IncomingRequest}
57+
* @param res {ServerResponse}
58+
*/
59+
export function setAuthenticateHeader (req, res) {
60+
const locals = req.app.locals
61+
62+
res.set('WWW-Authenticate', `WebID-TLS realm="${locals.host.serverUri}"`)
63+
}
64+
65+
export default {
66+
initialize,
67+
handler,
68+
setAuthenticateHeader,
69+
setEmptySession
70+
}

lib/handlers/auth-proxy.mjs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// An authentication proxy is a reverse proxy
2+
// that sends a logged-in Solid user's details to a backend
3+
4+
import { createRequire } from 'module'
5+
const require = createRequire(import.meta.url)
6+
const { createProxyMiddleware } = require('http-proxy-middleware')
7+
import debug from '../debug.mjs'
8+
import allow from './allow.mjs'
9+
10+
const PROXY_SETTINGS = {
11+
logLevel: 'silent',
12+
changeOrigin: true
13+
}
14+
const REQUIRED_PERMISSIONS = {
15+
get: ['Read'],
16+
options: ['Read'],
17+
use: ['Read', 'Write']
18+
}
19+
20+
// Registers Auth Proxy handlers for each target
21+
export default function addAuthProxyHandlers (app, targets) {
22+
for (const sourcePath in targets) {
23+
addAuthProxyHandler(app, sourcePath, targets[sourcePath])
24+
}
25+
}
26+
27+
// Registers an Auth Proxy handler for the given target
28+
function addAuthProxyHandler (app, sourcePath, target) {
29+
debug.settings(`Add auth proxy from ${sourcePath} to ${target}`)
30+
31+
// Proxy to the target, removing the source path
32+
// (e.g., /my/proxy/path resolves to http://my.proxy/path)
33+
const sourcePathLength = sourcePath.length
34+
const settings = Object.assign({
35+
target,
36+
onProxyReq: addAuthHeaders,
37+
onProxyReqWs: addAuthHeaders,
38+
pathRewrite: path => path.substr(sourcePathLength)
39+
}, PROXY_SETTINGS)
40+
41+
// Activate the proxy
42+
const proxy = createProxyMiddleware(settings)
43+
for (const action in REQUIRED_PERMISSIONS) {
44+
const permissions = REQUIRED_PERMISSIONS[action]
45+
app[action](`${sourcePath}*`, setOriginalUrl, ...permissions.map(allow), proxy)
46+
}
47+
}
48+
49+
// Adds a headers with authentication information
50+
function addAuthHeaders (proxyReq, req) {
51+
const { session = {}, headers = {} } = req
52+
if (session.userId) {
53+
proxyReq.setHeader('User', session.userId)
54+
}
55+
if (headers.host) {
56+
proxyReq.setHeader('Forwarded', `host=${headers.host}`)
57+
}
58+
}
59+
60+
// Sets the original URL on the request (for the ACL handler)
61+
function setOriginalUrl (req, res, next) {
62+
res.locals.path = req.originalUrl
63+
next()
64+
}

0 commit comments

Comments
 (0)