UserFrosting forms - Invalid or missing CSRF token - php

I am trying to submit a simple form in UserFrosting and as a test only display the success message, with no data modification. I followed the guidance from Lesson 2 but I ran into the CSRF issue:
UserFrosting returns the following error:
Invalid or missing CSRF token.
What am I missing? Up until this point UserFrosting was very easy to digest :(
The form:
<form class="form-horizontal" role="form" name="requestDescription" action="{{site.uri.public}}/soap/requests/desc/edit/{{ keyname }}" method="post">
<div class="form-group">
<label for="input_group" class="col-md-2 control-label">Name</label>
<div class="col-md-10">
<input type="text" id="input_name" class="form-control" name="lgname" placeholder="{{ name }}">
</div>
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-success text-center">Update</button>
</div>
</form>
with added script part to the bottom of the twig file:
<script>
$(document).ready(function() {
// Load the validator rules for this form
var validators = {{validators | raw}};
ufFormSubmit(
$("form[name='requestDescription']"),
validators,
$("#userfrosting-alerts"),
function(data, statusText, jqXHR) {
// Reload the page on success
window.location.reload(true);
}
);
});
</script>
Here are my two functions from the controller:
public function soapRequestDescriptionEditPage($keyname){
if (!$this->_app->user->checkAccess('uri_soap_requests')){
$this->_app->notFound();
}
$requestDetails = $this->soapRequestReadMeta($keyname);
$schema = new \Fortress\RequestSchema($this->_app->config('schema.path') . "/forms/soap-request-description-edit.json");
$this->_app->jsValidator->setSchema($schema);
$this->_app->render('soap/soap-request-description-edit.twig', [
"name" => $requestDetails['name'],
"description" => $requestDetails['description'],
"keyname" => $keyname,
"validators" => $this->_app->jsValidator->rules()
]);
}
public function test(){
if (!$this->_app->user->checkAccess('uri_soap_requests')) {
$this->_app->notFound();
}
$post = $this->_app->request->post();
$ms = $this->_app->alerts;
$requestSchema = new \Fortress\RequestSchema($this->_app->config('schema.path') . "/forms/soap-request-description-edit.json");
$rf = new \Fortress\HTTPRequestFortress($ms, $requestSchema, $post);
$ms->addMessageTranslated("success", "Everyone's title has been updated!", $post);
$rf->sanitize();
if (!$rf->validate()) {
$this->_app->halt(400);
}
$data = $rf->data();
}
Entries from the index.php file:
$app->post('/soap/requests/desc/edit/:request_id/?', function () use ($app) {
$controller = new UF\SoapController($app);
return $controller->test();
});
$app->get('/soap/requests/desc/edit/:request_id/?', function ($request_id) use ($app) {
$controller = new UF\SoapController($app);
return $controller->soapRequestDescriptionEditPage($request_id);
});
Finally, the schema:
{
"lgname" : {
"validators" : {
"length" : {
"min" : 1,
"max" : 150,
"message" : "The new title must be between 1 and 150 characters long."
}
},
"sanitizers" : {
"raw" : ""
}
}
}

As of UserFrosting 4, you should explicitly add the hidden CSRF input fields to your form. There is a partial template forms/csrf.html.twig that contains these fields, which you can insert using Twig's include tag:
<form class="form-horizontal" role="form" name="requestDescription" action="{{site.uri.public}}/soap/requests/desc/edit/{{ keyname }}" method="post">
{% include "forms/csrf.html.twig" %}
<div class="form-group">
<label for="input_group" class="col-md-2 control-label">Name</label>
<div class="col-md-10">
<input type="text" id="input_name" class="form-control" name="lgname" placeholder="{{ name }}">
</div>
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-success text-center">Update</button>
</div>
</form>
For requests that are made without a form (for example, if it has been constructed purely in Javascript), you can grab the CSRF token name and value from the global site.csrf variable:
var userName = 'luke';
var fieldName = 'lgname';
var data = {
'value': fieldValue
};
data[site.csrf.keys.name] = site.csrf.name;
data[site.csrf.keys.value] = site.csrf.value;
var url = site.uri.public + '/api/users/u/' + userName + '/' + fieldName;
return $.ajax({
type: "PUT",
url: url,
data: data
}).done(function (response) {
window.location.reload();
});

It turned out that my code was fine. There were unrelated javascript errors on the page affecting UserFrosting form processing. Fixing these errors allowed UserFrosting to handle the form.
Note to self... make it a habit to look into the console for javascript errors :)

Related

Codeigniter 4 reuse of CSRF token in AJAX modal

Scenario:
I am developing CMS system and I wan to add some categories to the objects (pages, posts, media etc.). In my view, to save a new category I use HTML form placed in Bootstrap modal which is sent via AJAX to my controller. The CSRF protection is enabled on the entire site.
While sending the data for the first time, I pass the CSRF token name and hash via form. Once being processed by PHP code in controller, I want to pass CSRF values in the response so I will be able to "re-use" the form in the modal (e.g. display error messages or/and create another category).
Yet, I am not able to access the get_csrf_token_name() and get_csrf_hash() methods to pass values back to the view.
In my view admmin/category/create.php:
...
<!-- CREATE CATEGORY MODAL MODAL -->
<div class="modal" id="createCategory" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Nová kategorie</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zavřít"></button>
</div>
<div class="modal-body">
<form action="" method="post" id="createCategoryForm">
<input type="hidden" value="<?= csrf_hash(); ?>" name="<?= csrf_token(); ?>" id="csrf">
<div class="form-group mb-3">
<label for="title" class="form-label">Název kategorie</label>
<input type="text" class="form-control" name="title" id="title" value="">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" id="createCategoryConfirm">Vytvořit novou kategorii</button>
</form>
</div>
</div>
</div>
</div>
...
<script>
$('#head').on('click', '.create', function() {
$('#createCategory').modal('show');
$('#createCategoryForm').attr('action', '<?= base_url(); ?>/admin/category/create');
$('#createCategoryConfirm').click(function(e) {
e.preventDefault();
var url = $('#createCategoryForm').attr('action');
var csrfElement = $('#csrf');
var csrfName = csrfElement.attr('name');
var csrfHash = csrfElement.attr('value');
var categoryTitle = $('input[name=title]').val();
var data = {
[csrfName]: csrfHash,
'title': categoryTitle
};
console.log(data);
$.ajax({
type: 'ajax',
method: 'POST',
url: url,
data: data,
dataType: 'json',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
headers: {'X-Requested-With': 'XMLHttpRequest'},
success: function(result) {
console.log(result);
},
error: function(result) {
console.log(result);
},
});
});
});
</script>
In my controller Category.php:
<?php
namespace App\Controllers\Admin;
use App\Controllers\BaseController;
use App\Models\CategoryModel;
use CodeIgniter\I18n\Time;
class Category extends BaseController {
protected $model;
protected $validator;
protected $security;
public function __construct() {
$this->model = new CategoryModel();
$this->validation = \Config\Services::validation();
$this->security = \Config\Services::security();
helper(['form', 'date', 'url']);
}
...
public function create() {
$response = [];
// This part of code returns error
//
// $response['csrf'] = array(
// 'name' => $this->security->get_csrf_token_name(),
// 'hash' => $this->security->get_csrf_hash()
// );
$response['security'] = $this->security;
if ($this->request->isAJAX()) {
$newCategory = [
'title' => $this->request->getVar('title'),
'slug' => url_title($this->request->getVar('title')),
'author' => session()->get('id'),
'created_at' => Time::now('Europe/Prague')->toDateTimeString(),
'updated_at' => Time::now('Europe/Prague')->toDateTimeString(),
'parent' => '0'
];
$this->validation->run($newCategory, 'categoryRules');
if (!empty($this->validation->getErrors())) {
$this->model->save($newCategory);
$response['errors'] = $this->validation->getErrors();
echo json_encode($response);
} else {
$this->model->save($newCategory);
$response['success'] = 'New category was created';
echo json_encode($response);
}
}
}
...
In the browser console, the AJAX response is POST http://localhost/admin/category/create 500 (Internal Server Error) with full response:
code: 500
file: "D:\Web\XAMPP\htdocs\lenka\app\Controllers\Admin\Category.php"
line: 38
message: "Call to undefined method CodeIgniter\Security\Security::get_csrf_token_name()"
title: "Error"
Could anyone please see the issue here? Is there any good solution on how to reuse CSRF tokens in CI4? I tried set config values of CSRF regenerate both to true and false, with no effect.
update this line cod in .ENV
or
app/config/security
CSRF Regenerate = false

laravel api ajax form wont submit

This is my first api project. Can you help me with my code please?
I can't see the problem.
Here is my controller.
public function store(Request $request)
{
//
$valid=Validator::make($request->all(),[
'text'=>'required',
'body'=>'required'
]);
if($valid->fails()){
return response()->json(['message'=>$valid->messages()]);
}else{
$item= Item::create([
'text'=>$request->input('text'),
'body'=>$request->input('body')
]);
return response()->json($item);
}
}
and here is my form.Is there anything wrong in the form?
<form id="form">
<div class="form-group">
<label>Text :</label>
<input type="text" id="text" class="form-control col-sm-4">
</div>
<div class="form-group">
<label>Body :</label>
<textarea id="body" class="form-control col-sm-4"></textarea>
</div>
<div class="form-action">
<input type="submit" class="btn btn-primary" value="submit">
</div>
</form>
and the ajax code between the show function is working but I don't know where the problem is ?.
$('#form').on('submit', function (e) {
e.preventDefault();//prevent the form to submit to file
let text = $('#text').val();
let body = $('#body').val();
addItems(text, body);
});
function addItems(text, body) {
var item = {
text: text,
body: body
};
$.ajax({
method: 'POST',
url: 'http://localhost:8000/api/items',
data: item,
success: function (item) {
alert('done the item number' + item.id + ' has been added!!');
location.reload();
},
error: function () {
alert('error')
}
})
}
Thanks for helping!
if your front-end source separated from back-end source, then add cross-Origin Resource Sharing
package to your laravel project.
if its on your laravel view then add csrf token to meta tag -
<meta name="csrf-token" content="{{ csrf_token() }}">
and send it with your ajax request { _token : document.querySelector('meta[name="csrf-token"]').content}
The problem is that you're sending the form without sending the cross site request forgery token.
Add the directive #csrf to your view
Then send it has Hasan wrote ;)

Getting a null value in my input file type field

I downloaded a web application and i found out that it is created using Smarty Template Engine. I want to add an avatar field when creating new company so i added enctype="multipart/form-data" and <input type="file" name="avatar"> to the existing <form> and i also added avatar to my companies table in my database. Here is the HTML code:
<form class="form-horizontal" id="ib_modal_form" enctype="multipart/form-data">
<div class="form-group"><label class="col-lg-4 control-label" for="company_name">{$_L['Company Name']}<small class="red">*</small></label>
<div class="col-lg-8"><input type="text" id="company_name" name="company_name" class="form-control" value="{$val['company_name']}"></div>
</div>
<div class="form-group"><label class="col-lg-4 control-label" for="avatar">{$_L['Avatar']}</label>
<div class="col-lg-8"><input type="file" name="avatar"></div>
</div>
<div class="form-group"><label class="col-lg-4 control-label" for="email">{$_L['Email']}</label>
<div class="col-lg-8"><input type="text" id="email" name="email" class="form-control" value="{$val['email']}"> </div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn btn-danger">{$_L['Cancel']}</button>
<button class="btn btn-primary modal_submit" type="submit" id="modal_submit"><i class="fa fa-check"></i> {$_L['Save']}</button>
</div>
I found out that the form goes to this javascript code when clicking the Save Button:
$modal.on('click', '.modal_submit', function(e){
e.preventDefault();
$.post( _url + "contacts/add_company_post/", $("#ib_modal_form").serialize())
.done(function( data ) {
if ($.isNumeric(data)) {
location.reload();
}
else {
$modal.modal('loading');
toastr.error(data);
}
});
});
Here is the code in the Controller:
case 'add_company_post':
$data = ib_posted_data();
$company = Model::factory('Models_Company')->create();
$company->company_name = $data['company_name'];
$company->url = $data['url'];
$company->email = $data['email'];
$company->phone = $data['phone'];
$company->logo_url = $data['logo_url'];
$company->avatar = $_FILES['avatar']['name'];
$company->save();
break;
The problem is that it does not recognize $_FILES['avatar']['name']; in the Controller Whenever i add a new company, i get a NULL value in my database. I cant seem to solve this problem. Any help would be appreciated. Thanks.
Change
From
$("#ib_modal_form").serialize()
To
new FormData($("#ib_modal_form")[0])
You should use FormData for uploading files using ajax. $(form).serialize() will give you just key and value.
Can you change your ajax call below way
$modal.on('click', '.modal_submit', function(e){
e.preventDefault();
var formData = new FormData($("#ib_modal_form")[0]);
$.ajax({
url: _url + "contacts/add_company_post/",
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if ($.isNumeric(data)) {
location.reload();
}
else {
$modal.modal('loading');
toastr.error(data);
}
},
});
});

Controller not getting inputs laravel

Currently I am trying to pass a creation form to a controller. I have the route and the ajax call setup and talking to the route. My problem is that when I use the ajax call the inspect tool for headers is showing my form values correctly but when I go into the controller the request->input doesnt show any values for the form.
Here is my ajax call
$(document).on("click", ".form-submit-btn", function() {
// Get the form id.
var formID = $(this).closest("form").attr("id");
var serializedForm = $(this).closest("form").serialize();
var substringEnd = formID.indexOf("-form");
var route = formID.substr(0, substringEnd).replace("-", "_");
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
// Submit the form.
$.ajax({
method: "POST",
url: "/" + route,
data: {
serializedForm
},
success: function(data) {
alert(data);
}
});
});
Here is my controller
// Create Role
public function create(Request $request)
{
// Get and validate request params
$role = $request->input('role_name');
$active = $request->input('role-active', false);
return $role;
}
And here is my route
Route::post('/create_role', 'RoleController#create');
Am I missing something that is preventing the ajax call from sending the values to the controller
Here is my form also if that helps.
<form id="create-role-form" class="form">
{{ csrf_field() }}
<button class="pull-right right-close-btn">X</button>
<h1>Add Role</h1>
<hr />
<div class="form-group">
<label>Role Name</label>
<input type="text" name="role_name" class="form-control" />
</div>
<div class="form-group">
<input type="checkbox" name="role_active" value="true" checked /> Active
</div>
<div class="form-group">
<button class="btn btn-primary form-control form-submit-btn">Create</button>
</div>
I think problem is this line
data: {serializedForm},
Just change it to
data: serializedForm,
and it should fix the problem.
Problems
I see two problems with your ajax request
Are you sure you're using ES-6 TransPiler because data:{serializedForm}, is ES-6 Syntax http://es6-features.org/#PropertyShorthand
If you're javascript is working fine. You should be able to get it like $request->get('serializedForm')['role_name'] with your existing code.
Hope it helps

Angularjs Image Upload showing empty nested object

I'm a bit confused how to access file data using with angular from a basic form. I'm following a tut on: (https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs) and youtube (https://www.youtube.com/watch?v=vLHgpOG1cW4). They seem to get it right but when I try things seem to go a different way. Anyways here's my HTML form:
<form>
<div class="form-group">
<label for="name">Full Name</label>
<input type="text" ng-model="customer.name" id="name" class="form-control"/>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" ng-model="customer.email" id="name" class="form-control"/>
</div>
<div class="form-group">
<label for="file">Image</label>
<input type="file" file-model="customer.file" id="file" class="form-control"/>
</div>
<div class="form-group">
<button type="submit" ng-click="submit()" class="btn btn-primary">Submit</button>
</div>
</form>
And the Directive
app.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
And finally the controller:
app.controller('CustomerController', ['$scope','CustomerService', function($scope, CustomerService){
$scope.customer = {};
CustomerService.save($scope.customer);
}]);
When I {{ customer }} in my view I'm getting something like this:
{"name":"Robert DeNiro","email":"robie#godfather.com","file":{}}
It's that empty last "file":{} file object that's causing me problems getting values to post to server.
Here's my Customer Service code:
var CustomerService = function($resource) {
return $resource('advertisements/:id', { id: '#_id' }, {
update: {
method: 'PUT' // this method issues a PUT request
},
save: {
method: 'post',
transformRequest: angular.identity,
'Content-Type': undefined
}
});
};
CustomerService.$inject = ['$resource'];
app.service('CustomerService', CustomerService);
I'm using Laravel 5.1 and its reporting validation errors 'required' on all fields suggesting there's an empty object sent through. Thanks in advance.
I have added a submit() method in CustomerController like this:
$scope.submit = function(){
var file = $scope.customer.file;
console.log('file is ' );
console.log(file.name);
console.log($scope.customer.file);
console.dir($scope.customer.file);
};
And in there you can see I've tried experimenting with console.log() and console.dir() and i seem to get the results. For example if i console.log($scope.customer) or console.dir($scope.customer) it gives me the nested file object with all file details. And its looking like this:
> Object {name: "robert deniro", email: "robie#godfather.com", file: File}
> Object
Notice file: File Therefore I'm able to access the file contents/object within the submit() like this: console.log(file.name) or console.log(file.type) or console.log(file.size). I don't know why I was missing it all this time. I hope someone learn from my mistake.
May be that form requires the attribute enctype="multipart/form-data".

Categories