Aller au contenu
Facteur
/ Playwright · 2FA

Le code arrive par e-mail, ou il se calcule

Un parcours à double authentification bloque une suite de tests à deux endroits : le code envoyé par e-mail, que le test ne peut pas lire, et celui de l'application d'authentification, qu'il ne peut pas générer. Les deux se règlent, et pas de la même façon.

Le code reçu par e-mail

La requête part avant le clic

Même mécanique que pour une inscription : la requête part avant le clic et répond à l'arrivée du message.

connexion-2fa.spec.ts TypeScript
import { expect, test } from '@playwright/test';

const INBOX = 'j3k9x2mq';

// hasOtp vise le code extrait, pas un texte qui le cite.
async function attendreLeCode(sentTo: string) {
  const search = new URLSearchParams({ inbox: INBOX, sentTo, hasOtp: 'true', wait: '30000' });
  const response = await fetch('https://api.facteur.eu/v1/messages?' + search, {
    headers: { authorization: 'Bearer ' + process.env.FACTEUR_API_KEY },
  });
  const { messages } = await response.json();
  if (!messages[0]) throw new Error('Aucun code pour ' + sentTo);
  return messages[0].otp;
}

test('la connexion demande le code reçu par e-mail', async ({ page }) => {
  const tag = 'login-' + Date.now();
  const arrive = attendreLeCode(tag);

  await page.goto('/connexion');
  await page.getByLabel('Email').fill(tag + '@' + INBOX + '.inbox.facteur.eu');
  await page.getByRole('button', { name: 'Continuer' }).click();

  // Le code, extrait à la réception.
  await page.getByLabel('Code').fill(await arrive);
  await expect(page.getByText('Bienvenue')).toBeVisible();
});
Un critère qui vise le code, pas le texte
Le critère hasOtp demande « le message qui porte un code », ce qui évite de retomber sur un e-mail qui cite un ancien code dans son historique.
Le code vit dans son propre champ
Extrait à la réception. Votre test compare une valeur, il ne cherche pas un motif dans du HTML.
Le code d'une application d'authentification

Vingt lignes de Node, aucune dépendance

Celui-là, personne ne peut vous l'envoyer : il se calcule à partir du secret que votre écran d'activation affiche. Facteur n'y joue aucun rôle, et le téléphone reste dans le tiroir.

totp.ts TypeScript
import { createHmac } from 'node:crypto';

const BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

/** Le secret que votre écran d'activation affiche, en base32. */
function decode(secret: string): Buffer {
  let bits = 0;
  let value = 0;
  const out: number[] = [];
  for (const c of secret.replace(/=/g, '').toUpperCase()) {
    value = (value << 5) | BASE32.indexOf(c);
    bits += 5;
    if (bits >= 8) {
      out.push((value >>> (bits - 8)) & 0xff);
      bits -= 8;
    }
  }
  return Buffer.from(out);
}

/** RFC 6238 : SHA-1, six chiffres, pas de trente secondes. */
export function totp(secret: string, at = Date.now()): string {
  const counter = Math.floor(at / 1000 / 30);
  const message = Buffer.alloc(8);
  message.writeUInt32BE(Math.floor(counter / 2 ** 32), 0);
  message.writeUInt32BE(counter % 2 ** 32, 4);

  const digest = createHmac('sha1', decode(secret)).update(message).digest();
  const offset = digest[digest.length - 1] & 0x0f;
  const binary =
    ((digest[offset] & 0x7f) << 24) |
    (digest[offset + 1] << 16) |
    (digest[offset + 2] << 8) |
    digest[offset + 3];

  return String(binary % 1_000_000).padStart(6, '0');
}
Pourquoi

Ce que ça change pour une suite de tests

01

Le parcours complet est testable

Activation de la double authentification, connexion, code de secours : rien ne dépend plus d'un appareil physique ni d'une boîte aux lettres personnelle.

02

Le test reste lisible

Deux appels et une assertion. Le code n'est pas extrait par vous, et l'attente n'est pas une boucle que vous maintenez.

03

Les exécutions parallèles ne se marchent pas dessus

Chaque test reçoit à son adresse, donc le code lu est celui de son propre parcours.

Le plugin Playwright

Écrit, pas encore publié

playwright-facteur sait attendre un message qui porte un code. Il n'est pas encore publié sur npm. Le test ci-dessus n'attend rien de lui : il appelle l'API directement, et continuera de marcher tel quel.

Débloquez vos tests de double authentification

Une inbox, une clé en lecture seule, et le parcours entier redevient testable. L'offre gratuite ne demande pas de carte bancaire.