Aller au contenu

Vérifier chaque inscription Supabase avant de créer le compte

Intégration Inscriptions et authentification

Une Edge Function sur le hook Before User Created de Supabase interroge PricklyMails et refuse l'inscription d'une adresse qui ne peut pas recevoir d'emails.

Pas besoin de connecteur : à chaque inscription, Supabase appelle l'API PricklyMails depuis le hook Before User Created, et une adresse qui ne peut pas recevoir d'emails est refusée avant que le compte n'existe.

Vérifier gratuitement

Pas à pas

Vérifier chaque inscription en quatre étapes

  1. Dans PricklyMails

    Créer une clé API dans PricklyMails

    1. Dans le tableau de bord, ouvrez Clés API et cliquez sur Nouvelle clé.
    2. À l'étape Environnement, choisissez d'abord Test : la clé commence par pm_test_, répond par des résultats prédéfinis, ne facture rien et ne contacte aucun serveur de messagerie.
    3. Copiez la clé dès qu'elle s'affiche : elle n'est montrée qu'une fois.

    Astuce

    L'offre Free ne compte qu'une clé : une fois que tout fonctionne, révoquez la clé de test, puis créez-en une en Production.

  2. Dans Supabase

    Ajouter la vérification dans une Edge Function Supabase

    Le hook est un endpoint HTTP, ici une Edge Function de votre projet, créée et déployée avec la CLI Supabase une fois celle-ci reliée au projet par supabase link. La clé va dans les secrets de la fonction, jamais dans son code.

    1. Créez la fonction avec supabase functions new before-user-created.
    2. Remplacez le contenu de son index.ts par le code ci-dessous.
    3. Enregistrez votre clé de test avec supabase secrets set PRICKLYMAILS_KEY=pm_test_votre_cle.
    4. Déployez la fonction avec supabase functions deploy before-user-created --no-verify-jwt.

    Le code demande à PricklyMails de répondre en quelques secondes (Prefer: wait) et cesse d'attendre peu après, pour qu'une inscription ne reste jamais suspendue à la réponse. Il ne refuse l'inscription que si la recommandation est remove, et la laisse passer sinon : adresse risquée, pas de réponse à temps, erreur.

    supabase/functions/before-user-created/index.ts
    import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
    
    const VERIFY_URL = 'https://api.pricklymails.com/api/verify'
    const REFUSAL =
        'This email address cannot receive mail. ' +
        'Check it for a typo, or use another one.'
    
    const hookSecret = Deno.env.get('BEFORE_USER_CREATED_HOOK_SECRET') ?? ''
    const webhook = new Webhook(hookSecret.replace('v1,whsec_', ''))
    
    interface BeforeUserCreatedEvent {
        user: { email?: string }
    }
    
    // Supabase gives the hook 5 seconds: wait 3 for the answer, give up at 3.5.
    async function recommendationFor(
        email: string,
        key: string,
    ): Promise<string | null> {
        try {
            const response = await fetch(VERIFY_URL, {
                method: 'POST',
                headers: {
                    Authorization: `Bearer ${key}`,
                    'Content-Type': 'application/json',
                    Prefer: 'wait=3',
                },
                body: JSON.stringify({ email }),
                signal: AbortSignal.timeout(3500),
            })
            if (200 !== response.status) {
                return null
            }
            const { verification } = await response.json()
            return verification?.recommendation ?? null
        } catch {
            return null
        }
    }
    
    Deno.serve(async (request) => {
        const payload = await request.text()
        const headers = Object.fromEntries(request.headers)
        let event: BeforeUserCreatedEvent
        try {
            event = webhook.verify(payload, headers) as BeforeUserCreatedEvent
        } catch {
            return new Response('Invalid signature', { status: 401 })
        }
        const email = event.user.email
        const key = Deno.env.get('PRICKLYMAILS_KEY') ?? ''
        if (email && 'remove' === (await recommendationFor(email, key))) {
            // Supabase turns a 4xx from the hook into a 500: refuse in a 200 body.
            return Response.json({ error: { http_code: 400, message: REFUSAL } })
        }
        return Response.json({})
    })
    
    Comment l'API répond en un seul appel

    À noter

    Supabase Auth appelle le hook avant qu'un jeton utilisateur n'existe, d'où --no-verify-jwt : c'est la signature Standard Webhooks vérifiée par le code qui prouve qu'un appel vient de Supabase. Pour garder ce réglage dans votre dépôt, mettez verify_jwt = false sous [functions.before-user-created] dans supabase/config.toml.

    Attention

    Gardez le statut 200 sur le refus, l'erreur dans le corps : le guide des hooks de Supabase traite un 400 ou un 403 renvoyé par un hook HTTP comme une erreur interne, et répond 500 à l'inscription au lieu du message.

  3. Dans Supabase

    Activer le hook dans Supabase

    Supabase signe chaque appel avec un secret qu'il génère pour le hook, et la fonction refuse tout appel qu'elle ne peut pas vérifier : enregistrez donc le secret avant de créer le hook. Une fonction déployée lit un nouveau secret aussitôt, sans redéploiement.

    1. Dans le tableau de bord, ouvrez Authentication puis Hooks.
    2. Créez un nouveau Before User Created hook.
    3. Choisissez HTTPS comme type de hook.
    4. Dans le champ URL, collez l'URL de la fonction, qui se termine par /functions/v1/before-user-created.
    5. Cliquez sur Generate Secret.
    6. Enregistrez le secret affiché avec supabase secrets set BEFORE_USER_CREATED_HOOK_SECRET=v1,whsec_votre_secret.
    7. Cliquez sur Create.

    Astuce

    Vous préférez le tableau de bord à la CLI ? Sur la page Edge Function Secrets, saisissez la Key et la Value, puis cliquez sur Save, pour PRICKLYMAILS_KEY comme pour le secret du hook.

    À noter

    La documentation de Supabase n'existe qu'en anglais : les libellés cités sur cette page sont ceux qu'elle donne.

  4. Dans Supabase

    Essayer avec la clé de test

    1. Inscrivez-vous avec une adresse qui commence par invalid, comme invalid@example.com : la clé de test répond remove, et l'inscription est refusée.
    2. Inscrivez-vous avec une autre adresse, comme valid@example.com : le compte est créé.
    3. Là où vous avez rangé la clé, remplacez-la par une clé créée en Production, qui commence par pm_live_ : dès lors, chaque inscription est vérifiée pour de vrai et facturée comme toute vérification.

Ce que devient chaque inscription

Le code lit la recommandation que l'API donne pour l'adresse, la même que dans le rapport d'une liste.

À retirer

Refusée : l'adresse n'existe pas, son domaine ne peut pas recevoir d'emails, ou elle est jetable.

Supabase Auth ne crée pas l'utilisateur et répond à l'inscription avec le message du hook et un statut 400. Avec supabase-js, signUp le renvoie dans error, une AuthApiError dont le message est cette phrase et dont le status vaut 400 : la personne la voit là où votre formulaire affiche error.message.

À revoir

Acceptée : domaine qui accepte toutes les adresses, adresse générique comme contact@, ou serveur qui n'a pas répondu. L'adresse peut fonctionner.

À envoyer

Acceptée : l'adresse est valide, ou hébergée chez un grand fournisseur qui accepte toutes les adresses.

Pas de réponse à temps
Supabase accorde 5 secondes en tout à un hook HTTP, nouvelles tentatives comprises, et fait échouer l'inscription avec une erreur hook_timeout si aucune réponse n'arrive dans ce délai. Le code attend PricklyMails 3 secondes au plus, abandonne à 3,5 et laisse passer l'inscription : le hook répond toujours à temps.

Questions fréquentes

Le hook s'exécute-t-il pour une inscription via un fournisseur OAuth ?

Oui. Il s'exécute chaque fois que Supabase Auth s'apprête à créer un utilisateur, et les exemples de Supabase s'en servent pour bloquer les nouveaux comptes créés via un fournisseur OAuth, lu dans app_metadata.provider. Un utilisateur qui existe déjà et se reconnecte n'est pas recréé : le hook ne s'exécute pas pour lui.

SourceAide officielle de Supabase

Peut-on essayer le hook en local avec la CLI Supabase ?

Oui. Dans supabase/config.toml, ajoutez une section [auth.hook.before_user_created] avec enabled = true, une uri qui atteint la fonction par host.docker.internal, le nom par lequel le conteneur Auth joint votre machine, et secrets = "env(BEFORE_USER_CREATED_HOOK_SECRET)". Générez le secret avec openssl rand -base64 32, précédé de v1,whsec_, puis placez-le dans le .env à la racine du projet, que lit config.toml, et dans supabase/functions/.env avec PRICKLYMAILS_KEY, que lit la fonction. Lancez enfin supabase functions serve before-user-created --no-verify-jwt.

SourceAide officielle de Supabase

Le hook peut-il être une fonction Postgres plutôt qu'une Edge Function ?

Supabase l'accepte, mais ne laisse que 2 secondes à un hook Postgres, contre 5 à un hook HTTP. L'Edge Function laisse la place à l'attente de 3 secondes sur la réponse de PricklyMails.

SourceAide officielle de Supabase

Le hook demande-t-il un forfait Supabase payant ?

Non. Supabase propose le hook Before User Created sur le forfait Free comme sur le Pro, et le forfait Free accepte jusqu'à 100 Edge Functions par projet.

SourceAide officielle de Supabase

Existe-t-il une extension PricklyMails pour Supabase ?

Non, et il n'y a rien à installer : le code ci-dessus appelle l'API PricklyMails avec votre clé.

Ces marques appartiennent à leurs propriétaires respectifs. Les citer n'implique aucun partenariat.

Vérifiez votre liste avant votre prochain envoi.

1 000 jetons offerts à l'inscription, sans carte bancaire. Ensuite, vous ne payez que les réponses.