How to fix AJAX modal form in Laravel 5.3 - ajax

I've upgraded my app from Laravel 4.2 to Laravel 5.3. On an index page listing citations, I have an AJAX modal form to edit or view the login credentials for the citation. This was working fine in Laravel 4.2, but I cannot for the life of me get it to work in 5.3. After about 5 hours Googling and trying different things, I thought I would post it here so that someone way more experienced than me can point me in the right direction.
Here's the link on the index page:
<a style="cursor: pointer; " title= "Login Credentials" data-loopback="cit-pg-1" data-citationid="1079" class="getCitationdetails"><span class="glyphicon glyphicon-lock " title="Login Credentials"></span></a>
And here's the JavaScript:
<script type="text/javascript">
$(document).on('click','.getCitationdetails',function(){
var citationid = $(this).data('citationid');
var loopback = $(this).data('loopback');
$.ajax({
url : '/citation-password',
type:'post',
data : {citationid :citationid, loopback :loopback},
success:function(resp){
$('#AppendLoginDetails').html(resp);
$('#LoginCredentialsModal').modal('show');
$('.loadingDiv').hide();
},
error:function(){
alert('Error');
}
})
})
Here's my route:
Route::match(['get', 'post'], '/citation-password', 'CitationsController#citationpassword');
And here's the Controller method that generates the form on get and saves the data on post:
public function citationpassword()
{
if (Request::ajax()) {
$data = Request::all();
if (!$data['citationid']) {
return redirect('/citations')
->with('flash-danger', 'Missing citation id for Login credentials form!!');
}
// Save loopback variable if we have it in order to return user to the page where they came from; default return location is citations
$loopback = 'citations';
if (array_key_exists("loopback", $data)) {
$loopback = $data['loopback'];
}
$getcitationdetails = Citation::where('id', $data['citationid'])->select('id', 'site_id', 'username', 'password', 'login_email', 'login_notes')->first();
$getcitationdetails = json_decode(json_encode($getcitationdetails), true);
$getsitedetails = Site::where('id', $getcitationdetails['site_id'])->select(
'id',
'directory_username',
'directory_password',
'security_questions',
'email_account',
'email_account_password',
'email_account_name',
'google_user',
'google_pwd',
'name_of_google_account'
)->first();
$getsitedetails = json_decode(json_encode($getsitedetails), true);
$response ="";
$response .= '<form action="'.url('/citation-password').'" method="post">
<div class="modal-body">';
if (!empty($getsitedetails['directory_username'])) {
$response .= '<div class="form-group">
<label for="recipient-name" class="col-form-label">Default login credentials for this site:</label>
<p>Username: '.$getsitedetails['directory_username'].'
<br />Password: '.$getsitedetails['directory_password'].'
<br />Email account: '.$getsitedetails['email_account'].'
<br />Email password: '.$getsitedetails['email_account_password'].'
<br />Name on email account: '.$getsitedetails['email_account_name'].'
<br />Default security questions: '.$getsitedetails['security_questions'].'</p>
<p>Gmail account: '.$getsitedetails['google_user'].'
<br />Gmail password: '.$getsitedetails['google_pwd'].'
<br />Name on Gmail account: '.$getsitedetails['name_of_google_account'].'</p>
</div>';
}
$response .= '
<input type="hidden" name="_token" value="'.csrf_token() .'" />
<input type="hidden" name="citation_id" value="'.$data['citationid'].'" />
<input type="hidden" name="loopback" value="'.$loopback.'" />
<div class="form-group">
<label for="recipient-name" class="col-form-label">Username:</label>
<input type="text" class="form-control" name="username" value="'.$getcitationdetails['username'].'" autocomplete="off">
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Password:</label>
<input type="text" class="form-control" name="password" value="'.$getcitationdetails['password'].'" autocomplete="off">
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Login email used:</label>
<input type="text" class="form-control" name="login_email" value="'.$getcitationdetails['login_email'].'" autocomplete="off">
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Login notes:</label>
<textarea class="form-control" style="height:130px;" name="login_notes">'.$getcitationdetails['login_notes'].'</textarea>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success" id="success">Save</button>
<button type="button" class="btn btn-danger" data-dismiss="modal" aria-hidden="true">Cancel</button>
</div>
</form>';
return $response;
} else {
// The popup modal has posted back here; process the data
$data = Request::all();
// Handle & translate loopback; returning user to the page where they came from
$loopback = 'citations';
if ($data['loopback']) {
$loopback = $data['loopback'];
// Translate pages it came from
$trackLoopback = new trackLoopback();
$loopback = $trackLoopback->translate($loopback);
}
$updatecitation = Citation::find($data['citation_id']);
$updatecitation->username = $data['username'];
$updatecitation->password = $data['password'];
$updatecitation->save();
return redirect($loopback)
->with('flash-success', 'Login credentials have been updated successfully!');
}
}
In an effort to isolate the error, I even simplified the form in the controller like this:
public function citationpassword()
{
if (Request::ajax()) {
return '<p>This is the modal form!</p>';
} else {
// The popup modal has posted back here; process the data
$data = Request::all();
// Handle & translate loopback; returning user to the page where they came from
$loopback = 'citations';
if ($data['loopback']) {
$loopback = $data['loopback'];
// Translate pages it came from
$trackLoopback = new trackLoopback();
$loopback = $trackLoopback->translate($loopback);
}
$updatecitation = Citation::find($data['citation_id']);
$updatecitation->username = $data['username'];
$updatecitation->password = $data['password'];
$updatecitation->save();
return redirect($loopback)
->with('flash-success', 'Login credentials have been updated successfully!');
}
}
and also simplified the route to this:
Route::get('/citation-password', 'CitationsController#citationpassword');
but all I get when I click the link is a popup notice, "Error."
I'm not experienced with AJAX. How do I get the form to display in Laravel 5.3?
And/or, how can I change the JavaScript function so that it shows the actual error instead of the "Error" notice? (I tried a number of methods I found on StackOverflow to display errors but all of them resulted in NO error notice; just a blank page. And, I've not been successful at getting my Firefox debugger to show the errors either.)
Thanks!

The correct way to debug the JavaScript is to post the errors this way:
<script type="text/javascript">
$(document).on('click','.getCitationdetails',function(){
var citationid = $(this).data('citationid');
var loopback = $(this).data('loopback');
$.ajax({
url : '/citation-password',
type:'post',
data : {citationid :citationid, loopback :loopback},
success:function(resp){
$('#AppendLoginDetails').html(resp);
$('#LoginCredentialsModal').modal('show');
$('.loadingDiv').hide();
},
error: function(xhr, ajaxOptions, thrownError) {
alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
}
})
})
Once you do so, you will see that the error has to do with missing CsrfToken for the form. [The actual error message is from the Laravel framework: Illuminate\Session\TokenMismatchException: in file /home/reviewsites/moxy53/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php on line 6] Since both the get and post verbs use the same route, Laravel is requiring the CsrfToken before the form with the Csrf field gets generated.
It is possible (but NOT recommended!) to exclude this route from CSRF protection by editing App\Http\Middleware\VerifyCsrfToken.php with the following exception:
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'/citation-password',
];
However, a much better approach is to add the token. It is correct that since you are using a post method to send the data values to the controller, you cannot use the controller to generate the token field in the form. Hence, the solution is to take the html out of the controller and put it in the blade. These lines:
$response .= '<form action="'.url('/citation-password').'" method="post">
<div class="modal-body">';
...
</div>
</form>';
should not be in the $response generated by the controller, but should instead be in the modal div in the blade itself. THEN, you can add the CSRF field in the blade thus:
<form action="{{url('/citation-password')}}" method="post">
{{ csrf_field() }}
<div class="modal-body" id="AppendLoginDetails">
</div>
</form>

Related

Route Login is not defined in Laravel

I have one problem, why when I accessing the login/register page always given Route [login] is not defined I already try to search for my problem and nothing result happen, I will give my detail in bellow.
User Controller :
public function __construct() {
$this->middleware('auth:api', ['except' => ['login', 'register']]);
}
in that case, I wanna get the token and user detail after successful login, and it's working I got the token and I got the user detail, but when I logout and back into the login or register page, it's error, and for the message Route [login] is not defined, but when I delete the __construct() it's working, but when I login again with my same account, the token and detail user is giving null value, I will give my route detail in the bellow.
Route :
Route::get('/', function () {
return redirect('auth');
});
Route::get('auth', 'UserController#viewLogin');
Route::get('register', 'UserController#viewRegister');
Route::get('dashboard', 'DashboardController#view');
Route::group(['prefix' => 'auth'], function () {
Route::post('login', 'UserController#login');
Route::post('logout', 'UserController#logout');
Route::get('user-profile', 'UserController#userProfile')->middleware('jwt.verify');
});
Update Login Post :
$user_data = [
'email' => $email,
'password' => $password,
'is_active' => 1
];
$user_credentials = JWTAuth::attempt($user_data);
if (!$user_credentials) {
return response()->json([
'error_message' => 'Your account is not registered yet, please register first'
], 401);
} else {
return response()->json([
'user' => JWTAuth::user(),
'token' => $user_credentials,
'success_message' => "Login Successfuly",
]);
}
Form Login :
<form action="javascript:;" method="POST" autocomplete="off">
#csrf
<div class="form-group">
<label class="font-weight-normal">Email</label>
<div class="input-group">
<input type="email" class="form-control" name="email" placeholder="user#mail.com">
<div class="input-group-append">
<div class="input-group-text input-group-email">
<span class="fas fa-envelope"></span>
</div>
</div>
</div>
<div class="text-danger" id="email-err"></div>
</div>
<div class="form-group">
<label class="font-weight-normal">Password</label>
<div class="input-group">
<input type="password" class="form-control" name="password" placeholder="********">
<div class="input-group-append">
<div class="input-group-text input-group-password">
<span class="fas fa-eye clicked" id="show-hide"></span>
</div>
</div>
</div>
<div class="text-danger" id="password-err"></div>
</div>
<div class="row">
<div class="col-12">
<button type="submit" id="login-process" class="btn btn-block" disabled>Sign In</button>
</div>
</div>
</form>
Javascript :
$("#login-process").on('click', function () {
const emailValue = $("input[name='email']").val();
const passwordValue = $("input[name='password']").val();
$.ajax({
url: "auth/login",
method: "POST",
async: true,
data: {
email: emailValue,
password: passwordValue,
},
success: function (success) {
// describe one by one success method
const accessToken = success['token'];
const successMessage = success['success_message'];
localStorage.setItem('monitoring-barang', accessToken);
window.location.replace('dashboard');
},
});
});
This error is probably related to your XHR request not having the correct Accept header (which should be application/json).
This will make the auth middleware respond, but even though you combined it with api, the lacking Accept header will make the web middleware group respond with it's authentication.
This will effectively fire the redirectTo method in app/Http/Middleware/Authenticate.php where, on line 18, there's a reference to the route named login, a route you have explicitly not added by adding the except parameter to the middleware method in your controller.
The solution is to either comment out redirect out, make a dummy route for the purpose or implement a better strategy like Sanctum

Web form - bots getting past all checks when human cant.?

I have the following code, pretty standard - but it seems that the bots get by without even entering anything into the input fields! where as a normal person cannot since it checks upon submit, so I keep just getting empty emails.
Here is my code, if anyone has any ideas:
the javascript:
$('form.ajax').on('submit', function () {
if($(".field-b").val()) {
return false;
}
var that = $(this),
url = that.attr('action'),
type = that.attr('method'),
data = {};
that.find('[name]').each(function(index, value) {
var that = $(this),
name = that.attr('name'),
value = that.val();
data[name] = value;
});
for(var property in data) {
if(data.hasOwnProperty(property)) {
if(data[property] == "") {
$('[name="' + property + '"]').parent().addClass("error");
return false;
}
}
}
$.ajax({
url: url,
type: type,
data: data,
success: function(response) {
$(".info,.ajax").hide();
$(".success-send").fadeIn(300);
return false;
}
});
return false;
});
The HTML:
<form class="ajax" action="../email.php" method="post"autocomplete="off">
<div class="form-field">
<input name="form_name" type="text" class="form-field-name">
<label>Your Name</label>
<div class="field-icon-name"></div>
</div>
<div class="form-field">
<input name="form_business" type="text" class="form-field-business">
<label>Business</label>
<div class="field-icon-business"></div>
</div>
<div class="form-field">
<input name="form_email" type="email" class="form-field-email">
<label>Email Address</label>
<div class="field-icon-email"></div>
</div>
<div class="form-field">
<input name="form_phone" type="text" class="form-field-phone">
<label>Phone #</label>
<div class="field-icon-phone"></div>
</div>
<div class="form-field special">
<input name="form_b" type="text" class="form-field-b">
<label>question</label>
</div>
<div class="form-field">
<textarea name="form_message"></textarea>
<label>Message</label>
<div class="field-icon-message"></div>
</div>
<button type="submit">Send Message</button>
</form>
And finally the PHP.
<?php
session_start();
$to = "myemail#domain.com";
$name = $_POST['form_name'];
$phone = $_POST['form_phone'];
$email = $_POST['form_email'];
$business = $_POST['form_business'];
$email = $_POST['form_email'];
$subject = 'Contact Form mattscorner';
$message = 'Name:'.$name.'\n Email + phone:'.$email.", ".$phone."\n Business: ".$business."\n\n".$message;
$headers = "From: $email\n";
$headers .= "MIME-Version: 1.0\n";
$headers .= "Content-type: text/html; charset=iso-8859-1\n";
mail($to, $subject, $message, $headers);
if (mail($to, $subject, $message, $headers)) echo "mail sent"; else echo "mail NOT sent";
?>
It's pretty standard code I think, but I just cant seem to get the bots to even get blocked by the field requirements.
I literally get emails that are just:
Name:\n Email + phone:, Business:
There's a fundamental misunderstanding here. Bots don't run JavaScript, so none of that matters, and you're not doing the same validations on the server side, so they have no trouble getting by. Implement your validations in PHP too.
Also, you really need to do more validation for security anyway - your script is open to all kinds of vulnerabilities, and you're building malformed, non-compliant messages.
I suggest you use an email library such as PHPMailer, which you tagged this question with.

How to use cashier package in laravel 5.2?

I am creating a user subscription plan, For this I am using cashier package in laravel 5.2. I am following the exact way in provided in the tutorial given in laravel document https://laravel.com/docs/5.2/billing. But I am getting the error
ErrorException in FacebookScraperController.php line 1767:
Undefined variable: creditCardToken
my controller code:
$user = User::find(2);
$res = $user->newSubscription('main', 'monthly')->create($creditCardToken);
dd($res);
What should I pass the value inside the $creditCardToken variable.
I tried to give the card details inside this variable. But getting error.
Please help me out.
You will need to pass the subscription plan here with the token generated at the time of card entry.
Here is the step you can follow.
create a view page:
<form action="/subscription" method="POST" id="payment-form">
<span class="payment-errors"></span>
<div class="form-row">
<label>
<span>Card Number</span>
<input type="text" size="20" data-stripe="number">
</label>
</div>
<div class="form-row">
<label>
<span>Expiration (MM/YY)</span>
<input type="text" size="2" data-stripe="exp_month">
</label>
<span> / </span>
<input type="text" size="2" data-stripe="exp_year">
</div>
<div class="form-row">
<label>
<span>CVC</span>
<input type="text" size="4" data-stripe="cvc">
</label>
</div>
<input type="submit" class="submit" value="Submit Payment">
</form>
<script src="//code.jquery.com/jquery-1.9.1.js"></script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.validate/1.9/jquery.validate.min.js"></script>
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
Stripe.setPublishableKey('pk_test_TSGgkchoa9iQU4ZQ628a8Auz');
</script>
<script>
$(function() {
var $form = $('#payment-form');
$form.submit(function(event) {
// Disable the submit button to prevent repeated clicks:
$form.find('.submit').prop('disabled', true);
// Request a token from Stripe:
Stripe.card.createToken($form, stripeResponseHandler);
// Prevent the form from being submitted:
return false;
});
});
function stripeResponseHandler(status, response) {
// Grab the form:
var $form = $('#payment-form');
if (response.error) { // Problem!
// Show the errors on the form:
$form.find('.payment-errors').text(response.error.message);
$form.find('.submit').prop('disabled', false); // Re-enable submission
} else { // Token was created!
// Get the token ID:
var token = response.id;
// Insert the token ID into the form so it gets submitted to the server:
$form.append($('<input type="hidden" name="stripeToken">').val(token));
// Submit the form:
$form.get(0).submit();
}
};
</script>
and in your controller:
public function subscription(Request $request)
{
$user = User::find(1);
$creditCardToken = $request->stripeToken;
$res = $user->newSubscription('main', 'pro')
->trialDays(30)
->create($creditCardToken, [
'plan' => 'pro',
'email' => $user->email,
]);
}

Weird behaviour of CI after a redirect()

I have a strange bug during the password recovery process.
When a user loses his pwd, the app send an email with a token inside the recovery link ( http://localhost/reset-password/f38fd00aa975b28c70f54d948d20de40 for exemple ) This token is an unique key inside the user table.
In the routes.php, i have :
$route['reset-password/(:any)'] = "/user/reset_password_form/$1";// new password form
$route['reset-password'] = "/register/reset_password"; //simple email form
then, reset_password_form generates a form with the token as hidden input :
public function reset_password_form($hash = NULL) { //create form to change password, with user validation hash inside
$user_id = $this->user_model->get_id_by_confirmation_code(strip_tags($hash));
if (isset($user_id)) {
$this->data['validation_code'] = $hash;
$this->data['title'] = $this->lang->line('user_title_password_edit', FALSE);
$this->template->load('default', 'register/reset_password_form', $this->data);
}
else{
$this->session->set_flashdata('error', $this->lang->line('user_error_reset_password', FALSE));
redirect('reset-password');
}
the view:
<?php $attributes = array('class' => '');
echo form_open('user/edit_password', $attributes) ?>
<input type="hidden" id="validate" name="validate" value="<?=$validation_code?>">
<div class="form-group">
<input type="password" id="password" name="password" placeholder="Password" class="form-control" value="<?php echo set_value('password'); ?>">
</div>
<div class="form-group">
<input type="password" id="password_confirm" name="password_confirm" placeholder="Password confirmation" class="form-control">
</div>
<button type="submit" name="submit" class="btn btn-success">Change password</button>
</form>
Finally, the user/edit_password function changes the user password with a new one.
public function edit_password() { //get new password and change it
$this->form_validation->set_rules('password', 'Password', 'trim|required|min_length[6]');
$this->form_validation->set_rules('password_confirm', 'Confirm Password', 'trim|required|matches[password]');
$this->form_validation->set_rules('validate', 'Validate', 'trim|alpha_numeric|required');
if ($this->form_validation->run() === false) {
//STRANGE BUG
$URL = '/reset-password/'.$this->input->post('validate');
$this->session->set_flashdata('error', validation_errors());
redirect($URL);
}
else {
//change pssword
}
}
The bug happen when the form validation fail : i'm suposed to be redirected to the previous form ( /reset-password/hash) with a flashdata error message, but the error message dont display.
Much more weird : even if i'm on the right form ( but without error message) if i decides to click on another menu item (for exemple /home) , it immediately displays the /reset-password form ( /register/reset_password in the routes) with the error message i was supposed to get previously.
As if the full php instruction was kept in stamp and launched after whatever action.
PS : as edit_password() and reset_password_form() are in the same controller, i could have used $this->reset_password_form($hash) instead of redirect() but it has exactly the same effect !
ps2: here is the register/reset_password:
public function reset_password() {
//display forgotten password form page
$this->data['title'] = 'Forgotten password';
$this->template->load('default', 'register/reset_password', $this->data);
}
you recovery link http://localhost/reset-password/f38fd00aa975b28c70f54d948d20de40 is not finding controller. CI is looking for your token number as controller

How do I redirect after ajax request in angularjs?

I am using angularjs and nodejs for my project. Now after I do my authentication using background call. Now after I receive my successful authentication, how do I redirect the user to dashboard? Here is my login div:
<div ng-controller="loginCtrl" class="control">
<form role="form" name="docRegForm" ng-submit="login()" enctype="multipart/form-data" class="col-xs-11 div-center">
<div class="input-group">
<input id="exampleInputEmail1" type="text" placeholder="Username" ng-model="user.username" class="form-control"/>
</div>
<div class="input-group">
<input id="exampleInputPassword1" type="password" placeholder="Password" ng-model="user.password" class="form-control"/>
</div>
<div class="col-xs-12 div-center">
<button type="submit" class="btn btn-danger full-width">LOGIN</button>
And my angular controller is:
app.controller('loginCtrl', function ($scope, $http, $window) {
$scope.message = '';
$scope.login = function () {
$http
.post('/authenticate', $scope.user)
.success(function (data, status, headers, config) {
$window.localStorage.nimbusToken = data.token;
console.log($window.localStorage.token);
$scope.message = 'Welcome';
};
alerts[data.status];
})
.error(function (data, status, headers, config) {
// Erase the token if the user fails to log in
alert("failure");
delete $window.localStorage.token;
// Handle login errors here
$scope.message = 'Error: Invalid user or password';
});
};
});
Now after the login, I have to redirect to dashboard or to relogin, if failed login. How do I do that?
If $location.path(''), not works for you, than try these:
// similar behavior as an HTTP redirect
window.location.replace("your path.");
or:
window.location.replace("your path.")
In success use:-
$location.path("/dashboard");
In error use:-
$location.path("/login");

Resources