diff --git a/.github/workflows/end-to-end.yml b/.github/workflows/end-to-end.yml index 3ce14a21..39954ab1 100644 --- a/.github/workflows/end-to-end.yml +++ b/.github/workflows/end-to-end.yml @@ -51,6 +51,7 @@ jobs: - signin_with_email_verification_renewal - signin_with_magic_link - signin_with_totp + - signin_with_webauthn - signup_entreprise_unipersonnelle - update_personal_information - update_totp_application diff --git a/cypress/e2e/signin_with_webauthn/env.conf b/cypress/e2e/signin_with_webauthn/env.conf new file mode 100644 index 00000000..712d7471 --- /dev/null +++ b/cypress/e2e/signin_with_webauthn/env.conf @@ -0,0 +1,2 @@ +DO_NOT_AUTHENTICATE_BROWSER=False +DO_NOT_SEND_MAIL=True diff --git a/cypress/e2e/signin_with_webauthn/fixtures.sql b/cypress/e2e/signin_with_webauthn/fixtures.sql new file mode 100644 index 00000000..bda36a35 --- /dev/null +++ b/cypress/e2e/signin_with_webauthn/fixtures.sql @@ -0,0 +1,45 @@ +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'); + +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, 'verified_email_domain', true); + +INSERT INTO authenticators + (credential_id, credential_public_key, counter, credential_device_type, credential_backed_up, + transports, user_id, display_name, created_at, last_used_at, usage_count, user_verified) +VALUES + ('Bdf73ipOxFEpTjCr4FqGYnLsWAKU/s6eLh2a32GihKo=', + '\xa401010327200621582015a9f4727d84c47413e94c4b5109aee81a0ec9d1e610ff5d522eb9f8e2af927a', +-- '\x3059301306072a8648ce3d020106082a8648ce3d0301070342000495886e1804854510af5d8cb4943c0caa1ae25eef46226258e9175eb461783e000f67da1363dab497ea492d7fd5ffd855f5d34158d02c89999dce353dcd1b1dcd', +-- '\xa50102032620012158203644bd38776918bb7d83059369ddbb634bd207df223153674c20994f91ca97bf2258203d6bd21d1da555db3eb6590a34e003642c9602670203d451b2adb9302ab1325a', + 0, 'singleDevice', false, ARRAY ['internal'], 1, null, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 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/', + 'MonComptePro test client. More info: https://github.com/numerique-gouv/moncomptepro-test-client.', + null, null, null, null); diff --git a/cypress/e2e/signin_with_webauthn/index.cy.ts b/cypress/e2e/signin_with_webauthn/index.cy.ts new file mode 100644 index 00000000..e701ef80 --- /dev/null +++ b/cypress/e2e/signin_with_webauthn/index.cy.ts @@ -0,0 +1,70 @@ +// Passkey crypto elements where generated with Webauthn Chrome dev tools. +// We create a virtual authenticator with these tools. +// We used it on a local instance of MonComptePro. +// We exported the private key from the dev tools. +// We exported the record of the authenticator from MonComptePro local database. + +describe("sign-in with webauthn on untrusted browser", () => { + before(async () => { + await Cypress.automation("remote:debugger:protocol", { + command: "WebAuthn.enable", + }); + }); + + it("should sign-in with webauthn", function () { + Cypress.automation("remote:debugger:protocol", { + command: "WebAuthn.addVirtualAuthenticator", + params: { + options: { + protocol: "ctap2", + transport: "internal", + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }, + }).then(({ authenticatorId }) => { + Cypress.automation("remote:debugger:protocol", { + command: "WebAuthn.addCredential", + params: { + authenticatorId, + credential: { + credentialId: "Bdf73ipOxFEpTjCr4FqGYnLsWAKU/s6eLh2a32GihKo=", + isResidentCredential: true, + userHandle: "MQ==", // unused1@yopmail.com + rpId: "localhost", + privateKey: + "MC4CAQAwBQYDK2VwBCIEIC5SpNCKBGOjrii3D7Ao5tsyPCiNdUHdZt78j6z2xQlR", + signCount: 0, + }, + }, + }); + }); + + cy.visit("http://localhost:4000"); + cy.get("button.moncomptepro-button").click(); + + cy.get('[name="login"]').type("unused1@yopmail.com"); + cy.get('[type="submit"]').click(); + + cy.get('[href="/users/sign-in-with-passkey"]') + .contains("Se connecter avec une clé d’accès") + .click(); + + cy.contains("Se connecter avec une clé d’accès"); + + cy.get("#webauthn-btn-begin-authentication").contains("Continuer").click(); + // An error is thrown here: + // The 'publickey-credentials-get' feature is not enabled in this document. + // See https://github.com/cypress-io/cypress/issues/6991#issuecomment-2168311131 + + cy.contains('"amr": [\n "pop",\n "mfa"\n ],'); + }); +}); + +// TODO test the amr result in the following cases +// TODO login with webauthn and userVerified=true +// TODO unable to login with webauthn and userVerified=false +// TODO login with password + webauthn and userVerified=false +// TODO login with password + webauthn and userVerified=true +// TODO a second factor should not trigger email verification for untrusted browser reason