Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MEP 11/10/2024 #287

Merged
merged 25 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4b3240a
upgrade DSFR
BenoitSerrano May 23, 2024
bc20e90
add bandeau information
BenoitSerrano May 23, 2024
880cb48
change date MEP
BenoitSerrano May 23, 2024
fdbfaf6
add acr_values and alg params
BenoitSerrano Apr 29, 2024
b410e83
add uid and email to scope
BenoitSerrano Apr 29, 2024
c87410b
do not send login hint
BenoitSerrano Apr 30, 2024
b007b00
add logs claims and userinfo
BenoitSerrano Apr 30, 2024
ceeb4a2
use email from user info
BenoitSerrano Apr 30, 2024
925528a
delete magic link implementation
BenoitSerrano May 23, 2024
b85964a
delete asking for mail adress in landing
BenoitSerrano May 23, 2024
c243e84
add button logout
BenoitSerrano May 28, 2024
1daf23e
add logout route implementation
BenoitSerrano May 28, 2024
afa3e4b
add await logout url
BenoitSerrano May 28, 2024
26631a2
add hostname to post logout redirect uri
BenoitSerrano May 28, 2024
ac43fcf
replace id_token with id_token_int
BenoitSerrano May 28, 2024
bd3cac0
delete logs
BenoitSerrano May 28, 2024
eed819a
add locals user
BenoitSerrano May 29, 2024
aed4e41
delete unused variable logout
BenoitSerrano May 29, 2024
e8440e8
add new scope and acr values in test
BenoitSerrano Oct 9, 2024
0331514
implement agent connect authentication (#282)
BenoitSerrano Oct 9, 2024
eec90f8
add whitelisting emails
BenoitSerrano Oct 9, 2024
a79657d
add error message
BenoitSerrano Oct 9, 2024
b9eb021
add whitelisting emails (#285)
BenoitSerrano Oct 9, 2024
ed6f266
add try catch if call to grist fails
BenoitSerrano Oct 11, 2024
393b962
add try catch if call to grist fails (#286)
BenoitSerrano Oct 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 5 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,20 @@ 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("|") : []

config.STATS_EXTERNAL_DASHBOARD_URL = process.env.STATS_EXTERNAL_DASHBOARD_URL


config.OIDC_PROVIDER_URL = process.env.OIDC_PROVIDER_URL
config.GRIST_API_KEY = process.env.GRIST_API_KEY
config.GRIST_API_DOMAINS_URL = process.env.GRIST_API_DOMAINS_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
12 changes: 4 additions & 8 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,15 +44,12 @@ 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
const { email, durationInMinutes, conferenceDay, userTimezoneOffset, error } = confData

if (!conferenceDay && !durationInMinutes) {
console.error("Login token contained no conferenceDay and no durationInMinutes. Cannot create conference.")
req.flash("error", "La conférence n'a pas pu être créée. Vous pouvez réessayer.")
req.flash("error", error || "La conférence n'a pas pu être créée. Vous pouvez réessayer.")
return res.redirect("/")
}

Expand Down Expand Up @@ -146,4 +142,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
12 changes: 12 additions & 0 deletions lib/domains.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const config = require("../config")

module.exports.getWhitelistedDomains = async () => {
const URL = config.GRIST_API_DOMAINS_URL
const API_KEY = config.GRIST_API_KEY
const result = await fetch(URL, {headers: {Authorization: `Bearer ${API_KEY}`}})
const data = await result.json()
if(data.status !== "success") {
throw new Error("Error while fetching GRIST API")
}
return data.items
}
14 changes: 14 additions & 0 deletions lib/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,17 @@ module.exports.formatLocalTime = function(date) {
return date.getHours() + ":" + minutes + ":" + seconds
}

module.exports.extractEmailDomain = function(email) {
// Expression régulière pour valider une adresse email
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

// Vérification si l'adresse email est valide
if (!emailRegex.test(email)) {
return undefined
}

// Extraction du domaine avec un split
const domain = email.split("@")[1];

return domain;
}
84 changes: 0 additions & 84 deletions lib/magicLinkAuth.js

This file was deleted.

68 changes: 52 additions & 16 deletions lib/oidcAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const config = require("../config.js")
const urls = require("../urls")
const db = require("../lib/db")
const format = require("../lib/format")
const { getWhitelistedDomains } = require("./domains.js")

const urlCallback = urls.createConf

Expand All @@ -24,14 +25,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 +51,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 +97,46 @@ 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
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

try {
const domain = format.extractEmailDomain(email)
const whitelistedDomains = await getWhitelistedDomains()
if(!domain || !whitelistedDomains.includes(domain)){
console.error(`The domain ${domain} is not whitelisted.`)
return { error: `L'adresse e-mail ${email} n'est pas autorisée à utiliser ce service. Si vous êtes agent de l'État, contactez-nous à [email protected]` }
}
)
const claims = tokenSet.claims()
const email = claims.preferred_username
} catch(e){
console.error(`error when validating email ${email}`,e)
}

const user = {id_token: tokenSet.id_token, state: request.state}

req.session.user = user

return {
email,
durationInMinutes: request.durationInMinutes,
Expand All @@ -114,3 +145,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})
}
Loading
Loading