I'm trying to localize my users throught their IP address. As the docs say, a class method called geocode_ip_address has been mixed into the ActionController::Base. But there must be something I'm missing. Do I have to define a filter like this before_filter :geocode_ip_address to use it? (I want to know the location for every request done).
The documentation also talks about "A first-time lookup will result in the GeoLoc class being stored in the session as :geo_location" but I certainly don't have that key inside the session hash.
What am I doing wrong?
Thanks.
You don't need to prepend before_filter to geocode_ip_address, but rather just put that in your controller:
class YourController < ApplicationController
geocode_ip_address
def action
if geo = session[:geo_location]
# geo here is a Geokit::GeoLoc object
end
end
end
Note that if the geocoding fails geo will be nil. If you're running in development you'll be trying to geocode 127.0.0.1, so you'll want to set your remote_ip in the request object. I did so by adding this to the bottom of config/environments/development.rb:
class ActionDispatch::Request
def remote_ip
"x.x.x.x" # fill in your IP address here
end
end
Related
I'm using Phoenix, and in router.ex, I have defined a function that verifies the user's JWT token, before continuing on to routing through the api, in order to protect the routes. However, I am having the issue that if, say, I return a status of 403 in the plug, it continues routing and I end up getting an error as I am both accessing the route and disallowing access.
My code looks something like this:
def validate(conn, _opts) do
if !Map.has_key?(conn.cookies, "jwt") do
conn
|> send_resp(403, "Forbidden")
# I would like to have something here that stops further
# routing - i.e. a way to say 'already sent a response; don't keep going'
end
end
Here is an excerpt from Plug.Conn.send_resp/3
Note that this function does not halt the connection, so if subsequent plugs try to send another response, it will error out. Use halt/1 after this function if you want to halt the plug pipeline.
I am wondering how to limit the connection to a channel or the streaming of messages over a channel in rails5. Currently I groups with users in the groups working with pundit and the connection to the websocket happens within that group. If a malicious user randomly guessed groups they could potentially read a message over a socket they shouldn't.
When you create a new message the following code is run in my controller:
if message.save
ActionCable.server.broadcast(
"messages_{message.groupchat_id}_channel",
message: message.content,
user: message.user.email
)
head :ok
end
I have no idea what I'm doing.
Here's the solution I found to use Pundit with Activecable.
First we need access to the user model. You can do that by following the instructions in the Action Cable Overview - Connection Setup. Mainly you need to change the code in connection.rb
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
Note: you may need to use a different way of finding your user. I'm using devise_token_auth and so needed to pass the uid, token, and client_id to the connection.rb and then got the user via this code:
if user && user.valid_token?(token, client_id)
user
else
reject_unauthorized_connection
end
I mention this just because how you get your user may vary. The main thing is that you need to use identified_by current_user and set it.
Another thing which I did not immediately find in the documentation, is that the current_user is now accessible by your channels. Since the user name may be different than your pundit_user name, I found it easiest to manually pass the user to Pundit at that point. So in subscribing with my channel file, I had this code:
def subscribed
message = MessagePolicy::Scope.new(self.current_user, Project).resolve.find(params[:message])
stream_for message
end
You could of course also manually authorize this way, instead of using Scope:
MessagePolicy.new(self.current_user, message).show?
You can add another layer of security for actioncable connections in app/channels/application_cable/connection.rb file.
You can follow this tutorial. I think this give you some idea:
https://www.learnenough.com/action-cable-tutorial#sec-login_protection
I have three different API calls happening when a record is created.
1) Call to create a bitly URL for the record.
2) Call to post to facebook with the bitly URL
3) Call to post to twitter with the bitly URL
My current process is as follows:
On record Create and Update
respond_to do |format|
if #dealer.save
call_bitly_api
end
end
in my model:
after_save :social_media_posting
def social_media_posting
if (self.bitly_url.present? && self.posted_to_facebook == false)
call_facebook_post
end
if (self.bitly_url.present? && self.posted_to_twitter == false)
call_twitter_post
end
end
The issue that I am facing is that the facebook and twitter post is being called on first save where the bitly_url is not yet created.
Need help is figuring out how to add these calls that they can still happen yet they happen in order and wait for the bitly_url to be present before the call the facebook and twitter is made? Major thing worth mentioning I am using sidekiq to make the calls and send the actual call to the sidekiq worker to work in the background.
In controller:
CallBitlyWorker.perform_async(dealer.id)
In your worker:
class CallBitlyWorker
include Sidekiq::Worker
def perform(dealer_id)
dealer = Dealer.find(dealer_id)
# make bitly call
dealer.update_attribute(:bitly_url, some_url)
# make the social media calls in parallel
CallFacebook.perform_async(dealer_id)
CallTwitter.perform_async(dealer_id)
end
end
ActiveRecord callbacks should be avoided where possible. They just make your code more opaque.
I found this great tutorial on Github about how to create a custom mailer from Devise. https://github.com/plataformatec/devise/wiki/How-To:-Use-custom-mailer
I implemented the code and the logic makes complete sense to me however when I deploy, I can't get the code to work when I set my config.mailer = 'MyMailer' in initialize/devise.rb. There are no errors in the terminal and it seems that everything got processed just like normal however the email never makes it to the inbox.
If I change config.mailer = 'Devise::Mailer', the email gets delivered correctly. However since MyMailer inherits from Devise::Mailer, shouldn't it work?
Is anyone else having this issue? Any ideas what I can do to troubleshoot?
Here is my "mailers/my_mailer.rb":
class MyMailer < Devise::Mailer
helper :application # gives access to all helpers defined within `application_helper`.
# helper :devise # gives access to all helpers defined within `devise_helper`.
#include Devise::Controllers # Optional. eg. `confirmation_url`
include Devise::Mailers::Helpers
def invoice_payment_failed_email(user, listing)
#user = user
#listing = listing
#url = 'http://example.com/login'
mail(to: #user.email, subject: 'Payment Failed')
end
end
Here is my "initializers/devise.rb":
Devise.setup do |config|
config.secret_key = ENV["DEVISE_SECRET_KEY"]
# Configure the class responsible to send e-mails.
config.mailer = 'MyMailer'
Here is my call to the deliver the mail:
# email customer
MyMailer.invoice_payment_failed_email(#user, #listing).deliver
*Update:
OMG... after a whole day of racking my brain trying to figure this out, it turns out that the code logic is fine but the problem was that my From: needed to match the Postmark sender accounts. This is why I wasn't getting any terminal errors. The deliver request was being sent correctly to Postmark but since my From address wasn't matching, postmark just never delivered the email!
I need to write a log when somebody failes to log in to my app (to track bruteforce attempts). Also I decided to log successful authentications.
So I created a SessionsController < Devise::SessionsController and tried to override the sessions#create method like that: https://gist.github.com/3884693
The first part works perfectly, but when the auth failes rails throws some kind of an exception and never reaches the if statement. So I don't know what to do.
This answer to a previous SO question - Devise: Registering log in attempts has the answer.
The create action in the devise controller calls warden.authenticate!, which attempts to authenticate the user with the supplied params. If authentication fails then authenticate! will call the devise failure app, which then runs the SessionsController#new action. Note, any filters you have for the create action will not run if authentication fails.
So the solution is to add a filter after the new action which checks the contents of env["warden.options"] and takes the appropriate action.
I tried out the suggestion, and was able to log both the successful & failed login attempts. Here is the relevant controller code:
class SessionsController < Devise::SessionsController
after_filter :log_failed_login, :only => :new
def create
super
::Rails.logger.info "\n***\nSuccessful login with email_id : #{request.filtered_parameters["user"]}\n***\n"
end
private
def log_failed_login
::Rails.logger.info "\n***\nFailed login with email_id : #{request.filtered_parameters["user"]}\n***\n" if failed_login?
end
def failed_login?
(options = env["warden.options"]) && options[:action] == "unauthenticated"
end
end
The log has the following entries:
For a successful login
Started POST "/users/sign_in"
...
...
***
Successful login with email_id : {"email"=>...
***
...
...
Completed 302 Found
For a failed login
Started POST "/users/sign_in"
...
...
Completed 401 Unauthorized
Processing by SessionsController#new as HTML
...
...
***
Failed login with email_id : {"email"=>...
***
...
...
Completed 302 Found
Prakash's answer is helpful, but it's not ideal to rely on SessionsController#new to be run as a side effect. I believe this is cleaner:
class LogAuthenticationFailure < Devise::FailureApp
def respond
if request.env.dig('warden.options', :action) == 'unauthenticated'
Rails.logger.info('...')
end
super
end
end
...
Devise.setup do |config|
config.warden do |manager|
manager.failure_app = LogAuthenticationFailure
end
Check out Graeme's answer if you'd prefer to hook into Warden's callbacks (Devise is implemented using Warden).
I had the same question but was unable to resolve it using the "warden.options" since, in my case, these were being cleared before redirecting to the sessions#new action. After looking into a few alternatives that I judged to be too brittle (because they involved extending some Devise classes and aliasing existing methods), I wound up using some callbacks provided by Warden. It works better for me because the callback is invoked inside the current request-response cycle and the parameters are all preserved in the env object.
These callbacks are named and appear to be designed to solve this and related problems. And they are documented!
Warden supports the following callbacks as of warden-1.2.3:
after_set_user
after_authentication (useful for logging successful sign ins)
after_fetch (alias for after_set_user)
before_failure (useful for logging failed sign ins - example below)
after_failed_fetch
before_logout
on_request
Each callback is set directly on the Warden::Manager class (may be inside config/initializers/devise.rb). To track a failed authentication attempt I added this:
Warden::Manager.before_failure do |env, opts|
email = env["action_dispatch.request.request_parameters"][:user] &&
env["action_dispatch.request.request_parameters"][:user][:email]
# unfortunately, the User object has been lost by the time
# we get here; so we take a db hit because I care to see
# if the email matched a user account in our system
user_exists = User.where(email: email).exists?
if opts[:message] == :unconfirmed
# this is a special case for me because I'm using :confirmable
# the login was correct, but the user hasn't confirmed their
# email address yet
::Rails.logger.info "*** Login Failure: unconfirmed account access: #{email}"
elsif opts[:action] == "unauthenticated"
# "unauthenticated" indicates a login failure
if !user_exists
# bad email:
# no user found by this email address
::Rails.logger.info "*** Login Failure: bad email address given: #{email}"
else
# the user exists in the db, must have been a bad password
::Rails.logger.info "*** Login Failure: email-password mismatch: #{email}"
end
end
end
I expect that you could use the before_logout callback to track logout actions as well, but I haven't tested it. There appear to be prepend_ variants of the callbacks as well.
For logout logging, you need to catch the destroy event, so add the following to the Session controller (from the above answer):
before_filter :log_logout, :only => :destroy #add this at the top with the other filters
def log_logout
::Rails.logger.info "*** Logging out : #{current_user.email} ***\n"
end
I've found another way to do this, if you want, for example, display a custom message if login fails.
In my job, if login fails we check the activity status (custom logic) and display a message, no matter if the login was correct or not.
After debug a little bit and read warden docs I know this now: Warden executes a throw(:warden, opts), so, according to ruby docs, a throw must be captured inside a catch block.
def create
flash.clear
login_result = catch(:warden) { super }
return unless login_failed?(login_result)
email = params[:user][:email]
flash[:alert] = # here I call my service that calculates the message
redirect_to new_user_session_path
end
def login_failed?(login_result)
login_result.is_a?(Hash) && login_result.key?(:scope) && login_result.key?(:recall)
end
throw docs:
https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-throw
catch docs:
https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-catch
Building on Prakash Murty's answer, I think the approach in this answer (https://stackoverflow.com/a/34816998/891359) is a cleaner way to log a succesfull login attempt. Instead of calling super, Devise offers a way to pass a block that is yielded before the view is rendered.
So instead of doing this:
class SessionsController < Devise::SessionsController
def create
super
::Rails.logger.info "\n***\nSuccessful login with email_id : #{request.filtered_parameters["user"]}\n***\n"
end
end
It is cleaner to do:
class SessionsController < Devise::SessionsController
def create
super do |user|
::Rails.logger.info "\n***\nSuccessful login with email_id : #{user.email}\n***\n"
end
end
end