Skip to content

Commit c89f6cb

Browse files
committed
Consent screen for allowing apps to access data
1 parent c2f3531 commit c89f6cb

File tree

6 files changed

+176
-26
lines changed

6 files changed

+176
-26
lines changed

default-views/auth/consent.hbs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,21 @@
1111
<body>
1212
<div class="container title">
1313
<div class="page-title">
14-
<h1>Authorize app to use your Web ID?</h1>
14+
<h1>Authorize this app to use your data?</h1>
15+
<p>You will be authorizing <strong>{{app_origin}}</strong> to have access perform the actions indicated below.</p>
16+
<p>NOTE: This screen is TEMPORARY. Eventually more fine-tuned controls will be available.</p>
1517
</div>
1618

17-
<form method="post" action="/authorize">
18-
<input type="hidden" name="response_type" value="{{response_type}}"/>
19-
<input type="hidden" name="display" value="{{display}}"/>
20-
<input type="hidden" name="scope" value="{{scope}}"/>
21-
<input type="hidden" name="client_id" value="{{client_id}}"/>
22-
<input type="hidden" name="redirect_uri" value="{{redirect_uri}}"/>
23-
<input type="hidden" name="state" value="{{state}}"/>
24-
<input type="hidden" name="nonce" value="{{nonce}}"/>
19+
<form method="post" action="/consent">
20+
21+
<input type="checkbox" name="read" value="access_mode" checked> Read your data<br>
22+
<input type="checkbox" name="write" value="access_mode" checked> Write new data<br>
23+
<input type="checkbox" name="append" value="access_mode" checked> Add to existing data<br>
24+
<input type="checkbox" name="control" value="access_mode"> Control who can access your data<br>
2525

2626
<button type="submit" class="btn btn-primary" name="consent" value="true">Authorize</button>
2727
<button type="submit" class="btn btn-default" name="cancel" value="true">Cancel</button>
28+
{{> auth/auth-hidden-fields}}
2829
</form>
2930
</div>
3031
</body>

lib/api/authn/webid-oidc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const { routeResolvedFile } = require('../../utils')
88
const bodyParser = require('body-parser').urlencoded({ extended: false })
99
const OidcManager = require('../../models/oidc-manager')
1010
const { LoginRequest } = require('../../requests/login-request')
11+
const { ConsentRequest } = require('../../requests/consent-request')
1112

1213
const restrictToTopDomain = require('../../handlers/restrict-to-top-domain')
1314

@@ -83,6 +84,9 @@ function middleware (oidc) {
8384

8485
router.post('/login/tls', bodyParser, LoginRequest.loginTls)
8586

87+
router.get('/consent', ConsentRequest.get)
88+
router.post('/consent', ConsentRequest.giveConsent)
89+
8690
router.get('/account/password/reset', restrictToTopDomain, PasswordResetEmailRequest.get)
8791
router.post('/account/password/reset', restrictToTopDomain, bodyParser, PasswordResetEmailRequest.post)
8892

lib/requests/auth-request.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ class AuthRequest {
210210

211211
return url.format(signupUrl)
212212
}
213+
214+
consentUrl () {
215+
let host = this.accountManager.host
216+
let consentUrl = url.parse(url.resolve(host.serverUri, '/consent'))
217+
218+
consentUrl.query = this.authQueryParams
219+
220+
return url.format(consentUrl)
221+
}
213222
}
214223

215224
AuthRequest.AUTH_QUERY_PARAMS = AUTH_QUERY_PARAMS

lib/requests/consent-request.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
'use strict'
2+
3+
const debug = require('./../debug').authentication
4+
5+
const AuthRequest = require('./auth-request')
6+
7+
/**
8+
* Models a local Login request
9+
*/
10+
class ConsentRequest extends AuthRequest {
11+
/**
12+
* @constructor
13+
* @param options {Object}
14+
*
15+
* @param [options.response] {ServerResponse} middleware `res` object
16+
* @param [options.session] {Session} req.session
17+
* @param [options.userStore] {UserStore}
18+
* @param [options.accountManager] {AccountManager}
19+
* @param [options.returnToUrl] {string}
20+
* @param [options.authQueryParams] {Object} Key/value hashmap of parsed query
21+
* parameters that will be passed through to the /authorize endpoint.
22+
* @param [options.authenticator] {Authenticator} Auth strategy by which to
23+
* log in
24+
*/
25+
constructor (options) {
26+
super(options)
27+
28+
this.authenticator = options.authenticator
29+
this.authMethod = options.authMethod
30+
}
31+
32+
/**
33+
* Factory method, returns an initialized instance of LoginRequest
34+
* from an incoming http request.
35+
*
36+
* @param req {IncomingRequest}
37+
* @param res {ServerResponse}
38+
* @param authMethod {string}
39+
*
40+
* @return {LoginRequest}
41+
*/
42+
static fromParams (req, res) {
43+
let options = AuthRequest.requestOptions(req, res)
44+
45+
return new ConsentRequest(options)
46+
}
47+
48+
/**
49+
* Handles a Login GET request on behalf of a middleware handler, displays
50+
* the Login page.
51+
* Usage:
52+
*
53+
* ```
54+
* app.get('/login', LoginRequest.get)
55+
* ```
56+
*
57+
* @param req {IncomingRequest}
58+
* @param res {ServerResponse}
59+
*/
60+
static get (req, res) {
61+
const request = ConsentRequest.fromParams(req, res)
62+
63+
request.renderForm(null, req)
64+
}
65+
66+
/**
67+
* Performs the login operation -- loads and validates the
68+
* appropriate user, inits the session with credentials, and redirects the
69+
* user to continue their auth flow.
70+
*
71+
* @param request {LoginRequest}
72+
*
73+
* @return {Promise}
74+
*/
75+
static giveConsent (req, res) {
76+
let request = ConsentRequest.fromParams(req, res)
77+
console.log(request.authQueryParams)
78+
// debug('Providing consent for app sharing')
79+
// return request.authenticator.findValidUser()
80+
81+
// .then(validUser => {
82+
// request.initUserSession(validUser)
83+
84+
// request.redirectPostLogin(validUser)
85+
// })
86+
87+
// .catch(error => request.error(error))
88+
}
89+
90+
/**
91+
* Returns a URL to redirect the user to after login.
92+
* Either uses the provided `redirect_uri` auth query param, or simply
93+
* returns the user profile URI if none was provided.
94+
*
95+
* @param validUser {UserAccount}
96+
*
97+
* @return {string}
98+
*/
99+
postConsentUrl (validUser) {
100+
return this.authorizeUrl()
101+
}
102+
103+
/**
104+
* Redirects the Login request to continue on the OIDC auth workflow.
105+
*/
106+
redirectPostLogin (validUser) {
107+
let uri = this.postLoginUrl(validUser)
108+
debug('Login successful, redirecting to ', uri)
109+
this.response.redirect(uri)
110+
}
111+
112+
/**
113+
* Renders the login form
114+
*/
115+
renderForm (error, req) {
116+
let queryString = req && req.url && req.url.replace(/[^?]+\?/, '') || ''
117+
let params = Object.assign({}, this.authQueryParams,
118+
{
119+
registerUrl: this.registerUrl(),
120+
returnToUrl: this.returnToUrl,
121+
enablePassword: this.localAuth.password,
122+
enableTls: this.localAuth.tls,
123+
tlsUrl: `/login/tls?${encodeURIComponent(queryString)}`
124+
})
125+
126+
if (error) {
127+
params.error = error.message
128+
this.response.status(error.statusCode)
129+
}
130+
131+
this.response.render('auth/consent', params)
132+
}
133+
}
134+
135+
module.exports = {
136+
ConsentRequest
137+
}

lib/requests/login-request.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ class LoginRequest extends AuthRequest {
159159
postLoginUrl (validUser) {
160160
// Login request is part of an app's auth flow
161161
if (/token|code/.test(this.authQueryParams['response_type'])) {
162-
return this.authorizeUrl()
162+
return this.consentUrl()
163163
// Login request is a user going to /login in browser
164164
} else if (validUser) {
165165
return this.authQueryParams['redirect_uri'] || validUser.accountUri

test/resources/accounts-acl/config/views/auth/consent.hbs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,26 @@
66
<title>{{title}}</title>
77
<!-- Bootstrap CSS and Theme for demo purposes -->
88
<link rel="stylesheet" href="/common/css/bootstrap.min.css">
9-
<style>
10-
.title { margin-top: 2em; margin-bottom: 2em; }
11-
#authorize { margin-right: 2em; }
12-
</style>
9+
<link rel="stylesheet" href="/common/css/solid.css">
1310
</head>
1411
<body>
1512
<div class="container title">
16-
<h4>Authorize app to use your Web ID?</h4>
17-
</div>
18-
<div class="container">
19-
<form method="post" action="/authorize">
20-
<input type="hidden" name="response_type" value="{{response_type}}" />
21-
<input type="hidden" name="display" value="{{display}}" />
22-
<input type="hidden" name="scope" value="{{scope}}" />
23-
<input type="hidden" name="client_id" value="{{client_id}}" />
24-
<input type="hidden" name="redirect_uri" value="{{redirect_uri}}" />
25-
<input type="hidden" name="state" value="{{state}}" />
26-
<input type="hidden" name="nonce" value="{{nonce}}" />
13+
<div class="page-title">
14+
<h1>Authorize this app to use your data?</h1>
15+
<p>You will be authorizing <strong>{{app_origin}}</strong> to have access perform the actions indicated below.</p>
16+
<p>NOTE: This screen is TEMPORARY. Eventually more fine-tuned controls will be available.</p>
17+
</div>
18+
19+
<form method="post" action="/consent">
20+
21+
<input type="checkbox" name="read" value="access_mode" checked> Read your data<br>
22+
<input type="checkbox" name="write" value="access_mode" checked> Write new data<br>
23+
<input type="checkbox" name="append" value="access_mode" checked> Add to existing data<br>
24+
<input type="checkbox" name="control" value="access_mode"> Control who can access your data<br>
2725

2826
<button type="submit" class="btn btn-primary" name="consent" value="true">Authorize</button>
29-
<button type="submit" class="btn" name="cancel" value="true">Cancel</button>
27+
<button type="submit" class="btn btn-default" name="cancel" value="true">Cancel</button>
28+
{{> auth/auth-hidden-fields}}
3029
</form>
3130
</div>
3231
</body>

0 commit comments

Comments
 (0)