SamC

Post form (action) to file - 403 error

Recommended Posts

Trying to make an ajax form (I think this option would work better than my current one, plus I've never used ajax before so wth), got the following:

  <form id="contact-form" method="post" class="ajax" action="<?= $templatesUrl . 'controller/suggest-form-controller.php'; ?>">

    <div class="row">
        <div class="form-group col-sm-12 col-lg-6 py-2">
            <label for="name">Name (required)</label>
            <input class="form-control" name="name" id="name" type="text">
        </div>

        <div class="form-group col-sm-12 col-lg-6 py-2">
            <label for="email">Email (required)</label>
            <input class="form-control" name="email" id="email" type="text">
        </div>
    </div>

    <div class="form-group py-2">
        <label for="message">Message (required)</label>
        <textarea class="form-control" name="message" id="message" rows="8"></textarea>
    </div>

    <div>
        <label for="recaptcha">Recaptcha (required)</label>
        <!-- Google Recaptcha code START -->
        <?php echo $captcha->render(); ?>
        <!-- Google Recaptcha code END -->
    </div>

    <div class="form-group">
        <button type="submit" class="btn btn-primary mt-3" name="sendMe" value="1">Suggest tutorial <i class="fa fa-angle-right" aria-hidden="true"></i></button>
    </div>

  </form>

...and the JS:

// ajax form
$("form.ajax").on("submit", function() {
  var self = $(this),
      url = self.attr("action"),
      type = self.attr("method"),
      data = {};

  self.find("[name=name], [name=email], [name=message]").each(function(index, value) {
    var self = $(this),
        name = self.attr("name"),
        value = self.val();
        
    // create object with name/value pairs
    data[name] = value;
  });

  $.ajax({
    url: url,
    type: type,
    data: data,
    success: function(res) {
      console.log(res);
    }
  });

  return false;
});

Problem is, when I click the submit button, I get a 403 on the post url (so at least the JS works so far).

No idea how I'm going to integrate recaptcha and valitron into all of this but need to get passed this hurdle first.

Do I have to create a matching PW path or something with a hidden page? The file is currently in:

/site/templates/controller/suggest-form-controller.php

Any hints would be awesome, thanks.

Share this post


Link to post
Share on other sites

Maybe make the form action '/' and then specify the actual URL in the js code rather than pulling it from the form?

Share this post


Link to post
Share on other sites
2 hours ago, SamC said:

<form id="contact-form" method="post" class="ajax" action="<?= $templatesUrl . 'controller/suggest-form-controller.php'; ?>">

ProcessWire will not allow you to POST to or call a script  (PHP and such) directly if that script is located in one of its protected folders. Hence, the 403 (forbidden).

 

Your options:

  1. Throw your script into a template file and post to a page using that template
  2. Throw your script into a module and post to that module's page (if backend; otherwise, will still get a 403)
  3. include/require once the script in some template file but post to a page using that template (e.g. posting to self as per @psy's suggestion)
  4. Etc...

Ajax stuff:

 

 

 

Edited by kongondo
more links
  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Thanks for the info. I used option 1 so I have the following files:

suggest-form.php (the form)

<?php namespace ProcessWire;

// get recaptcha module
$captcha = $modules->get("MarkupGoogleRecaptcha");

// save current page id in session
$session->currentPageId = $page->id;

?>

<div id="form-top"></div>

<h2>Suggest a tutorial</h2>

  <div id="form-alert" role="alert"></div>

  <form id="contact-form" method="post" class="ajax" action="<?= $pages->get(1832)->url; ?>">

    <div class="row">
        <div class="form-group col-sm-12 col-lg-6 py-2" id="name">
            <label for="name">Name (required)</label>
            <input class="form-control" name="name" type="text">
        </div>

        <div class="form-group col-sm-12 col-lg-6 py-2" id="email">
            <label for="email">Email (required)</label>
            <input class="form-control" name="email" type="text">
        </div>
    </div>

    <div class="form-group py-2" id="message">
        <label for="message">Message (required)</label>
        <textarea class="form-control" name="message" id="message" rows="8"></textarea>
    </div>

    <div class="form-group py-1" id="recaptcha">
        <label for="recaptcha">Recaptcha (required)</label>
        <!-- Google Recaptcha code START -->
        <?php echo $captcha->render(); ?>
        <!-- Google Recaptcha code END -->
    </div>

    <div class="form-group">
        <button type="submit" id="sendMe" class="btn btn-primary mt-3" name="sendMe" value="1">Suggest tutorial <i class="fa fa-angle-right" aria-hidden="true"></i></button>
    </div>

  </form>

<?php
  echo $captcha->getScript();
?>


suggest.php (process and echo JSON or output plain messages to '/suggest/' for no JS fallback)

<?php namespace ProcessWire;

// is form submitted
$isSubmitted = ($input->post->sendMe) ? true : false;

if ($isSubmitted) {

  // default out string for no JS
  $out = "<h2>Please fix the following errors</h2>";

  // require Valitron class
  require_once("./vendor/vlucas/valitron/src/Valitron/Validator.php");

  // where to send email
  $myEmail = "EMAIL_HERE";

  // sanitize input variables
  $name = $sanitizer->text($input->post->name);
  $email = $sanitizer->email($input->post->email);
  $message = $sanitizer->textarea($input->post->message);

  // create new valitron
  $v = new \Valitron\Validator(array(
    "name" => $name,
    "email" => $email,
    "message" => $message
    )
  );

  // create valitron rules
  $v->rule("required", ["name", "email", "message"]);
  $v->rule("email", "email");

  // does form validate
  $formValidates = $v->validate();

  // fields array if error, otherwise false
  $nameHasError = $v->errors("name");
  $emailHasError = $v->errors("email");
  $messageHasError = $v->errors("message");
  
  // multidimensional array of errors
  $errors = $v->errors();

  // loop errors and concat for 
  // no JS output
  foreach ($errors as $error) {
    foreach ($error as $value) {
      $out .= "<p>" . $value . "</p>";
    }
  }

  // get recaptcha module
  $captcha = $modules->get("MarkupGoogleRecaptcha");

  // does recaptcha validate?
  $recaptcha = $captcha->verifyResponse();

  // if not, concat for no JS output
  if (!$recaptcha) {
    $out .= "<p>Recaptcha is required</p>";
  }

  // default form submitted value
  $isSent = false;

  // if fields validate and recaptcha complete
  if ($recaptcha && $formValidates) {

    // create email message
    $msg = "
      <html>
        <body>
          <p><b>Name:</b> {$name}</p>
          <p><b>Email:</b> {$email}</p>
          <p><b>Message:</b></p>
          <p>{$message}</p>
        </body>
      </html>";

    $mail = wireMail();
    
    $mail->to($myEmail)
    ->from($email, $name)
    ->subject('Email from website...')
    ->bodyHTML($msg);

    // did email send?
    $isSent = ($mail->send()) ? true : false;

    // success message for no JS
    if ($isSent) {
      $out = "<h2>Thanks for the suggestion!</h2>";
    }
  }

  // form sent via ajax
  if ($config->ajax) {
    $result = [
      "name" => $name,
      "email" => $email,
      "message" => $message,
      "nameHasError" => $nameHasError,
      "emailHasError" => $emailHasError,
      "messageHasError" => $messageHasError,
      "fieldsValidate" => $formValidates,
      "recaptchaComplete" => $recaptcha,
      "formSent" => $isSent
    ];
    
    // output JSON for ajax
    echo json_encode($result);
  }
  // output for basic no JS page
  else {
    include("./includes/header.php");
    echo "
      <div class='container py-5'>
        <div class='row justify-content-center '>
          <div class='col-lg-7'>
              {$out}
            <h4><a href='{$pages->get($session->currentPageId)->url}'>Go back to previous page</a></h4>
          </div>
        </div>
      </div>
    ";
    include("./includes/footer.php");
  }

}
// if url accessed with no form submission
else {
  $session->redirect("/");
}

app.js (ajax)

// ajax form
$("form.ajax").on("submit", function() {
  var self = $(this),
      url = self.attr("action"),
      type = self.attr("method"),
      data = {};

  self.find("[name]").each(function(index, value) {
    var self = $(this),
        name = self.attr("name"),
        value = self.val();
        
    // add field values to data obj
    data[name] = value;
  });

  // send data obj to /suggest/ php page for processing
  $.ajax({
    url: url,
    type: type,
    data: data,
    dataType: "json",
    success: function(res) {
      var nameField = self.find("#name"),
          emailField = self.find("#email"),
          messageField = self.find("#message"),
          alert = $("#form-alert");
          dataObj = res;

      function errors(obj, key, field) {
        // if key value is empty string or array
        if (obj[key].length === 0 || Array.isArray(obj[key])) {
          alert.removeClass("alert alert-success");
          field.addClass("has-danger");
          alert.addClass("alert alert-danger");
          alert.text("Please complete required fields");
        }
        else {
          field.removeClass("has-danger");
        }
      }

      // copy function and set default value of 1st parameter
      // just for revision of bind function...
      var generateErrors = errors.bind(this, dataObj);
      
      // false = no error
      // array of errors = error
      generateErrors("nameHasError", nameField);
      generateErrors("emailHasError", emailField);

      // string = no error
      // empty string = error
      generateErrors("message", messageField);

      // return value of $mail->send() in suggest.php
      if (dataObj["formSent"]) {
        var form = $("form.ajax").html();
        $("form.ajax").html(form);
        alert.removeClass("alert alert-danger");
        alert.addClass("alert alert-success");
        alert.text("Thanks for the suggestion!");
      }
    }
  });

  return false;
});

Ignoring the goofy code itself (unless it presents a security risk), this all works.

The question I have, are there any security issues associated with ajax itself rather than just posting straight to a php file? I mean, the values in the fields are unsanitized when they get added to the data object just after ajax on("submit").

I noticed jquery serialize() for getting data from a form but not looked into that yet, could possibly replace the manual looping input fields in app.js.

Anyway, security is my biggest concern right now so any info would be awesome, thanks.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Recently Browsing   0 members

    No registered users viewing this page.