Skip to content

Commit

Permalink
feat(user): add certification routes
Browse files Browse the repository at this point in the history
  • Loading branch information
douglasduteil committed Feb 7, 2025
1 parent 2e9d9b8 commit 1beee87
Show file tree
Hide file tree
Showing 26 changed files with 690 additions and 14 deletions.
1 change: 1 addition & 0 deletions .github/workflows/end-to-end.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
- set_info_after_account_provisioning
- signin_from_proconnect_federation_client
- signin_from_standard_client
- signin_with_certification_dirigeant
- signin_with_email_verification
- signin_with_email_verification_renewal
- signin_with_legacy_scope
Expand Down
1 change: 1 addition & 0 deletions cypress/e2e/signin_with_certification_dirigeant/env.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DO_NOT_SEND_MAIL="True"
36 changes: 36 additions & 0 deletions cypress/e2e/signin_with_certification_dirigeant/fixtures.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
INSERT INTO users
(id, email, email_verified, email_verified_at, encrypted_password, created_at, updated_at,
given_name, family_name, phone_number, job, encrypted_totp_key, totp_key_verified_at, force_2fa)
VALUES
(1, '[email protected]', true, CURRENT_TIMESTAMP,
'$2a$10$kzY3LINL6..50Fy9shWCcuNlRfYq0ft5lS.KCcJ5PzrhlWfKK4NIO', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP,
'Jean', 'Certification', '0123456789', 'Dirigeant',
null, null, false);

INSERT INTO organizations
(id, siret, created_at, updated_at)
VALUES
(1, '21340126800130', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

INSERT INTO users_organizations
(user_id, organization_id, is_external, verification_type, has_been_greeted)
VALUES
(1, 1, false, 'domain', true);

INSERT INTO oidc_clients
(client_name, client_id, client_secret, redirect_uris,
post_logout_redirect_uris, scope, client_uri, client_description,
userinfo_signed_response_alg, id_token_signed_response_alg,
authorization_signed_response_alg, introspection_signed_response_alg)
VALUES
('Oidc Test Client',
'standard_client_id',
'standard_client_secret',
ARRAY [
'http://localhost:4000/login-callback'
],
ARRAY []::varchar[],
'openid email profile organization',
'http://localhost:4000/',
'ProConnect test client. More info: https://github.com/numerique-gouv/proconnect-test-client.',
null, null, null, null);
40 changes: 40 additions & 0 deletions cypress/e2e/signin_with_certification_dirigeant/index.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
describe("sign-in with a client requiring certification dirigeant", () => {
beforeEach(() => {
cy.visit("http://localhost:4000");
cy.setRequestedAcrs([
"https://proconnect.gouv.fr/assurance/certification-dirigeant",
]);
});

it("should sign-in an return the right acr value", function () {
cy.get("button#custom-connection").click({ force: true });
cy.login("[email protected]");

cy.contains("Authentifier votre statut");
cy.contains("S’identifier avec").click();

cy.origin("https://fcp.integ01.dev-franceconnect.fr", () => {
cy.contains("FIP1-LOW - eIDAS LOW").click();
});
cy.origin("https://fip1-low.integ01.fcp.fournisseur-d-identite.fr", () => {
cy.contains("Mot de passe").click();
cy.focused().type("123");
cy.contains("Valider").click();
});
cy.origin("https://fcp.integ01.dev-franceconnect.fr", () => {
cy.contains("Continuer sur FSPublic").click();
});

cy.contains("Vous allez vous connecter en tant que ");
cy.contains("Angela Claire Louise DUBOIS");

cy.contains(
"J'accepte que FranceConnect transmette mes données au service pour me connecter",
).click();
cy.contains("Continuer").click();

cy.contains(
'"acr": "https://proconnect.gouv.fr/assurance/certification-dirigeant"',
);
});
});
8 changes: 7 additions & 1 deletion cypress/e2e/signin_with_right_acr/index.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ describe("sign-in with a client requiring certification dirigeant identity", ()
cy.get("button#custom-connection").click({ force: true });

cy.login("[email protected]");
cy.contains("S’identifier avec").click();
cy.contains(
"J'accepte que FranceConnect transmette mes données au service pour me connecter",
).click();
cy.contains("Continuer").click();
cy.contains("Continuer").click();

cy.contains(
'"acr": "https://proconnect.gouv.fr/assurance/certification-dirigeant"',
Expand Down Expand Up @@ -154,7 +160,7 @@ describe("sign-in with a client requiring certification dirigeant and 2fa identi
});
});

describe("qign-in with a the requiring certification dirigeant and consistency-checked", () => {
describe("sign-in with a client requiring certification dirigeant and consistency-checked", () => {
beforeEach(() => {
cy.visit("http://localhost:4000");
cy.setRequestedAcrs([
Expand Down
48 changes: 44 additions & 4 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 @@ -9,10 +9,10 @@
},
"main": "src/index.js",
"workspaces": [
"packages/devtools/typescript",
"packages/core",
"packages/crisp",
"packages/debounce",
"packages/devtools/typescript",
"packages/email",
"packages/insee",
"packages/identite"
Expand Down
1 change: 1 addition & 0 deletions packages/identite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"spec": "src/**/*.test.ts"
},
"dependencies": {
"openid-client": "^6.1.7",
"sql-template-tag": "^5.2.1"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//

import {
authorizationCodeGrant,
buildAuthorizationUrl,
ClientSecretPost,
Configuration,
fetchUserInfo,
randomNonce,
randomState,
} from "openid-client";
import { z } from "zod";
import {
FranceConnectUserInfoSchema,
type FranceConnectUserInfo,
} from "../../types/franceconnect.schema.js";

//

export function getFranceConnectConfigurationFactory(
server: URL,
clientId: string,
clientSecret: string,
) {
return function getFranceConnectConfiguration() {
const serverUri = server.toString();
return new Configuration(
{
authorization_endpoint: `${serverUri}/authorize`,
issuer: server.origin,
jwks_uri: `${serverUri}/jwks`,
token_endpoint: `${serverUri}/token`,
userinfo_endpoint: `${serverUri}/userinfo`,
token_endpoint_auth_method: "client_secret_basic",
},
clientId,
{
id_token_signed_response_alg: "HS256",
},
ClientSecretPost(clientSecret),
);
};
}
export type GetFranceConnectConfigurationHandler = ReturnType<
typeof getFranceConnectConfigurationFactory
>;

export function createChecks() {
return {
state: randomState(),
nonce: randomNonce(),
};
}

export function getFranceConnectRedirectUrlFactory(
getConfiguration: GetFranceConnectConfigurationHandler,
parameters: {
callbackUrl: string;
scope: string;
},
) {
const { callbackUrl, scope } = parameters;
return async function getFranceConnectUser(nonce: string, state: string) {
const config = getConfiguration();
return buildAuthorizationUrl(
config,
new URLSearchParams({
nonce,
redirect_uri: callbackUrl,
scope,
state,
}),
);
};
}

export function getFranceConnectUserFactory(
getConfiguration: GetFranceConnectConfigurationHandler,
) {
return async function getFranceConnectUser(parameters: {
code: string;
currentUrl: string;
expectedNonce: string;
expectedState: string;
}) {
const { code, currentUrl, expectedNonce, expectedState } = parameters;
const config = getConfiguration();
const tokens = await authorizationCodeGrant(
config,
new URL(currentUrl),
{
expectedNonce,
expectedState,
},
{ code },
);
const claims = tokens.claims();

const { sub } = await z
.object({
sub: z.string(),
})
.parseAsync(claims);
const userInfo = await fetchUserInfo(config, tokens.access_token, sub);
return FranceConnectUserInfoSchema.passthrough().parseAsync(
userInfo,
) as Promise<FranceConnectUserInfo>;
};
}
3 changes: 3 additions & 0 deletions packages/identite/src/certification/executive/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//

export * from "./get-franceconnect-user.js";
18 changes: 18 additions & 0 deletions packages/identite/src/types/franceconnect.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//

import { z } from "zod";

//

/**
* @see https://docs.partenaires.franceconnect.gouv.fr/fs/fs-technique/fs-technique-scope-fc/#liste-des-claims
*/
export const FranceConnectUserInfoSchema = z.object({
birthdate: z.string(),
birthplace: z.string(),
family_name: z.string(),
gender: z.string(),
given_name: z.string(),
});

export type FranceConnectUserInfo = z.infer<typeof FranceConnectUserInfoSchema>;
7 changes: 6 additions & 1 deletion src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,19 @@ export const {
FEATURE_AUTHENTICATE_BROWSER,
FEATURE_CHECK_EMAIL_DELIVERABILITY,
FEATURE_CONSIDER_ALL_EMAIL_DOMAINS_AS_FREE,
FEATURE_BYPASS_MODERATION,
FEATURE_CONSIDER_ALL_EMAIL_DOMAINS_AS_NON_FREE,
FEATURE_DISPLAY_TEST_ENV_WARNING,
FEATURE_BYPASS_MODERATION,
FEATURE_RATE_LIMIT,
FEATURE_SEND_MAIL,
FEATURE_USE_ANNUAIRE_EMAILS,
FEATURE_USE_SECURE_COOKIES,
FEATURE_USE_SECURITY_RESPONSE_HEADERS,
FRANCECONNECT_CALLBACK_URL,
FRANCECONNECT_CLIENT_ID,
FRANCECONNECT_CLIENT_SECRET,
FRANCECONNECT_ISSUER,
FRANCECONNECT_SCOPES,
HTTP_CLIENT_TIMEOUT,
INSEE_CONSUMER_KEY,
INSEE_CONSUMER_SECRET,
Expand Down
Loading

0 comments on commit 1beee87

Please sign in to comment.