Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expires challenge calls continue to renew on page change with #35

Closed
teolaz opened this issue May 25, 2024 · 2 comments
Closed

Expires challenge calls continue to renew on page change with #35

teolaz opened this issue May 25, 2024 · 2 comments

Comments

@teolaz
Copy link

teolaz commented May 25, 2024

Hello, Matteo from Italy here!
Thank you for your great job with this alternative. I needed to switch on this because of new GDPR restrictions for cookies on reCaptcha.

I'm trying to integrate your WebComponent in a Gatsby site (React Headless) with Formik forms. I've already managed to make the entire process of challenge and validation works, without any big issue.
As Gatsby is using React, what I needed to do for the "statechange" event handling was to approach my form field component using the "useEffect" hook, so I was able to add the event listener on component mount and remove it on component unmount like below.
For simplify your reading, if you don't know that, when you return a method in useEffect, that method is fired when the component unmount.

import { Field } from "formik";
import React, { useEffect, useRef } from "react";

export default function Altcha({ props }) {
  const widgetRef = useRef(null);
  const eventListenerElements = useRef(new WeakSet());

  /* 
   need to use this as I need to append event listener on component creation
   and also on component destruction
   */
  useEffect(() => {
    const handleStateChange = (ev) => {
      // state can be: unverified, verifying, verified, error
      if (ev.detail.state === "verified") {
        // payload contains base64 encoded data for the server
        props.setFieldValue("altcha", ev.detail.payload);
      } else {
        props.setFieldValue("altcha", "");
      }
    };

    const widgetElement = widgetRef.current;

    if (widgetElement && !eventListenerElements.current.has(widgetElement)) {
      widgetElement.addEventListener("statechange", handleStateChange);
      eventListenerElements.current.add(widgetElement);
    }
    

    // Cleanup event listener and mutation observer on component unmount
    return () => {
      if (widgetElement) {
        widgetElement.removeEventListener("statechange", handleStateChange);
        eventListenerElements.current.delete(widgetElement);
      }
    };
  }, []);

  return (
    <div className="field-container">
      <div className="info">Accept the captcha to continue*</div>
      <Field type="hidden" name="altcha" value="" />
      <altcha-widget
        ref={widgetRef}
        challengeurl={"/.netlify/functions/altcha-challenge"}
      />
      {props.errors.altcha && props.touched.altcha ? (
        <div className="error">
          <span>{props.errors.altcha}</span>
        </div>
      ) : null}
    </div>
  );
}

this is my challenge handler that answer the url for the json challenge:

import { createChallenge } from "altcha-lib";

export default async function handler(req, context) {
  const hmacKey = process.env.HMAC_KEY;
  try {
    // set expiration time 1 minute max
    const expirationDate = new Date();
    expirationDate.setMinutes(expirationDate.getMinutes() + 1);

    // Create a new challenge and send it to the client:
    const challenge = await createChallenge({
      number: 10000,
      expires: expirationDate,
      hmacKey,
    });
    return Response.json(challenge);
  } catch (err) {
    console.log(err);
  }
}

I repeat, everything is working correctly. Still, I have a small issue and I believe the problem it's in your component, or in its lifecycle more precisely, maybe because you planned the event handling to respect a normal page change, so when someone change page pressing a link the event handler stops.
Instead, it seems that when the expirationDate fire a challenge call because it wants to reload the payload, even if I later change the page (with Gatsby you're using Browser History and React Router, not the entire DOM reload), so the widget has been removed from DOM, I'm continuing to see challenge request calls from the frontend, as the widget was removed but not the event handler.

Simulating a dumb user, if the user:

  • goes to contact page and press on verify in your widget
  • goes back to homepage without sending the form
  • goes to contact page and press on verify in your widget
  • goes back to homepage without sending the form
  • goes to contact page and press on verify in your widget
  • goes back to homepage without sending the form for the last time
  • remains still on homepage

what I see from the browser network section is a waterfall of unexplicable calls.

image

I don't think this is optimized for production deployments, as I'm loading both the server(s) and client(s) with useless calls and job to do.

What I'm imagining to solve the problem (without checking your code deeper) is to remove the event handlers when the component has been removed from DOM. I've searched through the net to find if something like this is possible and I've found something that seems to be quite similar to my case https://hackernoon.com/a-guide-to-handling-web-component-removal-with-disconnectedcallback

Or maybe I'm missing something?

I hope to have been clear enough, in case tell me if you need further info.
Thank you
Teo

@ovx
Copy link
Contributor

ovx commented May 26, 2024

Hi, thanks for reporting. The component didn't properly cleared the expiration timer, it's now fixed in 0.4.2. Would be great if you could retest with this version and report if it solved your issues.

@teolaz
Copy link
Author

teolaz commented May 26, 2024

Ehi @ovx, thank you so much for your responsiveness!
I just tried your fix and it seems now everything's ok.
Thank you!
Teo

@ovx ovx closed this as completed May 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants