Capturing Google Analytics events on validation failures in YII2 - yii

In our YII app, we would like to capture google analytics on validation failures. We are using modal rules for validating our data. Here is one of our validation modal code snippet
public function rules() {
return [
['email', 'validateEmail'],
];
}
public function validateEmail($attribute_name, $params) {
if (EMAIL EXITS IN DATABASE) {
$this->addError($attribute_name, Yii::t('app', 'Email Already Exist.'));
return false;
}
}
It correctly shows an error message on our frontend but we can not track this in google analytics. We would like to call a javascript function whenever this validation fails. Here is our javascript function
function captureGAEvent(category, action, label) {
alert(category + " " + action + " " + label);
ga('send', 'event', category, action, label);
}
Please guide.

yii2 documentation states that you could set up Client Side Validation
Which one has a clientValidateAttribute method Implementing Client Side Validation where you could put your analytics code as well.
You will need to enable enableClientValidation in your ActiveForm.
I would do following based on Deferred Validation paragraph.
Create a Validator Class
class UserEmailValidator extends Validator
{
public function init()
{
parent::init();
$this->message = 'Email Already Exist..';
}
public function validateAttribute($model, $attribute)
{
if (EMAIL EXISTS IN DATABASE) {
$this->addError($attribute, Yii::t('app', $this->message));
return false;
}
}
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
deferred.push($.get("/check-email-exists", {value: value}).done(function(data) {
if ('' !== data) {
messages.push(data);
alert(category + " " + action + " " + label);
ga('send', 'event', category, action, label);
}
}));
JS;
}
}
Use it in your model
public function rules() {
return [
['email', UserEmailValidator::class],
];
}

Related

Laravel /broadcasting/auth Always forbidden with 403 Error

I tried many solutions but no one works for me
I've installed Laravel echo and pusher js and Pusher/Pusher
#bootstrap.js
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
encrypted: true,
});
#.env
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=my_id
PUSHER_APP_KEY=my_key
PUSHER_APP_SECRET=my_secret
PUSHER_APP_CLUSTER=eu
my event file NewMessage
class NewMessage implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Message $message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('messages.'. $this->message->to);
}
public function broadcastWith()
{
return ["message" => $this->message];
}
}
channel.php
Broadcast::channel('messages.{id}', function ($user, $id) {
return $user->id === (int) $id;
});
Vue App JS code
mounted(){
Echo.private(`messages${this.user.id}`)
.listen('NewMessage', (e) => {
this.handleIncoming(e.message)
});
},
methods:{
saveNewMessage(msg){
this.messages.push(msg);
},
handleIncoming(message){
if(this.selectedContact && message.from == this.selectedContact.id ){
this.saveNewMessage(message);
return;
}
alert(message.text);
}
}
Api.php
Route::post('/conversation/send', 'Api\ContactController#sendNewMessage');
Contact Controller
public function sendNewMessage(Request $request)
{
$message = Message::create([
'from' => $request->sender_id,
'to' => $request->receiver_id,
'text' => $request->text
]);
broadcast(new NewMessage($message));
return response()->json($message);
}
I also read the official documentation everything is going good but I didn't figure out why, it's a throwing error. Have any idea?
I figure out why it is every time shows auth forbidden or doesn't display auth
Solution:
you need to double-check your PUSHER_APP_KEY because if it is not set correctly, it will through error because our stream not connected with pusher
PUSHER_APP_KEY="PUT KEY HERE"
If you are very sure that your app key is correct then go to the Network tab and click on your pusher app key which like e70ewesdsdssew0
If it is displaying the result like this
{"event":"pusher:connection_established","data":"{\"socket_id\":\"131139.31305364\",\"activity_timeout\":120}"}
your API key is good
if it not correct it will display an error like this
{"event":"pusher:error","data":{"code":4001,"message":"App key 3fdsfdfsdfsd not in this cluster. Did you forget to specify the cluster?"}}
Also, don't forget to put the cluster key
PUSHER_APP_CLUSTER=eu

How to add captcha required only for a particular condition yii2

I am trying make the captcha field required only when the number of failed login attempts exceed 3 times. For which I have written below code till now.
In LoginForm model I have added the below rules
public function rules()
{
return [
[['username', 'password'], 'required'],
['password', 'validatePassword'],
['verifyCode', 'captcha', 'when' => function($model) {
return $this->checkattempts();
}],
];
}
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addLoginAttempt($user->id);
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
public function checkattempts()
{
$user = $this->getUser();
$ip = $this->get_client_ip();
$data = (new Query())->select('*')
->from('login_attempts')
->where(['ip' => $ip])->andWhere(['user_ref_id' => $user->id])
->one();
if($data["attempts"] >=3){
return false;
}else{
return true;
}
}
public function addLoginAttempt($uid) {
$ip = $this->get_client_ip();
$data = (new Query())->select('*')
->from('login_attempts')
->where(['ip' => $ip])->andWhere(['user_ref_id' => $uid])
->one();
if($data)
{
$attempts = $data["attempts"]+1;
#Yii::$app->db->createCommand("UPDATE login_attempts SET attempts=".$attempts." where ip = '$ip' AND user_ref_id=$uid")->execute();
}
else {
Yii::$app->db->createCommand("INSERT into login_attempts (attempts, user_ref_id, ip) VALUES(1,'$uid', '$ip')")->execute();
}
}
Here I am validating the password first. If the password is incorrect then I am incrementing the count by 1. This part is working fine. The count is incrementing successfully.
After this I am trying to get the count of failed attempts while validating captcha using the function checkattempts(), but it is not working.
Can anyone please tell me where I have made mistake.
Thanks in advance.
In your model:
if (!$model->checkattempts())
//show the captcha
Then, in your model rules you'll need something like:
['captcha', 'captcha'],
In your case, what you can do is use different scenarios depending on the user attempts, and in one scenario (more than X attempts) make the captcha required.
More documentation about the captcha and about scenarios.

Laravel API give json response when got error This action is unauthorized

I newbie in Laravel API. There is an update function which only allows users to update their own post. It worked. When users try to update other user's post, it alswo worked, but it shows the error like this image. Actually i want it show in response json.
I want to show message like this
{
"status": "error",
"message": "This action is unauthorized",
}
This is my code for PostController.
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
//this will check the authorization of user but how to make if else statement, if the post belong to the user it will show this json below but if the post belong to other, it will show error message(response json)
$post->content = $request->get('content', $post->content);
$post->save();
return fractal()
->item($post)
->transformWith(new PostTransformer)
->toArray();
}
This code for PostPolicy
public function update(User $user, Post $post)
{
return $user->ownsPost($post);
}
This is code for User model
public function ownsPost(Post $post)
{
return Auth::user()->id === $post->user->id;
}
This code for AuthServiceProvider
protected $policies = [
'App\Post' => 'App\Policies\PostPolicy',
];
Hope anyone can help me.
I'm using Laravel 5.4
In the app/Exceptions/Handler.php class you can change the render function like so
public function render($request, Exception $exception)
{
$preparedException = $this->prepareException($exception);
if ($preparedException instanceof HttpException) {
return response(
[
'message' => sprintf(
'%d %s',
$preparedException->getStatusCode(),
Response::$statusTexts[$preparedException->getStatusCode()]
),
'status' => $preparedException->getStatusCode()
],
$preparedException->getStatusCode(),
$preparedException->getHeaders()
);
}
return parent::render($request, $exception);
}
Or if you look further in the rendering, overriding the renderHttpException might be a little safer. This will remove the custom error pages in views/errors
protected function renderHttpException(HttpException $e)
{
return response(
[
'message' => sprintf(
'%d %s',
$e->getStatusCode(),
Response::$statusTexts[$e->getStatusCode()]
),
'status' => $e->getStatusCode()
],
$e->getStatusCode(),
$e->getHeaders()
);
}

How do I define the response to a missing claim in NancyFX?

I have a module meant to enable administrators to manage users. Of course, this module requires not only authentication but also a specific claim. I have discovered that, if you are missing the claim in question, you actually get only a blank page as a response to your request. This isn't ideal.
How can I change that?
Module code below (if anyone needs to look)...
public class UserModule : NancyModule
{
public UserModule()
: base("/users")
{
this.RequiresAnyClaim(new[] { "evil-dictator" });
Get["/"] = _ =>
{
ViewBag.UserName = Context.CurrentUser.UserName;
return Negotiate.WithView("Index");
};
// Generate an invitation for a pre-approved user
Get["/invite"] = _ =>
{
throw new NotImplementedException();
};
}
}
You can use an After hook to alter the response in case the claim is missing. Note that the response you get when you do not have the required claim has HTTP status code 403 Forbidden. Check for that in the After hook and alter the response as needed.
E.g. the following will redirect to the root - "/" - of the application, when the user does have the evil dictator claim:
public class UserModule : NancyModule
{
public UserModule()
: base("/users")
{
After += context =>
{
if (ctx.Response.StatusCode == HttpStatusCode.Forbidden)
ctx.Response = this.Response.AsRedirect("/");
}
this.RequiresAnyClaim(new[] { "evil-dictator" });
Get["/"] = _ =>
{
ViewBag.UserName = Context.CurrentUser.UserName;
return Negotiate.WithView("Index");
};
// Generate an invitation for a pre-approved user
Get["/invite"] = _ =>
{
throw new NotImplementedException();
};
}
}

MVC4 .Net , controls on hidden fields

I wonder if it's possible to have controls (dataanotation) on hidden fields (HiddenFor or hidden EditorFor) ?
I don't think so, but we never know.
There are a lot of posts on how to hide EditorFor such as :
TextBoxFor vs EditorFor, and htmlAttributes vs additionalViewData
In my case,in a view I have a jquery call to a WCF REST service, that in success case fill my EditorFor. I would like that the Required DataAnotation to be applied on that EditorFor, would it be possible ?
I think that as long as the EditorFor is invisible the DataAnotation cannot be applied. Would it have a way to apply the DataAnotation on the hidden EditorFor ?
Here is the code :
To hide the EditorFor :
#Html.EditorFor(model => model.VilleDepart, "CustomEditor", new {style = "display:none;" })
The CustomEditor :
#{
string s = "";
if (ViewData["style"] != null) {
// The ViewData["name"] is the name of the property in the addtionalViewData...
s = ViewData["style"].ToString();
}
}
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { style = s })
the model :
string _VilleDepart;
[Required]
[Display(Name = "Ville Départ")]
public string VilleDepart
{
get
{
if (Commune != null) {
return Commune.Commune1;
}
return _VilleDepart;
}
set {
_VilleDepart = value;
}
}
The JQuery call to WCF REST Service :
$(document).ready(function () {
$([document.getElementById("IVilleDepart"), document.getElementById("IVilleArrivee")]).autocomplete({
source: function (request, response) {
$.ajax({
cache: false,
type: "GET",
async: false,
dataType: "json",
url: GetSearchCommunetURl + "(" + request.term + ")",
success: function (data) {
//alert(data);
response($.map(data, function (item) {
return {
label: item['Commune'] + ' (' + item['CodePostal'] + ')',
val: item
}
}))
},
error: function (response) {
alert("error ==>" + response.statusText);
},
failure: function (response) {
alert("failure ==>" + response.responseText);
}
});
},
select: function (e, i) {
if (e.target.id == "IVilleDepart") {
VilleDepart = i.item.val;
EVilleDepart.value = VilleDepart.Commune;
ECodePostalDepart.value = VilleDepart.CodePostal;
ECodeINSEEDepart.value = VilleDepart.CodeINSEE;
}
if (e.target.id == "IVilleArrivee") {
VilleArrivee = i.item.val;
EVilleArrivee.value = VilleArrivee.Commune;
ECodePostalArrivee.value = VilleArrivee.CodePostal;
ECodeINSEEArrivee.value = VilleArrivee.CodeINSEE;
}
},
minLength: 2
});
});
If I don't hide the EditorFor I can see it is correctly filled after the WCF REST service call and the Required DataAnotation is applied.
There are other way to hide the EditorFor, for instance to apply the style='width:0px;height:0px'
It hides but disable the Required DataAnotation,
if I apply the style='width:0px;height:1px', we don't see a lot of the EditorFor but the Required DataAnotation is active.
I've seen an answer at http://www.campusmvp.net/blog/validation-of-hidden-fields-at-the-client-in-asp-net-mvc
(but it seems i had badly searched precedently, the validation of hidden field is treated in some blogs and sites).
To active the validation of hidden fields, you just have to add this little javascript line :
$.validator.setDefaults({ ignore: null });
and it works !
Apparently it doesn't work with mvc2, but works since mvc3.