Skip to content

Commit

Permalink
implement agent connect authentication (#282)
Browse files Browse the repository at this point in the history
  • Loading branch information
BenoitSerrano authored Oct 9, 2024
2 parents 4d52df0 + e8440e8 commit 0331514
Show file tree
Hide file tree
Showing 26 changed files with 186 additions and 440 deletions.
1 change: 0 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ AFTER_MEETING_SURVEY_URL=https://startupdetat.typeform.com/to/CQhQfpVU
ENCRYPT_SECRET=un_secret_avec_exactement_64_bit

# Use OIDC auth instead of audioconf's magiclink auth
#FEATURE_OIDC=true
#OIDC_PROVIDER_URL=
#OIDC_CLIENT_ID=
#OIDC_CLIENT_SECRET=
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ jobs:
SECRET: fakesessionsecret
ENCRYPT_SECRET: un_secret_avec_exactement_64_bit
FEATURE_WEB_ACCESS: true
OIDC_ACR_VALUES: eidas1
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
AFTER_MEETING_SURVEY_URL: https://startupdetat.typeform.com/to/R5uC1b0k
4 changes: 3 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ config.FEATURE_JOB_COMPUTE_STATS = process.env.FEATURE_JOB_COMPUTE_STATS === "tr
config.FEATURE_JOB_ANONYMIZE_EMAILS = process.env.FEATURE_JOB_ANONYMIZE_EMAILS === "true" || false
config.FEATURE_JOB_CALLS_STATS = process.env.FEATURE_JOB_CALLS_STATS === "true" || false
config.FEATURE_WEB_ACCESS = process.env.FEATURE_WEB_ACCESS === "true" || false
config.FEATURE_OIDC = process.env.FEATURE_OIDC === "true" || false

config.ANNOUNCEMENTS = process.env.ANNOUNCEMENTS ? process.env.ANNOUNCEMENTS.split("|") : []

Expand All @@ -112,6 +111,9 @@ config.STATS_EXTERNAL_DASHBOARD_URL = process.env.STATS_EXTERNAL_DASHBOARD_URL
config.OIDC_PROVIDER_URL = process.env.OIDC_PROVIDER_URL
config.OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID
config.OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET
config.OIDC_ACR_VALUES = process.env.OIDC_ACR_VALUES
config.OIDC_ID_TOKEN_SIGNED_ALG = process.env.OIDC_ID_TOKEN_SIGNED_ALG
config.OIDC_USER_INFO_SIGNED_ALG = process.env.OIDC_USER_INFO_SIGNED_ALG

config.RIZOMO_URI = process.env.RIZOMO_URI

Expand Down
7 changes: 2 additions & 5 deletions controllers/createConfController.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const config = require("../config")
const db = require("../lib/db")
const emailer = require("../lib/emailer")
const format = require("../lib/format")
const magicLinkAuth = require("../lib/magicLinkAuth")
const oidcAuth = require("../lib/oidcAuth")
const urls = require("../urls")
const { isAcceptedEmail } = require("../lib/emailChecker")
Expand Down Expand Up @@ -45,9 +44,7 @@ const createConfWithDay = async (email, conferenceDay, userTimezoneOffset) => {
}

module.exports.createConf = async (req, res) => {
const confData = await (config.FEATURE_OIDC ?
oidcAuth.finishAuth(req) :
magicLinkAuth.finishAuth(req))
const confData = await oidcAuth.finishAuth(req)

const { email, durationInMinutes, conferenceDay, userTimezoneOffset } = confData

Expand Down Expand Up @@ -146,4 +143,4 @@ module.exports.cancelConf = async (req, res) => {

function shouldSendWebAccessMail(email) {
return isAcceptedEmail(email, config.EMAIL_WEB_ACCESS_WHITELIST) && config.FEATURE_WEB_ACCESS
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
const config = require("../config.js")
const magicLinkAuth = require("../lib/magicLinkAuth")
const oidcAuth = require("../lib/oidcAuth")

const urls = require("../urls")

module.exports.startAuth = async (req, res) => {
const userTimezoneOffset = req.body.userTimezoneOffset
const email = req.body.email
const conferenceDurationInMinutes = req.body.durationInMinutes
const conferenceDayString = req.body.day
if (typeof conferenceDayString === 'undefined' && typeof conferenceDurationInMinutes === 'undefined') {
throw new Error('Both conferenceDayString and conferenceDurationInMinutes are undefined. This should not happen.')
}

const authRequest = await (
config.FEATURE_OIDC ?
oidcAuth.startAuth(email, conferenceDurationInMinutes, conferenceDayString, userTimezoneOffset) :
magicLinkAuth.startAuth(email, conferenceDurationInMinutes, conferenceDayString, userTimezoneOffset)
)
const authRequest = await oidcAuth.startAuth(conferenceDurationInMinutes, conferenceDayString, userTimezoneOffset)

if (authRequest.error) {
console.log("Error in authentication", authRequest.error)
Expand All @@ -26,3 +19,15 @@ module.exports.startAuth = async (req, res) => {

res.redirect(authRequest.redirectUrl)
}

module.exports.logout = async (req, res) => {
const user = req.session.user
if(!user){
return res.redirect(urls.landing)
}
const {id_token, state} = user
req.session.destroy()

const logoutUrl = await oidcAuth.getLogoutUrl({id_token_hint: id_token, state})
return res.redirect(logoutUrl)
}
8 changes: 6 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const dashboardController = require("./controllers/dashboardController")
const format = require("./lib/format")
const createConfController = require("./controllers/createConfController")
const landingController = require("./controllers/landingController")
const startAuthController = require("./controllers/startAuthController")
const userController = require("./controllers/userController")
const statusController = require("./controllers/statusController")
const stats = require("./lib/stats")
const urls = require("./urls")
Expand Down Expand Up @@ -70,12 +70,14 @@ app.use(function(req, res, next){
res.locals.urls = urls
res.locals.version = version
res.locals.siteUrl = config.HOSTNAME_WITH_PROTOCOL
res.locals.user = req.session.user
next()
})

app.get(urls.landing, landingController.getLanding)

app.post(urls.startAuth, startAuthController.startAuth)
app.post(urls.startAuth, userController.startAuth)
app.post(urls.logout, userController.logout)

app.get(urls.validationEmailSent, (req, res) => {
res.render("validationEmailSent", {
Expand Down Expand Up @@ -139,6 +141,8 @@ app.get(urls.faq, (req, res) => {
})
})

app.get(urls.logout, userController.logout)

app.get(urls.status, statusController.getStatus)

app.use(Sentry.Handlers.errorHandler())
Expand Down
84 changes: 0 additions & 84 deletions lib/magicLinkAuth.js

This file was deleted.

58 changes: 40 additions & 18 deletions lib/oidcAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const { generators, Issuer } = require("openid-client")
const config = require("../config.js")
const urls = require("../urls")
const db = require("../lib/db")
const format = require("../lib/format")

const urlCallback = urls.createConf

Expand All @@ -24,14 +23,15 @@ module.exports.getClient = async () => {
client_secret: config.OIDC_CLIENT_SECRET,
redirect_uris: [config.HOSTNAME_WITH_PROTOCOL + urlCallback],
response_types: ["code"],
// id_token_signed_response_alg (default "RS256")
// token_endpoint_auth_method (default "client_secret_basic")
id_token_signed_response_alg: config.OIDC_ID_TOKEN_SIGNED_ALG,
userinfo_signed_response_alg: config.OIDC_USER_INFO_SIGNED_ALG,
// token_endpoint_auth_method (default "client_secret_basic")
})

return client
}

module.exports.startAuth = async (email, conferenceDurationInMinutes, conferenceDayString, userTimezoneOffset) => {
module.exports.startAuth = async (conferenceDurationInMinutes, conferenceDayString, userTimezoneOffset) => {
const client = await this.getClient()

/* todo : store the code_verifier in DB. We don't use it for now.
Expand All @@ -49,20 +49,21 @@ module.exports.startAuth = async (email, conferenceDurationInMinutes, conference
const nonce = generators.random(128)

const redirectUrl = client.authorizationUrl({
scope: "openid",
scope: "openid uid email",
state,
acr_values: config.OIDC_ACR_VALUES,
/* todo add this back
code_challenge,
code_challenge_method: 'S256',
*/
nonce,
login_hint: email
// login_hint: email
})

// todo write test : null nonce fails
try {
await db.insertOidcRequest(state, nonce, conferenceDurationInMinutes, conferenceDayString, userTimezoneOffset)
console.log(`OIDC request créé pour ${format.hashForLogs(email)}`)
console.log(`OIDC request créé pour state ${state}`)
} catch(err) {
console.log("Error when inserting authrequest token in DB", err)
return { error: "Une erreur interne s'est produite, nous n'avons pas pu créer votre conférence." }
Expand Down Expand Up @@ -94,18 +95,34 @@ module.exports.finishAuth = async (req) => {
return { error: "L'identification a échoué. Entrez votre adresse mail ci-dessous pour recommencer." }
}

const tokenSet = await client.callback(
config.HOSTNAME_WITH_PROTOCOL + urlCallback,
params,
{
state: request.state,
nonce: request.nonce
// todo code_verifier: req.session.code_verifier
}
)
const claims = tokenSet.claims()
const email = claims.preferred_username
let tokenSet
try {
tokenSet = await client.callback(
config.HOSTNAME_WITH_PROTOCOL + urlCallback,
params,
{
state: request.state,
nonce: request.nonce
// todo code_verifier: req.session.code_verifier
}
)
} catch(error){
console.error("error when requesting token from OIDC", error)
return { error: "L'identification a échoué. Entrez votre adresse mail ci-dessous pour recommencer." }
}

let userinfo
try {
userinfo = await client.userinfo(tokenSet)
} catch(error){
console.error("error when requesting userinfo from OIDC", error)
return { error: "L'identification a échoué. Entrez votre adresse mail ci-dessous pour recommencer." }
}
const email = userinfo.email
const user = {id_token: tokenSet.id_token, state: request.state}

req.session.user = user

return {
email,
durationInMinutes: request.durationInMinutes,
Expand All @@ -114,3 +131,8 @@ module.exports.finishAuth = async (req) => {
}
}

module.exports.getLogoutUrl = async({state, id_token_hint}) => {
const client = await this.getClient()

return client.endSessionUrl({id_token_hint,post_logout_redirect_uri: `${config.HOSTNAME_WITH_PROTOCOL}${urls.landing}`,state})
}
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
},
"homepage": "https://github.com/betagouv/conferences#readme",
"dependencies": {
"@gouvfr/dsfr": "^1.0.0-rc1.0",
"@gouvfr/dsfr": "^1.11.2",
"@sentry/node": "^6.2.2",
"chart.js": "^2.9.4",
"connect-flash": "^0.1.1",
Expand Down
Loading

0 comments on commit 0331514

Please sign in to comment.