Rails web socket - authorization - ruby-on-rails

I have Ember.js client and RoR server side with gem "websocket-rails".
I need to get private channel with authorization to send messages only for one user.
This is how I try:
class AuthorizationController < WebsocketRails::BaseController
before_action :require_login
def authorize_channels
# The channel name will be passed inside the message Hash
puts '*'*400
ap current_user//Here I see the user
channel = WebsocketRails[message[:channel]]
accept_channel current_user
end
def require_login
sign_in(User.find(4))
end
end
My events.rb is:
WebsocketRails::EventMap.describe do
namespace :websocket_rails do
subscribe :subscribe, :to => AuthorizationController, :with_method => :authorize_channels
end
end
But when I try to send message with:
WebsocketRails.users[4].trigger(:test_event, { message: 'This is a private message from WebSocket Rails' } )
I get error #<NoMethodError: undefined method ``trigger' for #<WebsocketRails::UserManager::MissingConnection:0x007ff5bb50dc88>>
And if I try to print WebsocketRails.users I see:
#<WebsocketRails::UserManager:0x007ff5bb617d68 #users={}>
What is wrong?
Thx a lot!

If u use rails-api gem, it disables a session, that was the problem.

Related

Logout function not working React-Native to Ruby on Rails? I can't delete the session

I am having trouble with my authentication actions from react-native to my ruby on rails backend api. I thought my signup/ sign in actions were working fine until I made my logout action and I began receiving a rails controller error. My logout function, requests a delete of user's session and can't find it, but my login/signup functions have no problem creating a session.
My Sessions#Controller:
class SessionsController < ApplicationController
def create
#user = User.find_by_credentials(
params[:user][:username],
params[:user][:password]
)
if #user
login(#user)
render "users/show.json.jbuilder"
else
render json: ["Invalid username/password combination"], status: 401
end
end
def destroy
#user = current_user # I believe the problem is here, as it can't find current_user see
# application controller below
if #user
logout
render json: {}
else
render json: ["Nobody signed in"], status: 404
end
end
end
Application#controller , where I find the current user based on the session_token
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
helper_method :current_user, :logged_in?, :logout
# private
def current_user
return nil unless session[:session_token]
#current_user ||= User.find_by(session_token: session[:session_token])
end
def logged_in?
!!current_user
end
def login(user)
user.reset_session_token!
session[:session_token] = user.session_token
#current_user = user
end
def logout
current_user.reset_session_token!
session[:session_token] = nil
#current_user = nil
end
end
My logout action:
export const testLogout = () => dispatch => {
dispatch(loadSpinner());
axios({
method: 'delete',
url: 'http://10.0.2.2:3000/session',
})
.then((response) => {
console.log("response: ", response);
if(reponse.status === 200) {
{dispatch(logoutCurrentUser(response))}
} else {
console.log("status", response.status);
};
});
};
My routes:
Rails.application.routes.draw do
resources :users, only: [:create, :show]
resource :session, only: [:create, :destroy, :show]
end
The errors (1):
Started DELETE "/session" for 127.0.0.1 at 2020-05-27 10:23:23 -0700
Processing by SessionsController#destroy as HTML
Completed 404 Not Found in 0ms (Views: 0.2ms | ActiveRecord: 0.0ms).
(2):
Possible Unhandled Promise Rejection (id: 0):
Error: Request failed with status code 404
Error 2 from the testLogout() function never even makes it to the .then(response) in the function.
Now this 404 error is my own from the #destroy method in my sessions_controller and I am a bit unsure how to get a better error here but from what I am reading online is that react-native handles browser cookies differently and I am concerned I am missing something conceptually but I am very unsure of what it is. I know I could use AsyncStorage for persisting sessions but how does that factor in with the back-end rails authentication? Any help figuring out how to log out a user is greatly appreciated thank you!
I highly recommend using some form of a JWT for authentication handling. Using sessions with an external client is certainly possible, but it's a headache you probably don't want to deal with. Read about CORS. In short, you don't want to deal with CORS if you don't have to.
This seems like a fairly decent guide if you're using devise: https://medium.com/#eth3rnit3/crud-react-native-ror-backend-with-devise-auth-token-4407cac3aa0b

Passing Gibbon (Mailchimp) error from service to controller

I am using Mailchimp (via the Gibbon gem) to add email addresses to my Mailchimp mailing list, and I want to handle any errors that are returned by Mailchimp and display them in my view.
Here is my Pages controller:
class PagesController < ApplicationController
def subscribe
email = subscriber_params[:email]
if email.empty?
flash[:error] = 'Please provide an email.'
redirect_to root_path
else
subscriber = Mailchimp.new.upsert(email)
if subscriber
flash[:success] = 'You\'re in!'
redirect_to root_path(subscribed: :true)
else
# Return error coming from Mailchimp (i.e. Gibbon::MailChimpError)
end
end
end
end
And here is the app > services > mailchimp.rb file I set up to separate out the Mailchimp logic:
class Mailchimp
def initialize
#gibbon = Gibbon::Request.new(api_key: Rails.application.credentials.mailchimp[:api_key])
#list_id = Rails.application.credentials.mailchimp[:list_id]
end
def upsert(email)
begin
#gibbon.lists(#list_id).members.create(
body: {
email_address: email,
status: "subscribed"
}
)
rescue Gibbon::MailChimpError => e #This is at the bottom of the Gibbon README
raise e.detail
end
end
end
What I'm trying to figure out is how to return/send Gibbon::MailChimpError back to my Pages#subscribe action. I see it being outputted as a RuntimeError in my console, but I'm not sure the right way to access/pass it along.
And please let me know if there's a better practice for this kind of implementation.
You could move the begin/rescue block to the subscribe action inside your controller to handle the error from there, or even better, you can use rescue_from in your controller like this:
class PagesController < ApplicationController
rescue_from Gibbon::MailChimpError do |e|
# Handle the exception however you want
end
def subscribe
# ....
end
end

StandardError redirect to page

I was handed a project that another developer worked on, without leaving any documentation behind. The code fetches some purchases from a shopping website, looks for a price and notifies the user.
The app may encounter errors like "no results found" and then I raise a standarderror.
I want to redirect the user to the error page and notify them about it but I can't do that because it isn't a controller, so the redirect_to option doesn't work.
services/purchase_checker.rb is called once an hour:
def call
user.transaction do
store_purchase
if better_purchase?
update_purchase
end
end
rescue MyError=> e
store_error(e)
end
def store_error(error)
user.check_errors.create!(error_type: error.class.name, message: error.message)
end
services/my_error.rb:
class MyError< StandardError
def initialize(error_type, error_message)
super(error_message)
#error_type = error_type
end
attr_reader :error_type
end
services/purchase_fetcher.rb:
def parse_result_page
raise purchase_form_page.error if purchase_form_page.error.present?
offer = purchase_page.map{|proposal_section|
propose(proposal_section, purchase) }
.min_by(&:price)
offer or raise MyError.new("No results", "No results could be found")
end
you should create another err class, eg NotFoundError:
offer or raise NotFoundError.new("No results", "No results could be found")
then in your controller:
begin
parse_result_page
rescue NotFoundError => e
redirect_to err_page, :notice => e.message
end
Since this is running in a job, the best way to notify the user would be by email, or some other async notification method. When an error is detected, an email is sent.
If that's not an option for some reason, you can check if a user has check_errors in any relevant controllers. Looking at the store_error(error) method that is called when an error is found, it seems it's creating a new record in the Database to log the error. You should be able to check if a user has any error logged via the user.check_errors relationship.
You could do it like this, for example:
class SomeController < ActionController::Base
# ...
before_action :redirect_if_check_errors
# ...
def redirect_if_check_errors
# Assuming you're using Devise or something similar
if current_user && current_user.check_errors.exists?
redirect_to some_error_page_you_create_for_this_path
end
end
end
This will check for these errors in every action of SomeController and redirect the user to an error page you should create, where you render the errors in the user.check_errors relationship.
There are multiple ways to do this, but I still think sending an email from the Job is a better option if you want to actively notify the user. Or perhaps add an interface element that warns the user whenever user.check_errors has stuff there, for example.
I propose that you do this synchronously so that the response can happen directly in the request/response cycle. Perhaps something like this:
# controller
def search
# do your searching
# ...
if search_results.blank?
# call model method, but do it synchrously
purchase_check = PurchaseChecker.call
end
if purchase_check.is_a?(MyError) # Check if it's your error
redirect_to(some_path, flash: { warning: "Warn them"})
end
end
# model, say PurchaseChecker
def call
# do your code
rescue MyError => e
store_error(e)
e # return the error so that the controller can do something with it
end

authenticate_user! devise for ics stream

I've set up some events where users can subscribe to calendar events via Outlook/Thunderbird/Iphone so on.
I use devise for authentication and in almost all controllers I have set up this devise method:
before_filter :authenticate_user!
The url for the Icalendar feed is:
/events/export_events.ics
When I remove the before_filter :authenticate_user! line basically you don't have to authenticate and then it's possible to subscribe to events.
I got it on the Phone/Thunderbird and Outlook.
When I uncomment this line I get errors on my Iphone which says:
Calendar Subscription
Unable to very account information.
When I enter my credentials I get rejected.
It seems like I have to add something to devise so users can authenticate via the Url.
The Rails log says:
Processing by EventsController#export_events as ICS
Completed 401 Unauthorized in 1ms
Any idea's how I could manage to authenticate this ?
Not important side notes but for the sake of completeness.
I use the Icalendar gem to create/transform my database events into valid Icalendar format.
in my events_controller.rb:
There is the export_events action which works like a charm:
It totally comes up to the authentication but someone might use this snippet for either understanding the problem or for own projects.
def export_events
#events = Event.first(10)
#calendar = Icalendar::Calendar.new
#events.each do |e|
event = Icalendar::Event.new
event.dtstart = e.start.strftime("%Y%m%dT%H%M%S")
event.dtend = e.end.strftime("%Y%m%dT%H%M%S")
event.summary = e.title
#calendar.add_event(event)
end
#calendar.publish
#headers['Content-Type'] = "text/calendar; charset=UTF-8"
#render :text => #calendar.to_ical
#stuff = #calendar.to_ical
respond_to do |w|
w.ics { render text: #stuff }
w.html{}
end
end
routes.rb:
resources :events do
collection do
get 'export_events'
end
end
I managed to authenticate.
I opened up the devise.rb file.
Where I had to uncomment this line:
config.http_authenticatable = true

REST Client for Ruby Gives ActionController::InvalidAuthenticityToken

I have a RESTful Rails application with a resource called "Foo". I'm trying to use REST Client to do a put:
resource = RestClient::Resource.new 'http://localhost:3000/foos/1', :user => 'me', :password => 'secret'
resource.put :name => 'somethingwitty', :content_type => 'application/xml'
But my app raises:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
/usr/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_controller/request_forgery_protection.rb:86:in `verify_authenticity_token'
It seems like my app isn't getting the message that this is an XML request and that the AuthenticityToken should be ignored. Maybe I'm not using REST Client correctly. Any ideas on why I'm getting the exception?
Try putting an :only => [:update, :delete, :create] on the protect_from_forgery line in your application controller.
More info: http://ryandaigle.com/articles/2007/9/24/what-s-new-in-edge-rails-better-cross-site-request-forging-prevention
Use something like:
resource.put '<foo><name>somethingwitty</name></foo>', :content_type => 'application/xml'
I think you need to make two changes;
(a) Use the rails routing to tag this as an XML request
(b) Use HTTP Basic Authentication to authenticate the request.
This means changing your URL above to include the username, password like this
me:secret#localhost:3000/foos/1.xml
also note .xml bit
I guess that somewhere on your server-side you have code that authenticates in-bound requests via a before filter. This needs to work something like this ...
#
# If you haven't authenticated already then you are either
# reqirected to the logon screen (for HTML formats) or
# the browser prompts you. You are always allowed to pass
# the username/password in the URL
#
def login_required
#current_user = valid_session?
unless #current_user
if params["format"]
#
# If you specify a format you must authenticate now
#
do_basic_authentication
else
display_logon_screen
end
end
end
#
# Ask Rails for the login and password then authenticate as if this
# were a new login.
#
def do_basic_authentication
user = authenticate_with_http_basic do |login, password|
User.authenticate(login, password)
end
if user
current_user(#current_user = user)
else
request_http_basic_authentication
end
end
That's from our own app and is triggered by a before_filter in ApplicationController.
Also, I don't think you need the :content_type => 'application/xml'. What I normally do is just call post or put directly like this ..
response = RestClient.post URI.encode(url), :record => args
where the url contains the basic authentication and the ".xml"
Happy coding
Chris
Since your application is a Rails app, it might be easier to use ActiveResource for the client.
Something like:
require 'active_resource'
class Foo < ActiveResource::Base
self.site = 'http://localhost:3000/'
end
foo = Foo.new(:name => 'somethingwitty')
foo.save
You can read up on how to do the authentication on the rdoc site.

Resources