Skip to content

Commit 67a05b7

Browse files
OIDC Integration - WIP
- WIP (fix standard warnings) - WIP (moving oidcConfig to config.json) - Add OIDC Issuer discovery link header on OPTIONS - Refactor OIDC client/handler code - Bump dep version, fix gitignore - Integrate Sign In w WebID page with OIDC login - Fix OIDC create user functionality - Fix storing redirectTo URL in session - Remove unneeded options obj (oidc.js) - WIP (create account using webid as OIDC _id) - Fix extraneous oidcIssuerHeader - WIP (fix response types) - Fix token() params - Fix authCallback() - WIP (switch to implicit workflow) - WIP (fix registration configs) - wip - Switch to authz code flow - Move createOIDCUser to oidc-rp-client (from identity-provider) - Implement OIDC /signout support
1 parent eddef4f commit 67a05b7

File tree

18 files changed

+787
-85
lines changed

18 files changed

+787
-85
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ npm-debug.log
99
/.acl
1010
/config.json
1111
/settings
12+

lib/api/accounts/signin.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,60 @@ module.exports = signin
33
const validUrl = require('valid-url')
44
const request = require('request')
55
const li = require('li')
6+
const debug = require('../../debug')
7+
// const util = require('../../utils')
68

79
function signin () {
810
return (req, res, next) => {
911
if (!validUrl.isUri(req.body.webid)) {
1012
return res.status(400).send('This is not a valid URI')
1113
}
1214

13-
request({ method: 'OPTIONS', uri: req.body.webid }, function (err, req) {
15+
let ldp = req.app.locals.ldp
16+
if (ldp.auth !== 'oidc') {
17+
res
18+
.status(500)
19+
.send('Not implemented')
20+
return
21+
}
22+
// let baseUrl = util.uriBase(req)
23+
24+
// Save the previously-requested URL to session
25+
// (so that the user can be redirected to it after signin)
26+
let returnToUrl = req.body.returnToUrl
27+
if (returnToUrl) {
28+
req.session.returnToUrl = returnToUrl
29+
debug.oidc('Saving returnToUrl in session as: ' + returnToUrl)
30+
} else {
31+
debug.oidc('Not saving returnToUrl to session (not found)!')
32+
}
33+
34+
// Discover the OIDC issuer from the WebID (or account URL)
35+
// via an OPTIONS request and its `oidc.issuer` link header
36+
request({ method: 'OPTIONS', uri: req.body.webid }, function (err, response) {
1437
if (err) {
1538
res.status(400).send('Did not find a valid endpoint')
1639
return
1740
}
18-
if (!req.headers.link) {
41+
if (!response.headers.link) {
1942
res.status(400).send('The URI requested is not a valid endpoint')
2043
return
2144
}
2245

23-
const linkHeaders = li.parse(req.headers.link)
24-
console.log(linkHeaders)
46+
const linkHeaders = li.parse(response.headers.link)
2547
if (!linkHeaders['oidc.issuer']) {
2648
res.status(400).send('The URI requested is not a valid endpoint')
2749
return
2850
}
51+
let issuer = linkHeaders['oidc.issuer']
2952

30-
res.redirect(linkHeaders['oidc.issuer'])
53+
// load the client for the issuer
54+
let oidcRpClient = req.app.locals.oidc
55+
oidcRpClient.authUrlForIssuer(issuer)
56+
.then((authUrl) => {
57+
res.redirect(authUrl)
58+
})
59+
.catch(next)
3160
})
3261
}
3362
}

lib/api/accounts/signout.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
11
module.exports = signout
22

3+
const debug = require('../../debug')
4+
5+
/**
6+
* Handles the /signout API call.
7+
* @param req
8+
* @param res
9+
*/
310
function signout () {
4-
return (req, res, next) => {
11+
return (req, res) => {
12+
const locals = req.app.locals
13+
const ldp = locals.ldp
14+
const userId = req.session.userId
15+
debug.idp(`Signing out user: ${userId}`)
16+
const idToken = req.session.idToken
17+
if (idToken && ldp.auth === 'oidc') {
18+
const issuer = req.session.issuer
19+
const oidcRpClient = locals.oidc
20+
Promise.resolve()
21+
.then(() => {
22+
return oidcRpClient.clientForIssuer(issuer)
23+
})
24+
.then((userOidcClient) => {
25+
return userOidcClient.client.signout(idToken)
26+
})
27+
.catch((err) => {
28+
debug.oidc('Error signing out: ', err)
29+
})
30+
}
531
req.session.userId = ''
632
req.session.identified = false
7-
res.status(200).send()
33+
debug.oidc('signout() finished. Redirecting.')
34+
res.redirect('/signed_out.html')
35+
// res.status(200).send('You have been signed out.')
836
}
937
}

lib/capability-discovery.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const serviceConfigDefaults = {
1111
'accounts': {
1212
// 'changePassword': '/api/account/changePassword',
1313
// 'delete': '/api/accounts/delete',
14+
15+
// Create new user (see IdentityProvider.post() in identity-provider.js)
1416
'new': '/api/accounts/new',
1517
'recover': '/api/accounts/recover',
1618
'signin': '/api/accounts/signin',
@@ -37,7 +39,7 @@ function capabilityDiscovery () {
3739
}
3840

3941
/**
40-
* Handles advertising the server capability endpoint (adds a Link Relation
42+
* Advertises the server capability endpoint (adds a Link Relation
4143
* header of type `service`, points to the capability document).
4244
* To be used with OPTIONS requests.
4345
* @method serviceEndpointHeader
@@ -60,7 +62,7 @@ function serviceEndpointHeader (req, res, next) {
6062
* @param next
6163
*/
6264
function serviceCapabilityDocument (serviceConfig) {
63-
return (req, res, next) => {
65+
return (req, res) => {
6466
// Add the server root url
6567
serviceConfig.root = util.uriBase(req) // TODO make sure we align with the rest
6668
// Add the 'apps' urls section

lib/create-app.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ const AccountRecovery = require('./account-recovery')
1515
const capabilityDiscovery = require('./capability-discovery')
1616
const bodyParser = require('body-parser')
1717
const API = require('./api')
18+
var debug = require('./debug')
19+
var OidcRpClient = require('./oidc-rp-client')
20+
var oidcHandler = require('./handlers/oidc')
1821

1922
var corsSettings = cors({
2023
methods: [
2124
'OPTIONS', 'HEAD', 'GET', 'PATCH', 'POST', 'PUT', 'DELETE'
2225
],
23-
exposedHeaders: 'User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Updates-Via, Allow, Content-Length',
26+
exposedHeaders: 'Authorization, User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Updates-Via, Allow, Content-Length',
2427
credentials: true,
2528
maxAge: 1728000,
2629
origin: true,
@@ -30,6 +33,7 @@ var corsSettings = cors({
3033
function createApp (argv = {}) {
3134
var ldp = new LDP(argv)
3235
var app = express()
36+
var oidcConfig = argv.oidc
3337

3438
app.use(corsSettings)
3539

@@ -56,6 +60,8 @@ function createApp (argv = {}) {
5660
// Setting options as local variable
5761
app.locals.ldp = ldp
5862
app.locals.appUrls = argv.apps // used for service capability discovery
63+
app.locals.oidcConfig = oidcConfig
64+
app.locals.rootUrl = argv.rootUrl
5965

6066
if (argv.email && argv.email.host) {
6167
app.locals.email = new EmailService(argv.email)
@@ -93,6 +99,21 @@ function createApp (argv = {}) {
9399
// Session
94100
app.use(session(sessionSettings))
95101

102+
// OpenID Connect Auth
103+
if (oidcConfig && ldp.auth === 'oidc') {
104+
app.options('*', oidcHandler.oidcIssuerHeader)
105+
debug.idp('Auth: OIDC!')
106+
var oidcRpClient = new OidcRpClient()
107+
// TODO: ensureTrustedClient is async, fix race condition on server startup
108+
debug.oidc('Initializing local/trusted client...')
109+
oidcRpClient.ensureTrustedClient(oidcConfig)
110+
app.locals.oidc = oidcRpClient
111+
112+
app.use('/', express.static(path.join(__dirname, '../static/oidc')))
113+
app.use('/', oidcHandler.authenticate(oidcRpClient))
114+
app.use('/api/oidc', oidcHandler.api(oidcRpClient))
115+
}
116+
96117
// Adding proxy
97118
if (ldp.proxy) {
98119
proxy(app, ldp.proxy)
@@ -119,10 +140,10 @@ function createApp (argv = {}) {
119140

120141
var needsOverwrite = function (req, res, next) {
121142
checkMasterAcl(req, function (found) {
122-
if (!found) {
143+
if (!found && !ldp.idp) {
123144
// this allows IdentityProvider to overwrite root acls
124145
idp.middleware(true)(req, res, next)
125-
} else if (found && ldp.idp) {
146+
} else if (ldp.idp) {
126147
idp.middleware(false)(req, res, next)
127148
} else {
128149
next()
@@ -133,8 +154,10 @@ function createApp (argv = {}) {
133154
// adds POST /api/accounts/new
134155
// adds POST /api/accounts/newCert
135156
app.get('/', idp.get.bind(idp))
157+
app.post('/api/accounts/signin',
158+
bodyParser.urlencoded({ extended: false }), API.accounts.signin())
136159
app.use('/api/accounts', needsOverwrite)
137-
app.post('/api/accounts/signin', bodyParser.urlencoded({ extended: false }), API.accounts.signin())
160+
app.get('/signout', API.accounts.signout())
138161
app.post('/api/accounts/signout', API.accounts.signout())
139162
}
140163

lib/debug.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ exports.subscription = debug('solid:subscription')
1212
exports.container = debug('solid:container')
1313
exports.idp = debug('solid:idp')
1414
exports.ldp = debug('solid:ldp')
15+
exports.oidc = debug('solid:oidc')

lib/handlers/error-pages.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module.exports = handler
22

33
var debug = require('../debug').server
44
var fs = require('fs')
5+
var util = require('../utils')
56

67
function handler (err, req, res, next) {
78
debug('Error page because of ' + err.message)
@@ -17,9 +18,18 @@ function handler (err, req, res, next) {
1718
// If noErrorPages is set,
1819
// then use built-in express default error handler
1920
if (ldp.noErrorPages) {
20-
return res
21+
if (err.status === 401 &&
22+
req.accepts('text/html') &&
23+
ldp.auth === 'oidc') {
24+
debug('On error pages redirect on 401')
25+
res.status(err.status)
26+
redirectToLogin(req, res, next)
27+
return
28+
}
29+
res
2130
.status(err.status)
2231
.send(err.message + '\n' || '')
32+
return
2333
}
2434

2535
// Check if error page exists
@@ -36,3 +46,31 @@ function handler (err, req, res, next) {
3646
res.send(text)
3747
})
3848
}
49+
50+
function redirectBody (url) {
51+
return `<!DOCTYPE HTML>
52+
<meta charset="UTF-8">
53+
<script>
54+
window.location.href = "${url}"
55+
</script>
56+
<noscript>
57+
<meta http-equiv="refresh" content="0; url=${url}">
58+
</noscript>
59+
<title>Redirecting...</title>
60+
If you are not redirected automatically, follow the <a href='${url}'>link to login</a>
61+
`
62+
}
63+
64+
function redirectToLogin (req, res, next) {
65+
res.header('Content-Type', 'text/html')
66+
var currentUrl = util.fullUrlForReq(req)
67+
let locals = req.app.locals
68+
// let loginUrl = util.uriBase(req) + '/signin.html?returnToUrl=' +
69+
// currentUrl
70+
let loginUrl = locals.rootUrl + '/signin.html?returnToUrl=' +
71+
currentUrl
72+
debug('Redirecting to login: ' + loginUrl)
73+
74+
var body = redirectBody(loginUrl)
75+
res.send(body)
76+
}

0 commit comments

Comments
 (0)