Skip to content

Commit

Permalink
Merge pull request #697 from numerique-gouv/fix-to-many-reauth
Browse files Browse the repository at this point in the history
feat: login_hint from sp does not trigger login prompt
  • Loading branch information
rdubigny authored Sep 20, 2024
2 parents 436aafa + 73005cb commit 19c7581
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 22 deletions.
6 changes: 4 additions & 2 deletions cypress/e2e/signin_from_agentconnect_client/fixtures.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ INSERT INTO users
(id, email, email_verified, email_verified_at, encrypted_password, created_at, updated_at, given_name, family_name,
phone_number, job)
VALUES
(1, 'unused1@yopmail.com', true, CURRENT_TIMESTAMP, '$2a$10$kzY3LINL6..50Fy9shWCcuNlRfYq0ft5lS.KCcJ5PzrhlWfKK4NIO', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Jean', 'Jean', '0123456789', 'Sbire');
(1, 'unused1@yopmail.com', true, CURRENT_TIMESTAMP, '$2a$10$kzY3LINL6..50Fy9shWCcuNlRfYq0ft5lS.KCcJ5PzrhlWfKK4NIO', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Jean', 'Jean', '0123456789', 'Sbire'),
(2, 'unused2@yopmail.com', true, CURRENT_TIMESTAMP, '$2a$10$kzY3LINL6..50Fy9shWCcuNlRfYq0ft5lS.KCcJ5PzrhlWfKK4NIO', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Jean', 'Jean', '0123456789', 'Sbire');


INSERT INTO organizations
Expand All @@ -13,7 +14,8 @@ VALUES
INSERT INTO users_organizations
(user_id, organization_id, is_external, verification_type, has_been_greeted)
VALUES
(1, 1, false, 'domain', true);
(1, 1, false, 'domain', true),
(2, 1, false, 'domain', true);

INSERT INTO oidc_clients
(client_name, client_id, client_secret, redirect_uris,
Expand Down
29 changes: 28 additions & 1 deletion cypress/e2e/signin_from_agentconnect_client/index.cy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//

describe("sign-in from agentconnect client", () => {
it("should sign-in", function () {
it("should sign-in", () => {
cy.visit("http://localhost:4001");
cy.get("button.moncomptepro-button").click();

Expand All @@ -14,4 +14,31 @@ describe("sign-in from agentconnect client", () => {
cy.contains("unused1@yopmail.com");
cy.contains("21340126800130");
});

it("should not prompt for password if a session is already opened", () => {
cy.visit("/");
cy.login("unused1@yopmail.com");

cy.visit("http://localhost:4001");
cy.get("button.moncomptepro-button").click();

cy.contains("moncomptepro-agentconnect-client");
cy.contains("unused1@yopmail.com");
});

it("login_hint should take precedence over existing session", () => {
cy.visit("/");
cy.login("unused2@yopmail.com");

cy.visit("http://localhost:4001");
cy.get("button.moncomptepro-button").click();

cy.get('[name="password"]').type("password123");
cy.get('[action="/users/sign-in"] [type="submit"]')
.contains("S’identifier")
.click();

cy.contains("moncomptepro-agentconnect-client");
cy.contains("unused1@yopmail.com");
});
});
11 changes: 10 additions & 1 deletion cypress/e2e/signin_from_standard_client/index.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,14 @@ describe("sign-in from standard client", () => {
cy.contains("Commune de lamalou-les-bains - Mairie");
});

it("should prompt for organization selection", function () {});
it("should not prompt for password if a session is already opened", () => {
cy.visit("/");
cy.login("unused1@yopmail.com");

cy.visit("http://localhost:4000");
cy.get("button.moncomptepro-button").click();

cy.contains("moncomptepro-standard-client");
cy.contains("unused1@yopmail.com");
});
});
2 changes: 1 addition & 1 deletion src/config/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class WebauthnAuthenticationFailedError extends Error {}

export class UserNotLoggedInError extends Error {}

export class NoEmailFoundInLoggedOutSessionError extends Error {}
export class NoEmailFoundInUnauthenticatedSessionError extends Error {}

export class InvalidTotpTokenError extends Error {}

Expand Down
23 changes: 12 additions & 11 deletions src/controllers/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { ENABLE_FIXED_ACR } from "../config/env";
import {
getSessionStandardizedAuthenticationMethodsReferences,
getUserFromAuthenticatedSession,
isWithinAuthenticatedSession,
isWithinTwoFactorAuthenticatedSession,
} from "../managers/session/authenticated";
import { setEmailInUnauthenticatedSession } from "../managers/session/unauthenticated";
import { setLoginHintInUnauthenticatedSession } from "../managers/session/unauthenticated";
import epochTime from "../services/epoch-time";
import { mustReturnOneOrganizationInPayload } from "../services/must-return-one-organization-in-payload";
import { shouldTrigger2fa } from "../services/should-trigger-2fa";
import { postStartSignInController } from "./user/signin-signup";

export const interactionStartControllerFactory =
(oidcProvider: any) =>
Expand All @@ -29,20 +29,21 @@ export const interactionStartControllerFactory =
req.session.mustUse2FA = true;
}

if (prompt.name === "login" && prompt.reasons.includes("login_prompt")) {
if (login_hint) {
setEmailInUnauthenticatedSession(req, login_hint);
req.body.login = login_hint;
return postStartSignInController(req, res, next);
}
if (login_hint) {
setLoginHintInUnauthenticatedSession(req, login_hint);
}

if (prompt.name === "login" && prompt.reasons.includes("login_prompt")) {
return res.redirect(`/users/start-sign-in`);
}

if (prompt.name === "login" || prompt.name === "choose_organization") {
if (login_hint) {
req.body.login = login_hint;
return postStartSignInController(req, res, next);
if (
login_hint &&
isWithinAuthenticatedSession(req.session) &&
getUserFromAuthenticatedSession(req).email !== login_hint
) {
return res.redirect(`/users/start-sign-in`);
}

return res.redirect(`/interaction/${interactionId}/login`);
Expand Down
14 changes: 12 additions & 2 deletions src/controllers/user/signin-signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../../config/errors";
import { createAuthenticatedSession } from "../../managers/session/authenticated";
import {
getAndRemoveLoginHintFromUnauthenticatedSession,
getEmailFromUnauthenticatedSession,
setEmailInUnauthenticatedSession,
setPartialUserFromUnauthenticatedSession,
Expand All @@ -33,13 +34,22 @@ export const getStartSignInController = async (
next: NextFunction,
) => {
try {
// Bypass email submission when a login hint is provided in the interaction
const hintFromOidcInteraction =
getAndRemoveLoginHintFromUnauthenticatedSession(req);
if (hintFromOidcInteraction) {
setEmailInUnauthenticatedSession(req, hintFromOidcInteraction);
req.body.login = hintFromOidcInteraction;
return postStartSignInController(req, res, next);
}

const schema = z.object({
did_you_mean: z.string().trim().min(1).optional(),
});

const { did_you_mean: didYouMean } = await schema.parseAsync(req.query);

const loginHint = getEmailFromUnauthenticatedSession(req);
const hintFromSession = getEmailFromUnauthenticatedSession(req);

const hasEmailError =
(await getNotificationLabelFromRequest(req)) === "invalid_email";
Expand All @@ -49,7 +59,7 @@ export const getStartSignInController = async (
notifications: !hasEmailError && (await getNotificationsFromRequest(req)),
hasEmailError,
didYouMean,
loginHint,
loginHint: hintFromSession,
csrfToken: csrfToken(req),
displayTestEnvWarning: DISPLAY_TEST_ENV_WARNING,
});
Expand Down
19 changes: 17 additions & 2 deletions src/managers/session/unauthenticated.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import type { Request } from "express";
import { isEmpty } from "lodash-es";
import { NoEmailFoundInLoggedOutSessionError } from "../../config/errors";
import { NoEmailFoundInUnauthenticatedSessionError } from "../../config/errors";
import { findByEmail, update } from "../../repositories/user";

export const getAndRemoveLoginHintFromUnauthenticatedSession = (
req: Request,
) => {
const loginHint = req.session.loginHint;
delete req.session.loginHint;
return loginHint;
};
export const setLoginHintInUnauthenticatedSession = (
req: Request,
loginHint: string,
) => {
req.session.loginHint = loginHint;

return loginHint;
};
export const getEmailFromUnauthenticatedSession = (req: Request) => {
return req.session.email;
};
Expand Down Expand Up @@ -42,7 +57,7 @@ export const updatePartialUserFromUnauthenticatedSession = async (
needs_inclusionconnect_welcome_page: boolean;
}> => {
if (!req.session.email) {
throw new NoEmailFoundInLoggedOutSessionError();
throw new NoEmailFoundInUnauthenticatedSessionError();
}

req.session.needsInclusionconnectWelcomePage =
Expand Down
5 changes: 3 additions & 2 deletions src/types/express-session.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface LoggedOutSessionData {
export interface UnauthenticatedSessionData {
email?: string;
loginHint?: string;
needsInclusionconnectWelcomePage?: boolean;
interactionId?: string;
mustReturnOneOrganizationInPayload?: boolean;
Expand All @@ -25,7 +26,7 @@ export interface AuthenticatedSessionData {
}

declare module "express-session" {
export interface SessionData extends LoggedOutSessionData {
export interface SessionData extends UnauthenticatedSessionData {
user?: User;
temporaryEncryptedTotpKey?: string;
amr?: AmrValue[];
Expand Down

0 comments on commit 19c7581

Please sign in to comment.