Skip to content

Commit dfba398

Browse files
Merge pull request #751 from solid/feature/client-login
Always log in through the client
2 parents 94fe0ba + 7654c3c commit dfba398

16 files changed

+137
-291
lines changed

common/js/solid-auth-client.bundle.js

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../node_modules/solid-auth-client/dist-lib/solid-auth-client.bundle.js

common/js/solid-auth-client.bundle.js.map

Lines changed: 0 additions & 1 deletion
This file was deleted.

common/js/solid-auth-client.bundle.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/popup.html

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>Log in</title>
7+
<link rel="stylesheet" href="/common/css/bootstrap.min.css">
8+
<script src="/common/js/solid-auth-client.bundle.js"></script>
9+
</head>
10+
<body>
11+
<div class="container">
12+
<div class="jumbotron">
13+
<h2>Log in to access this resource</h2>
14+
<p>
15+
The resource you are trying to access
16+
(<code>{{currentUrl}}</code>)
17+
requires you to log in.
18+
</p>
19+
<button class="btn btn-primary" onclick="login()">Log in</button>
20+
<button class="btn btn-primary" onclick="register()">Register</button>
21+
</div>
22+
</div>
23+
<script>
24+
async function login() {
25+
// Log in through the popup
26+
const popupUri = '/common/popup.html'
27+
const session = await SolidAuthClient.popupLogin({ popupUri })
28+
if (session) {
29+
// Make authenticated request to the server to establish a session cookie
30+
await SolidAuthClient.fetch(location)
31+
// Now that we have a cookie, reload to display the authenticated page
32+
location.reload()
33+
}
34+
}
35+
36+
function register() {
37+
const registration = new URL('/register', location);
38+
registration.searchParams.set('returnToUrl', location);
39+
location.href = registration;
40+
}
41+
</script>
42+
</body>
43+
</html>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>No permission</title>
7+
<link rel="stylesheet" href="/common/css/bootstrap.min.css">
8+
</head>
9+
<body>
10+
<div class="container">
11+
<div class="jumbotron">
12+
<h2>No permission to access this resource</h2>
13+
<p>
14+
You are currently logged in as <code>{{webId}}</code>,
15+
but do not have permission to access <code>{{currentUrl}}</code>.
16+
</p>
17+
</div>
18+
</div>
19+
</body>
20+
</html>

default-views/auth/select-provider.hbs

Lines changed: 0 additions & 41 deletions
This file was deleted.

lib/api/authn/webid-oidc.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ const PasswordChangeRequest = require('../../requests/password-change-request')
1313

1414
const {
1515
AuthCallbackRequest,
16-
LogoutRequest,
17-
SelectProviderRequest
16+
LogoutRequest
1817
} = require('oidc-auth-manager').handlers
1918

2019
/**
@@ -67,9 +66,6 @@ function middleware (oidc) {
6766
const router = express.Router('/')
6867

6968
// User-facing Authentication API
70-
router.get('/api/auth/select-provider', SelectProviderRequest.get)
71-
router.post('/api/auth/select-provider', bodyParser, SelectProviderRequest.post)
72-
7369
router.get(['/login', '/signin'], LoginRequest.get)
7470

7571
router.post('/login/password', bodyParser, LoginRequest.loginPassword)
@@ -90,6 +86,14 @@ function middleware (oidc) {
9086
// The relying party callback is called at the end of the OIDC signin process
9187
router.get('/api/oidc/rp/:issuer_id', AuthCallbackRequest.get)
9288

89+
// Static assets related to authentication
90+
const authAssets = [
91+
['/common/', 'solid-auth-client/dist-popup/popup.html'],
92+
['/common/js/', 'solid-auth-client/dist-lib/solid-auth-client.bundle.js'],
93+
['/common/js/', 'solid-auth-client/dist-lib/solid-auth-client.bundle.js.map']
94+
]
95+
authAssets.map(([path, file]) => routeResolvedFile(router, path, file))
96+
9397
// Initialize the OIDC Identity Provider routes/api
9498
// router.get('/.well-known/openid-configuration', discover.bind(provider))
9599
// router.get('/jwks', jwks.bind(provider))
@@ -174,6 +178,15 @@ function isEmptyToken (req) {
174178
return false
175179
}
176180

181+
/**
182+
* Adds a route that serves a static file from another Node module
183+
*/
184+
function routeResolvedFile (router, path, file) {
185+
const fullPath = path + file.match(/[^/]+$/)
186+
const fullFile = require.resolve(file)
187+
router.get(fullPath, (req, res) => res.sendFile(fullFile))
188+
}
189+
177190
module.exports = {
178191
initialize,
179192
isEmptyToken,

lib/handlers/error-pages.js

Lines changed: 32 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ const fs = require('fs')
33
const util = require('../utils')
44
const Auth = require('../api/authn')
55

6-
// Authentication methods that require a Provider Select page
7-
const SELECT_PROVIDER_AUTH_METHODS = ['oidc']
8-
96
/**
107
* Serves as a last-stop error handler for all other middleware.
118
*
@@ -28,23 +25,21 @@ function handler (err, req, res, next) {
2825
return ldp.errorHandler(err, req, res, next)
2926
}
3027

31-
let statusCode = statusCodeFor(err, req, authMethod)
32-
33-
if (statusCode === 401) {
34-
debug(err, 'error:', err.error, 'desc:', err.error_description)
35-
setAuthenticateHeader(req, res, err)
36-
}
37-
38-
if (requiresSelectProvider(authMethod, statusCode, req)) {
39-
return redirectToSelectProvider(req, res)
40-
}
41-
42-
// If noErrorPages is set,
43-
// then return the response directly
44-
if (ldp.noErrorPages) {
45-
sendErrorResponse(statusCode, res, err)
46-
} else {
47-
sendErrorPage(statusCode, res, err, ldp)
28+
const statusCode = statusCodeFor(err, req, authMethod)
29+
switch (statusCode) {
30+
case 401:
31+
setAuthenticateHeader(req, res, err)
32+
renderLoginRequired(req, res)
33+
break
34+
case 403:
35+
renderNoPermission(req, res)
36+
break
37+
default:
38+
if (ldp.noErrorPages) {
39+
sendErrorResponse(statusCode, res, err)
40+
} else {
41+
sendErrorPage(statusCode, res, err, ldp)
42+
}
4843
}
4944
}
5045

@@ -67,26 +62,6 @@ function statusCodeFor (err, req, authMethod) {
6762
return statusCode
6863
}
6964

70-
/**
71-
* Tests whether a given authentication method requires a Select Provider
72-
* page redirect for 401 error responses.
73-
*
74-
* @param authMethod {string}
75-
* @param statusCode {number}
76-
* @param req {IncomingRequest}
77-
*
78-
* @returns {boolean}
79-
*/
80-
function requiresSelectProvider (authMethod, statusCode, req) {
81-
if (statusCode !== 401) { return false }
82-
83-
if (!SELECT_PROVIDER_AUTH_METHODS.includes(authMethod)) { return false }
84-
85-
if (!req.accepts('text/html')) { return false }
86-
87-
return true
88-
}
89-
9065
/**
9166
* Dispatches the writing of the `WWW-Authenticate` response header (used for
9267
* 401 Unauthorized responses).
@@ -150,26 +125,30 @@ function sendErrorPage (statusCode, res, err, ldp) {
150125
}
151126

152127
/**
153-
* Sends a 401 response with an HTML http-equiv type redirect body, to
154-
* redirect any users requesting a resource directly in the browser to the
155-
* Select Provider page and login workflow.
156-
* Implemented as a 401 + redirect body instead of a 302 to provide a useful
157-
* 401 response to REST/XHR clients.
128+
* Renders a 401 response explaining that a login is required.
158129
*
159130
* @param req {IncomingRequest}
160131
* @param res {ServerResponse}
161132
*/
162-
function redirectToSelectProvider (req, res) {
133+
function renderLoginRequired (req, res) {
134+
const currentUrl = util.fullUrlForReq(req)
135+
debug(`Display login-required for ${currentUrl}`)
163136
res.status(401)
164-
res.header('Content-Type', 'text/html')
137+
res.render('auth/login-required', { currentUrl })
138+
}
165139

166-
const locals = req.app.locals
140+
/**
141+
* Renders a 403 response explaining that the user has no permission.
142+
*
143+
* @param req {IncomingRequest}
144+
* @param res {ServerResponse}
145+
*/
146+
function renderNoPermission (req, res) {
167147
const currentUrl = util.fullUrlForReq(req)
168-
const loginUrl = `${locals.host.serverUri}/api/auth/select-provider?returnToUrl=${encodeURIComponent(currentUrl)}`
169-
debug('Redirecting to Select Provider: ' + loginUrl)
170-
171-
const body = redirectBody(loginUrl)
172-
res.send(body)
148+
const webId = req.session.userId
149+
debug(`Display no-permission for ${currentUrl}`)
150+
res.status(403)
151+
res.render('auth/no-permission', { currentUrl, webId })
173152
}
174153

175154
/**
@@ -199,8 +178,6 @@ follow the <a href='${url}'>link to login</a>
199178
module.exports = {
200179
handler,
201180
redirectBody,
202-
redirectToSelectProvider,
203-
requiresSelectProvider,
204181
sendErrorPage,
205182
sendErrorResponse,
206183
setAuthenticateHeader

0 commit comments

Comments
 (0)