Skip to content

Commit

Permalink
include registrationInfo in new users's first authorization response;…
Browse files Browse the repository at this point in the history
… optionally include email in that info wth passEmailToHub setting
  • Loading branch information
wmurphyrd committed Dec 20, 2022
1 parent 5a7f2bf commit 33d4f97
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 9 deletions.
1 change: 1 addition & 0 deletions .env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ maxUploadSize=20
monetizationPointer=
port=8081
[email protected]
passEmailToHub=false
emailOptInURL=
emailOptInParam=
emailOptInNameParam=
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Unreleased

### Added

* When new users register on your immer, some additional data is now returned in the authorization response to your site: `isNewUser`, `provider` (their login provider if using OIDC else `email`). With immers-client >=2.14.0, this will be available from `immersClient.sessionInfo` after login ([reference](https://immers-space.github.io/immers-client/ImmersClient.html#sessionInfo)).
* `passEmailToHub` environment config, when `true` the cleartext email is included with the above sessionInfo, and the email data safety message is omitted from the registration page.

### Fixed

* Fix Facebook OIDC by dropping request for currently unused `profile` scope from all OIDC authorization requests
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ imageAttributionUrl | Attribution for backgroundImage, if needed | https://www.v
maxUploadSize | Limit on media upload file size in Mb | 20
monetizationPointer | [Payment pointer](https://webmonetization.org/docs/ilp-wallets/#payment-pointers) for Web Monetization on login & profile pages | Immers Space organization wallet
port | Port number for immers sever | 8081
smtpFrom | From address for emails | noreplay@mail.`domain`
smtpFrom | From address for emails | noreply@mail.`domain`
passEmailToHub | For apps that depend on user emails, this option will include the cleartext e-mail address in the initial token response (as an additional hash parameter named `email`) to the hub on registration so it can be saved and associated with the profile | `false`
emailOptInURL | Link to an opt-in form for email updates to show on registration page | None
emailOptInParam | Query parameter for `emailOptInURL` for the e-mail address | Use opt-in url without inserting e-mail
emailOptInNameParam | Query parameter for `emailOptInURL` for the name | Use opt-in url without inserting name
Expand Down
6 changes: 5 additions & 1 deletion src/auth/authdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ const authdb = {
const expiry = new Date(Date.now() + tokenAge)
await db.collection('tokens')
.insertOne({ token, user, clientId, expiry, tokenType, origin: ares.origin, scope: ares.scope })
return done(null, token, { token_type: tokenType, issuer: ares.issuer, scope: ares.scope.join(' ') })
const responseParams = { token_type: tokenType, issuer: ares.issuer, scope: ares.scope.join(' ') }
if (ares.registrationInfo) {
Object.assign(responseParams, ares.registrationInfo)
}
return done(null, token, responseParams)
} catch (err) { done(err) }
},
async validateAccessToken (token, done) {
Expand Down
26 changes: 22 additions & 4 deletions src/auth/oauthServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
authorization: [
stashHandle,
login.ensureLoggedIn('/auth/login'),
sessionToLocals,
server.authorization(authdb.validateClient, checkIfAuthorizationDialogNeeded),
renderAuthorizationDialog
],
Expand Down Expand Up @@ -95,15 +96,22 @@ async function registerClient (req, res, next) {
return res.json(client)
}

function checkIfAuthorizationDialogNeeded (client, user, scope, type, req, done) {
function checkIfAuthorizationDialogNeeded (client, user, scope, type, authRequest, locals, done) {
// Auto-approve for home immer
if (client.isTrusted) {
const params = {}
const origin = new URL(req.redirectURI)
const origin = new URL(authRequest.redirectURI)
params.origin = `${origin.protocol}//${origin.host}`
// express protocol does not include colon
params.issuer = `https://${domain}`
params.scope = ['*']
/**
* Can pass additional info to its own hub on user registration,
* added as params in auth response, parsed to client.sessionInfo in ImmersClient
*/
if (client.clientId === `https://${domain}/o/immer`) {
// registrationInfo set in resourceServer->registerUser
params.registrationInfo = locals.registrationInfo
}
return done(null, true, params)
}
// Otherwise ask user
Expand All @@ -125,7 +133,6 @@ function determineTokenParams (req, done) {
const params = {}
const origin = new URL(req.oauth2.redirectURI)
params.origin = `${origin.protocol}//${origin.host}`
// express protocol does not include colon
params.issuer = `https://${domain}`
params.scope = req.body.scope?.split(' ') || []
done(null, params)
Expand All @@ -143,3 +150,14 @@ function stashHandle (req, res, next) {
}
next()
}

/**
* oauth2orize doesn't allow access to raw request or session in callbacks,
* but it does allow access to request.locals
*/
function sessionToLocals (req, res, next) {
req.locals ??= {}
req.locals.registrationInfo = req.session.registrationInfo
delete req.session.registrationInfo
next()
}
9 changes: 9 additions & 0 deletions src/auth/resourceServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const {
domain,
name,
hubs,
passEmailToHub,
smtpHost,
smtpPort,
smtpFrom,
Expand Down Expand Up @@ -389,6 +390,14 @@ async function registerUser (req, res, next) {
if (!user) {
throw new Error('Unable to create user')
}
req.session.registrationInfo = {
isNewUser: true,
provider: oidcProviders?.[0] || 'email'
}
if (passEmailToHub) {
// temporarily save cleartext email in session, it will be deleted and passed to hub with the authorization response
req.session.registrationInfo.email = email
}
req.login(user, next)
} catch (err) { next(err) }
}
Expand Down
5 changes: 4 additions & 1 deletion src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const {
maxUploadSize,
monetizationPointer,
name,
passEmailToHub,
port,
proxyMode,
sessionSecret,
Expand Down Expand Up @@ -85,6 +86,7 @@ const appSettings = {
monetizationPointer,
mongoURI,
name,
passEmailToHub: passEmailToHub === 'true',
port,
proxyMode: parseProxyMode(proxyMode),
sessionSecret,
Expand Down Expand Up @@ -116,7 +118,8 @@ const renderConfig = {
imageAttributionText,
imageAttributionUrl,
emailOptInURL,
enablePublicRegistration
enablePublicRegistration,
passEmailToHub: appSettings.passEmailToHub
}

const isTrue = (settingName) => {
Expand Down
4 changes: 2 additions & 2 deletions views/components/EmailOptIn.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'

export default function EmailOptIn () {
const { emailOptInURL } = window._serverData
const { emailOptInURL, passEmailToHub } = window._serverData
const onClick = event => {
event.preventDefault()
const search = new URLSearchParams()
Expand All @@ -17,7 +17,7 @@ export default function EmailOptIn () {
}
return (
<p className='form-footer'>
We don't save your e-mail, just an encrypted hash of it for password resets.{' '}
{!passEmailToHub && <span>We don't save your e-mail, just an encrypted hash of it for password resets.</span>}{' '}
{emailOptInURL && (<span><a href={emailOptInURL} onClick={onClick}>Click here</a> to opt-in to our e-mail contact list.</span>)}
</p>
)
Expand Down
1 change: 1 addition & 0 deletions views/layout.njk
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
transactionId: '{{ transactionId }}',
loggedInUser: '{{ loggedInUser }}',
preferredScope: '{{ preferredScope }}'.split(' '),
passEmailToHub: {{ passEmailToHub }},
emailOptInURL: '{{ emailOptInURL }}',
loginTab: '{{ loginTab }}',
enablePublicRegistration: '{{ enablePublicRegistration }}',
Expand Down

0 comments on commit 33d4f97

Please sign in to comment.